nebius/SWE-rebench-V2-PRs huggingface dataset:
Repository: palantir/python-language-server
Pull request #80 (2017-07-25 16:15:02)

Unquote parsed urls in both Workspace and Document
Naïve fix for #79, where document URIs containing encoded spaces (`%20`) were not url decoded before being passed to Jedi.

Fix also applied to Workspace, as it is already written to handle URIs.

- [x] Test added for workspace and document

palantirtech: Thanks for your interest in [palantir/python-language-server](https://github.com/palantir/python-language-server), @tomv564! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.
gatesn: Thanks very much for this! Made a couple of comments, after which it looks good to merge. Shame we can't get Circle to run for forks though.
tomv564: Fixed both your suggestions, thanks for the review!
gatesn: I've cloned and tested locally, all looks good!

Patch:
diff --git a/pyls/workspace.py b/pyls/workspace.py
index 3837b03..8747399 100644
--- a/pyls/workspace.py
+++ b/pyls/workspace.py
@@ -4,7 +4,7 @@ import logging
 import os
 import re
 import sys
-from urllib.parse import urlparse, urlunparse
+from urllib.parse import urlparse, urlunparse, unquote
 
 import jedi
 
@@ -24,7 +24,7 @@ class Workspace(object):
     M_SHOW_MESSAGE = 'window/showMessage'
 
     def __init__(self, root, lang_server=None):
-        self._url_parsed = urlparse(root)
+        self._url_parsed = urlparse(unquote(root))
         self.root = self._url_parsed.path
         self._docs = {}
         self._lang_server = lang_server
@@ -36,7 +36,7 @@ class Workspace(object):
         return self._docs[doc_uri]
 
     def put_document(self, doc_uri, content, version=None):
-        path = urlparse(doc_uri).path
+        path = unquote(urlparse(doc_uri).path)
         self._docs[doc_uri] = Document(
             doc_uri, content, sys_path=self.syspath_for_path(path), version=version
         )
@@ -80,7 +80,7 @@ class Document(object):
     def __init__(self, uri, source=None, version=None, local=True, sys_path=None):
         self.uri = uri
         self.version = version
-        self.path = urlparse(uri).path
+        self.path = unquote(urlparse(uri).path)
         self.filename = os.path.basename(self.path)
 
         self._local = local


Test patch:
diff --git a/test/test_workspace.py b/test/test_workspace.py
index f30afa7..f2ad306 100644
--- a/test/test_workspace.py
+++ b/test/test_workspace.py
@@ -51,3 +51,13 @@ def test_non_root_project(pyls):
     pyls.workspace.put_document(test_uri, 'assert True')
     test_doc = pyls.workspace.get_document(test_uri)
     assert project_root in pyls.workspace.syspath_for_path(test_doc.path)
+
+
+def test_urlencoded_paths():
+    root_uri = "file:///Encoded%20Space/"
+    file_uri = root_uri + "test.py"
+    ws = workspace.Workspace(root_uri)
+    assert ws.root == "/Encoded Space/"
+    ws.put_document(file_uri, "")
+    doc = ws.get_document(file_uri)
+    assert doc.path == '/Encoded Space/test.py'
Repository: palantir/python-language-server
Pull request #81 (2017-07-26 06:53:38)

Reverse urlencoding/unquote
For example, if a question mark was urlencoded then it would change the uri if it was unquoted first and then parsed.

gatesn: Fixes #79 

Patch:
diff --git a/pyls/workspace.py b/pyls/workspace.py
index 8747399..56527ab 100644
--- a/pyls/workspace.py
+++ b/pyls/workspace.py
@@ -24,8 +24,8 @@ class Workspace(object):
     M_SHOW_MESSAGE = 'window/showMessage'
 
     def __init__(self, root, lang_server=None):
-        self._url_parsed = urlparse(unquote(root))
-        self.root = self._url_parsed.path
+        self._url_parsed = urlparse(root)
+        self.root = unquote(self._url_parsed.path)
         self._docs = {}
         self._lang_server = lang_server
 


Test patch:
diff --git a/test/test_workspace.py b/test/test_workspace.py
index f2ad306..37cbf2d 100644
--- a/test/test_workspace.py
+++ b/test/test_workspace.py
@@ -54,10 +54,12 @@ def test_non_root_project(pyls):
 
 
 def test_urlencoded_paths():
-    root_uri = "file:///Encoded%20Space/"
+    root_uri = "file:///Encoded%20%3FSpace/"
     file_uri = root_uri + "test.py"
+
     ws = workspace.Workspace(root_uri)
-    assert ws.root == "/Encoded Space/"
+    assert ws.root == "/Encoded ?Space/"
+
     ws.put_document(file_uri, "")
     doc = ws.get_document(file_uri)
-    assert doc.path == '/Encoded Space/test.py'
+    assert doc.path == '/Encoded ?Space/test.py'
Repository: palantir/python-language-server
Pull request #91 (2017-08-10 13:55:48)

Fix edits that occur at the end of the file
We had previously special-cased an empty file by creating an empty line. 

But the case was more general and happens whenever edits occur at the very end of a file. 

We remove the special case for empty, and instead special case end-of-file edits. This is because when we enumerate `lines`, we stop enumerating 1 before the change line!


Patch:
diff --git a/pyls/workspace.py b/pyls/workspace.py
index 29f898d..ed1f807 100644
--- a/pyls/workspace.py
+++ b/pyls/workspace.py
@@ -92,9 +92,7 @@ class Document(object):
 
     @property
     def lines(self):
-        # An empty document is much nicer to deal with assuming it has a single
-        # line with no characters and no final newline.
-        return self.source.splitlines(True) or [u'']
+        return self.source.splitlines(True)
 
     @property
     def source(self):
@@ -118,7 +116,13 @@ class Document(object):
         end_line = change_range['end']['line']
         end_col = change_range['end']['character']
 
+        # Check for an edit occuring at the very end of the file
+        if start_line == len(self.lines):
+            self._source = self.source + text
+            return
+
         new = io.StringIO()
+
         # Iterate over the existing document until we hit the edit range,
         # at which point we write the new text, then loop until we hit
         # the end of the range and continue writing.


Test patch:
diff --git a/test/test_document.py b/test/test_document.py
index 8e74a4a..393e8d1 100644
--- a/test/test_document.py
+++ b/test/test_document.py
@@ -77,3 +77,20 @@ def test_document_multiline_edit():
         "def hello(a, b):\n",
         "    print a, b\n"
     ]
+
+
+def test_document_end_of_file_edit():
+    old = [
+        "print 'a'\n",
+        "print 'b'\n"
+    ]
+    doc = Document('file:///uri', u''.join(old))
+    doc.apply_change({'text': u'o', 'range': {
+        'start': {'line': 2, 'character': 0},
+        'end': {'line': 2, 'character': 0}
+    }})
+    assert doc.lines == [
+        "print 'a'\n",
+        "print 'b'\n",
+        "o",
+    ]
Repository: palantir/python-language-server
Pull request #164 (2017-10-20 19:18:40)

pycodestyle config: convert arrays to comma seperated strings
This fixes https://github.com/palantir/python-language-server/pull/156#issuecomment-334129877 since pycodestyle expects the config values as a comma separated string.

Unfortunately ignoring a specific error code still doesn't work correctly, all errors are ignored instead. The same happens for warnings too. Any idea why this is happening?

Fixes #156 

gatesn: Thanks for this! Is there a separate ticket open for the other issue? I can look into some time this week probably

Patch:
diff --git a/pyls/_utils.py b/pyls/_utils.py
index 0bc490a..312c740 100644
--- a/pyls/_utils.py
+++ b/pyls/_utils.py
@@ -23,3 +23,7 @@ def debounce(interval_s):
 def camel_to_underscore(string):
     s1 = FIRST_CAP_RE.sub(r'\1_\2', string)
     return ALL_CAP_RE.sub(r'\1_\2', s1).lower()
+
+
+def list_to_string(value):
+    return ",".join(value) if type(value) == list else value
diff --git a/pyls/plugins/pycodestyle_lint.py b/pyls/plugins/pycodestyle_lint.py
index 74d54a0..caae341 100644
--- a/pyls/plugins/pycodestyle_lint.py
+++ b/pyls/plugins/pycodestyle_lint.py
@@ -26,7 +26,7 @@ def pyls_lint(config, document):
 
     # Then, read the PYLS configuration
     ide_conf = config.plugin_settings('pycodestyle')
-    conf.update({_utils.camel_to_underscore(k): v for k, v in ide_conf.items()})
+    conf.update({_utils.camel_to_underscore(k): _utils.list_to_string(v) for k, v in ide_conf.items()})
 
     # Finally, read the project configuration
     conf.update(_config_from_files(config.find_parents(document.path, CONFIG_FILES)))


Test patch:
diff --git a/test/test_utils.py b/test/test_utils.py
index 911519c..d400edf 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -24,3 +24,13 @@ def test_debounce():
     time.sleep(interval * 2)
     call_m()
     assert call_m._count == 2
+
+
+def test_list_to_string():
+    assert _utils.list_to_string("string") == "string"
+    assert _utils.list_to_string(["a", "r", "r", "a", "y"]) == "a,r,r,a,y"
+
+
+def test_camel_to_underscore():
+    assert _utils.camel_to_underscore("camelCase") == "camel_case"
+    assert _utils.camel_to_underscore("under_score") == "under_score"
Repository: palantir/python-language-server
Pull request #216 (2018-01-04 15:13:29)

Fix character normalization
The language server will throw a exception if a request is sent from an empty document because `self.lines == []` and therefor `self.lines[0]` will throw `IndexError: list index out of range`.


Patch:
diff --git a/pyls/_utils.py b/pyls/_utils.py
index 3251fdb..8141431 100644
--- a/pyls/_utils.py
+++ b/pyls/_utils.py
@@ -126,3 +126,10 @@ def format_docstring(contents):
     contents = contents.replace('  ', u'\u00A0' * 2)
     contents = contents.replace('*', '\\*')
     return contents
+
+
+def clip_column(column, lines, line_number):
+    # Normalise the position as per the LSP that accepts character positions > line length
+    # https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#position
+    max_column = len(lines[line_number]) - 1 if len(lines) > line_number else 0
+    return min(column, max_column)
diff --git a/pyls/workspace.py b/pyls/workspace.py
index a4ee406..bf9f7c3 100644
--- a/pyls/workspace.py
+++ b/pyls/workspace.py
@@ -264,9 +264,5 @@ class Document(object):
         }
         if position:
             kwargs['line'] = position['line'] + 1
-
-            # Normalise the position as per the LSP that accepts character positions > line length
-            # https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md#position
-            line_len = len(self.lines[position['line']])
-            kwargs['column'] = min(position['character'], line_len - 1)
+            kwargs['column'] = _utils.clip_column(position['character'], self.lines, position['line'])
         return jedi.Script(**kwargs)


Test patch:
diff --git a/test/test_utils.py b/test/test_utils.py
index f31e96a..6675374 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -49,3 +49,9 @@ def test_merge_dicts():
         {'a': True, 'b': {'x': 123, 'y': {'hello': 'world'}}},
         {'a': False, 'b': {'y': [], 'z': 987}}
     ) == {'a': False, 'b': {'x': 123, 'y': [], 'z': 987}}
+
+
+def test_clip_column():
+    assert _utils.clip_column(5, ['123'], 0) == 2
+    assert _utils.clip_column(2, ['\n', '123'], 1) == 2
+    assert _utils.clip_column(0, [], 0) == 0
Repository: palantir/python-language-server
Pull request #470 (2019-01-08 22:51:32)

Rename jsonrpc package


Patch:
diff --git a/pyls/python_ls.py b/pyls/python_ls.py
index da9d8f4..c85fe9e 100644
--- a/pyls/python_ls.py
+++ b/pyls/python_ls.py
@@ -3,9 +3,9 @@ import logging
 import socketserver
 import threading
 
-from jsonrpc.dispatchers import MethodDispatcher
-from jsonrpc.endpoint import Endpoint
-from jsonrpc.streams import JsonRpcStreamReader, JsonRpcStreamWriter
+from pyls_jsonrpc.dispatchers import MethodDispatcher
+from pyls_jsonrpc.endpoint import Endpoint
+from pyls_jsonrpc.streams import JsonRpcStreamReader, JsonRpcStreamWriter
 
 from . import lsp, _utils, uris
 from .config import config
diff --git a/setup.py b/setup.py
index 2d09e9d..b5eeaa8 100755
--- a/setup.py
+++ b/setup.py
@@ -36,7 +36,7 @@ setup(
         'future>=0.14.0',
         'futures; python_version<"3.2"',
         'jedi>=0.12',
-        'python-jsonrpc-server',
+        'python-jsonrpc-server>=0.1.0',
         'pluggy'
     ],
 


Test patch:
diff --git a/test/test_language_server.py b/test/test_language_server.py
index 41b7615..3a4b18d 100644
--- a/test/test_language_server.py
+++ b/test/test_language_server.py
@@ -2,7 +2,7 @@
 import os
 from threading import Thread
 
-from jsonrpc.exceptions import JsonRpcMethodNotFound
+from pyls_jsonrpc.exceptions import JsonRpcMethodNotFound
 import pytest
 
 from pyls.python_ls import start_io_lang_server, PythonLanguageServer
Repository: palantir/python-language-server
Pull request #473 (2019-01-19 05:18:26)

yapf_format: fix config lookup

palantirtech: Thanks for your interest in [palantir/python-language-server](https://github.com/palantir/python-language-server), @perrinjerome! Before we can accept your pull request, you need to sign our contributor license agreement - just visit https://cla.palantir.com/ and follow the instructions. Once you sign, I'll automatically update this pull request.

Patch:
diff --git a/pyls/plugins/yapf_format.py b/pyls/plugins/yapf_format.py
index 4d770a4..16afe97 100644
--- a/pyls/plugins/yapf_format.py
+++ b/pyls/plugins/yapf_format.py
@@ -37,7 +37,7 @@ def _format(document, lines=None):
         lines=lines,
         filename=document.filename,
         style_config=file_resources.GetDefaultStyleForDir(
-            os.path.dirname(document.filename)
+            os.path.dirname(document.path)
         )
     )
 


Test patch:
diff --git a/test/plugins/test_yapf_format.py b/test/plugins/test_yapf_format.py
index 98f37b6..4bf6be3 100644
--- a/test/plugins/test_yapf_format.py
+++ b/test/plugins/test_yapf_format.py
@@ -45,3 +45,14 @@ def test_range_format():
 def test_no_change():
     doc = Document(DOC_URI, GOOD_DOC)
     assert not pyls_format_document(doc)
+
+
+def test_config_file(tmpdir):
+    # a config file in the same directory as the source file will be used
+    conf = tmpdir.join('.style.yapf')
+    conf.write('[style]\ncolumn_limit = 14')
+    src = tmpdir.join('test.py')
+    doc = Document(uris.from_fs_path(src.strpath), DOC)
+
+    # A was split on multiple lines because of column_limit from config file
+    assert pyls_format_document(doc)[0]['newText'] == "A = [\n    'h', 'w',\n    'a'\n]\n\nB = ['h', 'w']\n"
Repository: palantir/python-language-server
Pull request #868 (2020-09-29 17:39:42)

Pass server settings to new workspaces
This prevents workspaces created after the initial `didChangeConfiguration` request and before any other requests of that kind to have empty settings.


Patch:
diff --git a/pyls/python_ls.py b/pyls/python_ls.py
index adae39d..3717756 100644
--- a/pyls/python_ls.py
+++ b/pyls/python_ls.py
@@ -379,6 +379,7 @@ class PythonLanguageServer(MethodDispatcher):
                 workspace_config = config.Config(
                     added_uri, self.config._init_opts,
                     self.config._process_id, self.config._capabilities)
+                workspace_config.update(self.config._settings)
                 self.workspaces[added_uri] = Workspace(
                     added_uri, self._endpoint, workspace_config)
 


Test patch:
diff --git a/test/test_workspace.py b/test/test_workspace.py
index 5871300..45549b0 100644
--- a/test/test_workspace.py
+++ b/test/test_workspace.py
@@ -242,3 +242,23 @@ def test_workspace_loads_pycodestyle_config(pyls, tmpdir):
 
     seetings = pyls.workspaces[str(workspace3_dir)]._config.settings()
     assert seetings['plugins']['pycodestyle']['maxLineLength'] == 20
+
+
+def test_settings_of_added_workspace(pyls, tmpdir):
+    test_uri = str(tmpdir.mkdir('Test123'))
+    pyls.root_uri = test_uri
+    pyls.workspace._root_uri = test_uri
+
+    # Set some settings for the server.
+    server_settings = {'pyls': {'plugins': {'jedi': {'environment': '/usr/bin/python3'}}}}
+    pyls.m_workspace__did_change_configuration(server_settings)
+
+    # Create a new workspace.
+    workspace1 = {'uri': str(tmpdir.mkdir('NewTest456'))}
+    event = {'added': [workspace1]}
+    pyls.m_workspace__did_change_workspace_folders(event)
+
+    # Assert settings are inherited from the server config.
+    workspace1_object = pyls.workspaces[workspace1['uri']]
+    workspace1_jedi_settings = workspace1_object._config.plugin_settings('jedi')
+    assert workspace1_jedi_settings == server_settings['pyls']['plugins']['jedi']
Repository: palantir/python-language-server
Pull request #879 (2020-10-24 22:58:50)

Add document path to Jedi's sys_path 
This allows to have completions for files out of the workspace's root_path.

This addresses spyder-ide/spyder#11118


Patch:
diff --git a/pyls/workspace.py b/pyls/workspace.py
index 90ebbd3..a47bb59 100644
--- a/pyls/workspace.py
+++ b/pyls/workspace.py
@@ -258,7 +258,7 @@ class Document(object):
         env_vars.pop('PYTHONPATH', None)
 
         environment = self.get_enviroment(environment_path, env_vars=env_vars) if environment_path else None
-        sys_path = self.sys_path(environment_path, env_vars=env_vars) + extra_paths
+        sys_path = self.sys_path(environment_path, env_vars=env_vars) + extra_paths + [os.path.dirname(self.path)]
         project_path = self._workspace.root_path
 
         kwargs = {


Test patch:
diff --git a/test/fixtures.py b/test/fixtures.py
index 654cbf1..a21396a 100644
--- a/test/fixtures.py
+++ b/test/fixtures.py
@@ -44,6 +44,15 @@ def workspace(tmpdir):
     return ws
 
 
+@pytest.fixture
+def workspace_other_root_path(tmpdir):
+    """Return a workspace with a root_path other than tmpdir."""
+    ws_path = str(tmpdir.mkdir('test123').mkdir('test456'))
+    ws = Workspace(uris.from_fs_path(ws_path), Mock())
+    ws._config = Config(ws.root_uri, {}, 0, {})
+    return ws
+
+
 @pytest.fixture
 def config(workspace):  # pylint: disable=redefined-outer-name
     """Return a config object."""
diff --git a/test/plugins/test_completion.py b/test/plugins/test_completion.py
index 370ad61..247c2c2 100644
--- a/test/plugins/test_completion.py
+++ b/test/plugins/test_completion.py
@@ -334,3 +334,26 @@ def test_jedi_completion_environment(workspace):
     completions = pyls_jedi_completions(doc._config, doc, com_position)
     assert completions[0]['label'] == 'loghub'
     assert 'changelog generator' in completions[0]['documentation'].lower()
+
+
+def test_document_path_completions(tmpdir, workspace_other_root_path):
+    # Create a dummy module out of the workspace's root_path and try to get
+    # completions for it in another file placed next to it.
+    module_content = '''
+def foo():
+    pass
+'''
+
+    p = tmpdir.join("mymodule.py")
+    p.write(module_content)
+
+    # Content of doc to test completion
+    doc_content = """import mymodule
+mymodule.f"""
+    doc_path = str(tmpdir) + os.path.sep + 'myfile.py'
+    doc_uri = uris.from_fs_path(doc_path)
+    doc = Document(doc_uri, workspace_other_root_path, doc_content)
+
+    com_position = {'line': 1, 'character': 10}
+    completions = pyls_jedi_completions(doc._config, doc, com_position)
+    assert completions[0]['label'] == 'foo()'