Repository: sublimelsp/LSP

Issue:
LSP crashes on unsaved files.
1. Open a new file.
2. Set Syntax to one of the supported ones.
3. Paste some code.
4. Move mouse over keywords.

```
Traceback (most recent call last):
  File "C:\Apps\Sublime\sublime_plugin.py", line 625, in on_hover
    vel.on_hover(point, hover_zone)
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 1924, in on_hover
    self.request_symbol_hover(point)
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 1934, in request_symbol_hover
    Request.hover(get_document_position(self.view, point)),
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 1880, in get_document_position
    d['textDocument'] = {"uri": filename_to_uri(view.file_name())}
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 885, in filename_to_uri
    return urljoin('file:', pathname2url(path))
  File "./python3.3/nturl2path.py", line 46, in pathname2url
TypeError: argument of type 'NoneType' is not iterable
```


Another situation is closing an unsaved file, which creates following traceback:

```
Traceback (most recent call last):
  File "C:\Apps\Sublime\sublime_plugin.py", line 389, in run_callback
    expr()
  File "C:\Apps\Sublime\sublime_plugin.py", line 452, in <lambda>
    run_callback('on_close', callback, lambda: callback.on_close(v))
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 2460, in on_close
    Events.publish("view.on_close", view)
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 1908, in publish
    listener(*args)
  File "C:\Apps\Sublime\Data\Packages\LSP\main.py", line 1684, in remove_diagnostics
    if not window.find_open_file(view.file_name()):
  File "C:\Apps\Sublime\sublime.py", line 315, in find_open_file
    view_id = sublime_api.window_find_open_file(self.window_id, fname)
TypeError: String required
```


Discussion:
I would like get mypy to a point where it picks up all these `TypeError`s where `view.file_name()` can be None. I welcome a fix but may take a stab at this one myself now.

Resolving patch:
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -142,47 +142,47 @@ def initialize(cls, params):
         return Request("initialize", params)
 
     @classmethod
-    def hover(cls, params):
+    def hover(cls, params: dict):
         return Request("textDocument/hover", params)
 
     @classmethod
-    def complete(cls, params):
+    def complete(cls, params: dict):
         return Request("textDocument/completion", params)
 
     @classmethod
-    def signatureHelp(cls, params):
+    def signatureHelp(cls, params: dict):
         return Request("textDocument/signatureHelp", params)
 
     @classmethod
-    def references(cls, params):
+    def references(cls, params: dict):
         return Request("textDocument/references", params)
 
     @classmethod
-    def definition(cls, params):
+    def definition(cls, params: dict):
         return Request("textDocument/definition", params)
 
     @classmethod
-    def rename(cls, params):
+    def rename(cls, params: dict):
         return Request("textDocument/rename", params)
 
     @classmethod
-    def codeAction(cls, params):
+    def codeAction(cls, params: dict):
         return Request("textDocument/codeAction", params)
 
     @classmethod
-    def executeCommand(cls, params):
+    def executeCommand(cls, params: dict):
         return Request("workspace/executeCommand", params)
 
     @classmethod
-    def formatting(cls, params):
+    def formatting(cls, params: dict):
         return Request("textDocument/formatting", params)
 
     @classmethod
-    def documentSymbols(cls, params):
+    def documentSymbols(cls, params: dict):
         return Request("textDocument/documentSymbol", params)
 
     @classmethod
-    def resolveCompletionItem(cls, params):
+    def resolveCompletionItem(cls, params: dict):
         return Request("completionItem/resolve", params)
 
     def __repr__(self):
@@ -211,23 +211,23 @@ def initialized(cls):
         return Notification("initialized", None)
 
     @classmethod
-    def didOpen(cls, params):
+    def didOpen(cls, params: dict):
         return Notification("textDocument/didOpen", params)
 
     @classmethod
-    def didChange(cls, params):
+    def didChange(cls, params: dict):
         return Notification("textDocument/didChange", params)
 
     @classmethod
-    def didSave(cls, params):
+    def didSave(cls, params: dict):
         return Notification("textDocument/didSave", params)
 
     @classmethod
-    def didClose(cls, params):
+    def didClose(cls, params: dict):
         return Notification("textDocument/didClose", params)
 
     @classmethod
-    def didChangeConfiguration(cls, params):
+    def didChangeConfiguration(cls, params: dict):
         return Notification("workspace/didChangeConfiguration", params)
 
     @classmethod
@@ -1083,40 +1083,46 @@ def notify_did_open(view: sublime.View):
     client = client_for_view(view)
     if client and config:
         view.settings().set("show_definitions", False)
-        if view.file_name() not in document_states:
-            ds = get_document_state(view.file_name())
-            if show_view_status:
-                view.set_status("lsp_clients", config.name)
-            params = {
-                "textDocument": {
-                    "uri": filename_to_uri(view.file_name()),
-                    "languageId": config.languageId,
-                    "text": view.substr(sublime.Region(0, view.size())),
-                    "version": ds.version
+        view_file = view.file_name()
+        if view_file:
+            if view_file not in document_states:
+                ds = get_document_state(view_file)
+                if show_view_status:
+                    view.set_status("lsp_clients", config.name)
+                params = {
+                    "textDocument": {
+                        "uri": filename_to_uri(view_file),
+                        "languageId": config.languageId,
+                        "text": view.substr(sublime.Region(0, view.size())),
+                        "version": ds.version
+                    }
                 }
-            }
-            client.send_notification(Notification.didOpen(params))
+                client.send_notification(Notification.didOpen(params))
 
 
 def notify_did_close(view: sublime.View):
-    if view.file_name() in document_states:
-        del document_states[view.file_name()]
-        config = config_for_scope(view)
-        clients = window_clients(sublime.active_window())
-        if config and config.name in clients:
-            client = clients[config.name]
-            params = {"textDocument": {"uri": filename_to_uri(view.file_name())}}
-            client.send_notification(Notification.didClose(params))
+    file_name = view.file_name()
+    if file_name:
+        if file_name in document_states:
+            del document_states[file_name]
+            config = config_for_scope(view)
+            clients = window_clients(sublime.active_window())
+            if config and config.name in clients:
+                client = clients[config.name]
+                params = {"textDocument": {"uri": filename_to_uri(file_name)}}
+                client.send_notification(Notification.didClose(params))
 
 
 def notify_did_save(view: sublime.View):
-    if view.file_name() in document_states:
-        client = client_for_view(view)
-        if client:
-            params = {"textDocument": {"uri": filename_to_uri(view.file_name())}}
-            client.send_notification(Notification.didSave(params))
-    else:
-        debug('document not tracked', view.file_name())
+    file_name = view.file_name()
+    if file_name:
+        if file_name in document_states:
+            client = client_for_view(view)
+            if client:
+                params = {"textDocument": {"uri": filename_to_uri(file_name)}}
+                client.send_notification(Notification.didSave(params))
+        else:
+            debug('document not tracked', file_name)
 
 
 # TODO: this should be per-window ?
@@ -1173,24 +1179,26 @@ def purge_did_change(buffer_id: int, buffer_version=None):
 
 
 def notify_did_change(view: sublime.View):
-    if view.buffer_id() in pending_buffer_changes:
-        del pending_buffer_changes[view.buffer_id()]
-    # config = config_for_scope(view)
-    client = client_for_view(view)
-    if client:
-        document_state = get_document_state(view.file_name())
-        uri = filename_to_uri(view.file_name())
-        params = {
-            "textDocument": {
-                "uri": uri,
-                # "languageId": config.languageId, clangd does not like this field, but no server uses it?
-                "version": document_state.inc_version(),
-            },
-            "contentChanges": [{
-                "text": view.substr(sublime.Region(0, view.size()))
-            }]
-        }
-        client.send_notification(Notification.didChange(params))
+    file_name = view.file_name()
+    if file_name:
+        if view.buffer_id() in pending_buffer_changes:
+            del pending_buffer_changes[view.buffer_id()]
+        # config = config_for_scope(view)
+        client = client_for_view(view)
+        if client:
+            document_state = get_document_state(file_name)
+            uri = filename_to_uri(file_name)
+            params = {
+                "textDocument": {
+                    "uri": uri,
+                    # "languageId": config.languageId, clangd does not like this field, but no server uses it?
+                    "version": document_state.inc_version(),
+                },
+                "contentChanges": [{
+                    "text": view.substr(sublime.Region(0, view.size()))
+                }]
+            }
+            client.send_notification(Notification.didChange(params))
 
 
 document_sync_initialized = False
@@ -1396,9 +1404,11 @@ def run(self, edit, event=None):
         client = client_for_view(self.view)
         if client:
             pos = get_position(self.view, event)
-            request = Request.definition(get_document_position(self.view, pos))
-            client.send_request(
-                request, lambda response: self.handle_response(response, pos))
+            document_position = get_document_position(self.view, pos)
+            if document_position:
+                request = Request.definition(document_position)
+                client.send_request(
+                    request, lambda response: self.handle_response(response, pos))
 
     def handle_response(self, response, position):
         window = sublime.active_window()
@@ -1535,12 +1545,13 @@ def run(self, edit, event=None):
         if client:
             pos = get_position(self.view, event)
             document_position = get_document_position(self.view, pos)
-            document_position['context'] = {
-                "includeDeclaration": False
-            }
-            request = Request.references(document_position)
-            client.send_request(
-                request, lambda response: self.handle_response(response, pos))
+            if document_position:
+                document_position['context'] = {
+                    "includeDeclaration": False
+                }
+                request = Request.references(document_position)
+                client.send_request(
+                    request, lambda response: self.handle_response(response, pos))
 
     def handle_response(self, response, pos):
         window = self.view.window()
@@ -1683,11 +1694,12 @@ def remove_diagnostics(view: sublime.View):
     window = sublime.active_window()
 
     file_path = view.file_name()
-    if not window.find_open_file(view.file_name()):
-        update_file_diagnostics(window, file_path, 'lsp', [])
-        update_diagnostics_panel(window)
-    else:
-        debug('file still open?')
+    if file_path:
+        if not window.find_open_file(file_path):
+            update_file_diagnostics(window, file_path, 'lsp', [])
+            update_diagnostics_panel(window)
+        else:
+            debug('file still open?')
 
 
 def handle_diagnostics(update: 'Any'):
@@ -1876,20 +1888,17 @@ def start_server(server_binary_args, working_dir, env):
         printf(err)
 
 
-def get_document_range(view: sublime.View, region: sublime.Region) -> OrderedDict:
-    d = OrderedDict()  # type: OrderedDict[str, Any]
-    d['textDocument'] = {"uri": filename_to_uri(view.file_name())}
-    d['range'] = Range.from_region(view, region).to_lsp()
-    return d
-
-
-def get_document_position(view: sublime.View, point) -> OrderedDict:
-    if not point:
-        point = view.sel()[0].begin()
-    d = OrderedDict()  # type: OrderedDict[str, Any]
-    d['textDocument'] = {"uri": filename_to_uri(view.file_name())}
-    d['position'] = Point.from_text_point(view, point).to_lsp()
-    return d
+def get_document_position(view: sublime.View, point) -> 'Optional[OrderedDict]':
+    file_name = view.file_name()
+    if file_name:
+        if not point:
+            point = view.sel()[0].begin()
+        d = OrderedDict()  # type: OrderedDict[str, Any]
+        d['textDocument'] = {"uri": filename_to_uri(file_name)}
+        d['position'] = Point.from_text_point(view, point).to_lsp()
+        return d
+    else:
+        return None
 
 
 class Events:
@@ -1940,9 +1949,11 @@ def request_symbol_hover(self, point):
         if client and client.has_capability('hoverProvider'):
             word_at_sel = self.view.classify(point)
             if word_at_sel & SUBLIME_WORD_MASK:
-                client.send_request(
-                    Request.hover(get_document_position(self.view, point)),
-                    lambda response: self.handle_response(response, point))
+                document_position = get_document_position(self.view, point)
+                if document_position:
+                    client.send_request(
+                        Request.hover(document_position),
+                        lambda response: self.handle_response(response, point))
 
     def handle_response(self, response, point):
         debug(response)
@@ -2174,10 +2185,12 @@ def do_request(self, prefix, locations):
 
         if complete_all_chars or self.is_after_trigger_character(locations[0]):
             purge_did_change(view.buffer_id())
-            client.send_request(
-                Request.complete(get_document_position(view, locations[0])),
-                self.handle_response)
-            self.state = CompletionState.REQUESTING
+            document_position = get_document_position(view, locations[0])
+            if document_position:
+                client.send_request(
+                    Request.complete(document_position),
+                    self.handle_response)
+                self.state = CompletionState.REQUESTING
 
     def format_completion(self, item) -> 'Tuple[str, str]':
         # Sublime handles snippets automatically, so we don't have to care about insertTextFormat.
@@ -2255,9 +2268,11 @@ def on_modified_async(self):
                 client = client_for_view(self.view)
                 if client:
                     purge_did_change(self.view.buffer_id())
-                    client.send_request(
-                        Request.signatureHelp(get_document_position(self.view, pos)),
-                        lambda response: self.handle_response(response, pos))
+                    document_position = get_document_position(self.view, pos)
+                    if document_position:
+                        client.send_request(
+                            Request.signatureHelp(document_position),
+                            lambda response: self.handle_response(response, pos))
             else:
                 # TODO: this hides too soon.
                 if self.view.is_popup_visible():
@@ -2317,11 +2332,12 @@ def get_diagnostics_for_view(view: sublime.View) -> 'List[Diagnostic]':
     window = view.window()
     file_path = view.file_name()
     origin = 'lsp'
-    if window.id() in window_file_diagnostics:
-        file_diagnostics = window_file_diagnostics[window.id()]
-        if file_path in file_diagnostics:
-            if origin in file_diagnostics[file_path]:
-                return file_diagnostics[file_path][origin]
+    if file_path:
+        if window.id() in window_file_diagnostics:
+            file_diagnostics = window_file_diagnostics[window.id()]
+            if file_path in file_diagnostics:
+                if origin in file_diagnostics[file_path]:
+                    return file_diagnostics[file_path][origin]
     return []
 
 


Test patch: