Skip to content

Commit

Permalink
Merge d2c9c9a into a618236
Browse files Browse the repository at this point in the history
  • Loading branch information
tomv564 committed Apr 27, 2019
2 parents a618236 + d2c9c9a commit 97ed576
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 144 deletions.
2 changes: 1 addition & 1 deletion plugin/code_actions.py
Expand Up @@ -9,7 +9,7 @@

from .core.registry import client_for_view, LspTextCommand
from .core.protocol import Request
from .core.diagnostics import get_point_diagnostics
from .diagnostics import get_point_diagnostics
from .core.url import filename_to_uri
from .core.views import region_to_range
from .core.registry import session_for_view
Expand Down
144 changes: 48 additions & 96 deletions plugin/core/diagnostics.py
@@ -1,121 +1,73 @@
import sublime

from .logging import debug
from .url import uri_to_filename
from .protocol import Diagnostic
from .events import global_events
from .views import range_to_region
from .windows import WindowLike, ViewLike

assert Diagnostic

try:
import sublime
from typing import Any, List, Dict, Tuple, Callable, Optional
assert sublime
assert Any and List and Dict and Tuple and Callable and Optional
assert ViewLike and WindowLike
except ImportError:
pass


global_diagnostics = dict(
) # type: Dict[int, Dict[str, Dict[str, List[Diagnostic]]]]


def update_file_diagnostics(window: sublime.Window, file_path: str, source: str,
diagnostics: 'List[Diagnostic]') -> bool:
updated = False
if diagnostics:
file_diagnostics = global_diagnostics.setdefault(window.id(), dict()).setdefault(
file_path, dict())
file_diagnostics[source] = diagnostics
updated = True
else:
if window.id() in global_diagnostics:
window_diagnostics = global_diagnostics[window.id()]
if file_path in window_diagnostics:
if source in window_diagnostics[file_path]:
updated = True
del window_diagnostics[file_path][source]
if not window_diagnostics[file_path]:
del window_diagnostics[file_path]
return updated


class DiagnosticsUpdate(object):
def __init__(self, window: sublime.Window, client_name: str,
def __init__(self, window, client_name: str,
file_path: str, diagnostics: 'List[Diagnostic]') -> 'None':
self.window = window
self.client_name = client_name
self.file_path = file_path
self.diagnostics = diagnostics


def handle_client_diagnostics(window: sublime.Window, client_name: str, update: dict):
maybe_file_uri = update.get('uri')
if maybe_file_uri is not None:
file_path = uri_to_filename(maybe_file_uri)

diagnostics = list(
Diagnostic.from_lsp(item) for item in update.get('diagnostics', []))

if update_file_diagnostics(window, file_path, client_name, diagnostics):
global_events.publish("document.diagnostics",
DiagnosticsUpdate(window, client_name, file_path, diagnostics))
else:
debug('missing uri in diagnostics update')
# TODO: expose updates to features

class WindowDiagnostics(object):

def remove_diagnostics(view: sublime.View, client_name: str):
"""Removes diagnostics for a file
"""
window = view.window() or sublime.active_window()
def __init__(self):
self._diagnostics = {} # type: Dict[str, Dict[str, List[Diagnostic]]]
self._on_updated = None # type: Optional[Callable]

file_path = view.file_name()
if file_path:
if update_file_diagnostics(window, file_path, client_name, []):
global_events.publish("document.diagnostics", DiagnosticsUpdate(window, client_name, file_path, []))
def get(self) -> 'Dict[str, Dict[str, List[Diagnostic]]]':
return self._diagnostics

def set_on_updated(self, update_handler: 'Callable'):
self._on_updated = update_handler

class GlobalDiagnostics(object):
def update(self, window: 'Any', client_name: str, update: dict):
handle_client_diagnostics(window, client_name, update)
def get_by_path(self, file_path: str) -> 'List[Diagnostic]':
view_diagnostics = []
if file_path in self._diagnostics:
for origin in self._diagnostics[file_path]:
view_diagnostics.extend(self._diagnostics[file_path][origin])
return view_diagnostics

def remove(self, view: 'Any', client_name: str):
"""Removes diagnostics for a file if no views exist for it
"""
remove_diagnostics(view, client_name)


def get_line_diagnostics(view, point):
row, _ = view.rowcol(point)
diagnostics = get_diagnostics_for_view(view)
return tuple(
diagnostic for diagnostic in diagnostics
if diagnostic.range.start.row <= row <= diagnostic.range.end.row
)


def get_point_diagnostics(view, point):
diagnostics = get_diagnostics_for_view(view)
return tuple(
diagnostic for diagnostic in diagnostics
if range_to_region(diagnostic.range, view).contains(point)
)


def get_window_diagnostics(window: sublime.Window) -> 'Optional[Dict[str, Dict[str, List[Diagnostic]]]]':
return global_diagnostics.get(window.id())


def get_diagnostics_for_view(view: sublime.View) -> 'List[Diagnostic]':
view_diagnostics = []
window = view.window()
file_path = view.file_name()
if file_path and window:
if window.id() in global_diagnostics:
file_diagnostics = global_diagnostics[window.id()]
if file_path in file_diagnostics:
for origin in file_diagnostics[file_path]:
view_diagnostics.extend(file_diagnostics[file_path][origin])
return view_diagnostics
def update(self, file_path: str, client_name: str, diagnostics: 'List[Diagnostic]') -> bool:
updated = False
if diagnostics:
file_diagnostics = self._diagnostics.setdefault(file_path, dict())
file_diagnostics[client_name] = diagnostics
updated = True
else:
if file_path in self._diagnostics:
if client_name in self._diagnostics[file_path]:
updated = True
del self._diagnostics[file_path][client_name]
if not self._diagnostics[file_path]:
del self._diagnostics[file_path]
return updated

def handle_client_diagnostics(self, client_name: str, update: dict):
maybe_file_uri = update.get('uri')
if maybe_file_uri is not None:
file_path = uri_to_filename(maybe_file_uri)

diagnostics = list(
Diagnostic.from_lsp(item) for item in update.get('diagnostics', []))

if self.update(file_path, client_name, diagnostics):
if self._on_updated:
self._on_updated(file_path, client_name, diagnostics)
else:
debug('missing uri in diagnostics update')

def remove(self, file_path: str, client_name: str):
self.update(file_path, client_name, [])
4 changes: 1 addition & 3 deletions plugin/core/registry.py
@@ -1,6 +1,5 @@
import sublime
import sublime_plugin
from .diagnostics import GlobalDiagnostics
from .windows import WindowRegistry, DocumentHandlerFactory
from .configurations import (
ConfigManager
Expand Down Expand Up @@ -102,10 +101,9 @@ def unload_sessions():


configs = ConfigManager(client_configs.all)
diagnostics = GlobalDiagnostics()
documents = DocumentHandlerFactory(sublime, settings)
handlers_dispatcher = LanguageHandlerDispatcher()
windows = WindowRegistry(configs, documents, diagnostics, start_window_config, sublime, handlers_dispatcher)
windows = WindowRegistry(configs, documents, start_window_config, sublime, handlers_dispatcher)


def config_for_scope(view: 'Any', point=None) -> 'Optional[ClientConfig]':
Expand Down
59 changes: 59 additions & 0 deletions plugin/core/test_diagnostics.py
@@ -0,0 +1,59 @@
import unittest
from .diagnostics import WindowDiagnostics
from .protocol import Diagnostic, Range, Point
# from .configurations import WindowConfigManager, _merge_dicts, ConfigManager, is_supported_syntax
# from .test_session import test_config, test_language
from .test_protocol import LSP_MINIMAL_DIAGNOSTIC


class WindowDiagnosticsTest(unittest.TestCase):

def test_empty_diagnostics(self):
wd = WindowDiagnostics()
self.assertEqual(wd.get_by_path(__file__), [])

# todo: remove

def test_updated_diagnostics(self):
wd = WindowDiagnostics()

test_file_path = "test.py"
diag = Diagnostic('message', Range(Point(0, 0), Point(1, 1)), 1, None, dict())

wd.update(test_file_path, "test_server", [diag])
view_diags = wd.get_by_path(test_file_path)
self.assertEqual(len(view_diags), 1)
self.assertEqual(view_diags[0], diag)

wd.update(test_file_path, "test_server", [])
view_diags = wd.get_by_path(test_file_path)
self.assertEqual(len(view_diags), 0)

def test_handle_diagnostics_update(self):
wd = WindowDiagnostics()

test_file_path = "/test.py"
update = {
'uri': 'file:///test.py',
'diagnostics': [LSP_MINIMAL_DIAGNOSTIC]
}

wd.handle_client_diagnostics("test_server", update)

view_diags = wd.get_by_path(test_file_path)
self.assertEqual(len(view_diags), 1)

def test_remove_diagnostics(self):
wd = WindowDiagnostics()

test_file_path = "test.py"
diag = Diagnostic('message', Range(Point(0, 0), Point(1, 1)), 1, None, dict())

wd.update(test_file_path, "test_server", [diag])
view_diags = wd.get_by_path(test_file_path)
self.assertEqual(len(view_diags), 1)

wd.remove(test_file_path, "test_server")

view_diags = wd.get_by_path(test_file_path)
self.assertEqual(len(view_diags), 0)
32 changes: 11 additions & 21 deletions plugin/core/test_windows.py
@@ -1,4 +1,5 @@
from .windows import WindowManager, WindowRegistry, WindowLike, ViewLike
from .windows import WindowManager, WindowRegistry, ViewLike
from .diagnostics import WindowDiagnostics
from .sessions import create_session, Session
from .test_session import MockClient, test_config, test_language
from .test_rpc import MockSettings
Expand Down Expand Up @@ -219,17 +220,6 @@ def for_window(self, window, configs):
return MockDocuments()


class MockDiagnostics(object):
def __init__(self):
pass

def update(self, window: WindowLike, client_name: str, update: dict) -> None:
pass

def remove(self, view: ViewLike, client_name: str) -> None:
pass


def mock_start_session(window, project_path, config, on_created: 'Callable', on_ended: 'Callable'):
return create_session(test_config, project_path, dict(), MockSettings(),
bootstrap_client=MockClient(),
Expand All @@ -241,7 +231,7 @@ class WindowRegistryTests(unittest.TestCase):

def test_can_get_window_state(self):
windows = WindowRegistry(TestGlobalConfigs(), TestDocumentHandlerFactory(),
MockDiagnostics(), mock_start_session,
mock_start_session,
test_sublime, MockHandlerDispatcher())
test_window = MockWindow()
wm = windows.lookup(test_window)
Expand All @@ -251,7 +241,7 @@ def test_removes_window_state(self):
global_events.reset()
test_window = MockWindow([[MockView(__file__)]])
windows = WindowRegistry(TestGlobalConfigs(), TestDocumentHandlerFactory(),
MockDiagnostics(), mock_start_session,
mock_start_session,
test_sublime, MockHandlerDispatcher())
wm = windows.lookup(test_window)
wm.start_active_views()
Expand All @@ -271,7 +261,7 @@ class WindowManagerTests(unittest.TestCase):
def test_can_start_active_views(self):
docs = MockDocuments()
wm = WindowManager(MockWindow([[MockView(__file__)]]), MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
WindowDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
wm.start_active_views()

# session must be started (todo: verify session is ready)
Expand All @@ -281,7 +271,7 @@ def test_can_start_active_views(self):
def test_can_open_supported_view(self):
docs = MockDocuments()
window = MockWindow([[]])
wm = WindowManager(window, MockConfigs(), docs, MockDiagnostics(), mock_start_session, test_sublime,
wm = WindowManager(window, MockConfigs(), docs, WindowDiagnostics(), mock_start_session, test_sublime,
MockHandlerDispatcher())

wm.start_active_views()
Expand All @@ -298,7 +288,7 @@ def test_can_open_supported_view(self):
def test_can_restart_sessions(self):
docs = MockDocuments()
wm = WindowManager(MockWindow([[MockView(__file__)]]), MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
WindowDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
wm.start_active_views()

# session must be started (todo: verify session is ready)
Expand All @@ -320,7 +310,7 @@ def test_ends_sessions_when_closed(self):
docs = MockDocuments()
test_window = MockWindow([[MockView(__file__)]])
wm = WindowManager(test_window, MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
WindowDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
wm.start_active_views()

# session must be started (todo: verify session is ready)
Expand All @@ -341,7 +331,7 @@ def test_ends_sessions_when_quick_switching(self):
docs = MockDocuments()
test_window = MockWindow([[MockView(__file__)]])
wm = WindowManager(test_window, MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
WindowDiagnostics(), mock_start_session, test_sublime, MockHandlerDispatcher())
wm.start_active_views()

# session must be started (todo: verify session is ready)
Expand All @@ -367,7 +357,7 @@ def test_ends_sessions_when_quick_switching(self):
def test_offers_restart_on_crash(self):
docs = MockDocuments()
wm = WindowManager(MockWindow([[MockView(__file__)]]), MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime,
WindowDiagnostics(), mock_start_session, test_sublime,
MockHandlerDispatcher())
wm.start_active_views()

Expand All @@ -389,7 +379,7 @@ def test_invokes_language_handler(self):
docs = MockDocuments()
dispatcher = MockHandlerDispatcher()
wm = WindowManager(MockWindow([[MockView(__file__)]]), MockConfigs(), docs,
MockDiagnostics(), mock_start_session, test_sublime,
WindowDiagnostics(), mock_start_session, test_sublime,
dispatcher)
wm.start_active_views()

Expand Down

0 comments on commit 97ed576

Please sign in to comment.