deepnote-python-lsp-server 0.0.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. deepnote_python_lsp_server-0.0.0.dist-info/METADATA +263 -0
  2. deepnote_python_lsp_server-0.0.0.dist-info/RECORD +43 -0
  3. deepnote_python_lsp_server-0.0.0.dist-info/WHEEL +6 -0
  4. deepnote_python_lsp_server-0.0.0.dist-info/entry_points.txt +24 -0
  5. deepnote_python_lsp_server-0.0.0.dist-info/licenses/LICENSE +22 -0
  6. deepnote_python_lsp_server-0.0.0.dist-info/top_level.txt +1 -0
  7. pylsp/__init__.py +33 -0
  8. pylsp/__main__.py +132 -0
  9. pylsp/_utils.py +325 -0
  10. pylsp/config/__init__.py +2 -0
  11. pylsp/config/config.py +202 -0
  12. pylsp/config/flake8_conf.py +64 -0
  13. pylsp/config/pycodestyle_conf.py +34 -0
  14. pylsp/config/source.py +88 -0
  15. pylsp/hookspecs.py +140 -0
  16. pylsp/lsp.py +115 -0
  17. pylsp/plugins/__init__.py +2 -0
  18. pylsp/plugins/_resolvers.py +132 -0
  19. pylsp/plugins/_rope_task_handle.py +106 -0
  20. pylsp/plugins/autopep8_format.py +95 -0
  21. pylsp/plugins/definition.py +84 -0
  22. pylsp/plugins/flake8_lint.py +243 -0
  23. pylsp/plugins/folding.py +210 -0
  24. pylsp/plugins/highlight.py +36 -0
  25. pylsp/plugins/hover.py +57 -0
  26. pylsp/plugins/jedi_completion.py +318 -0
  27. pylsp/plugins/jedi_rename.py +56 -0
  28. pylsp/plugins/mccabe_lint.py +56 -0
  29. pylsp/plugins/preload_imports.py +38 -0
  30. pylsp/plugins/pycodestyle_lint.py +113 -0
  31. pylsp/plugins/pydocstyle_lint.py +127 -0
  32. pylsp/plugins/pyflakes_lint.py +97 -0
  33. pylsp/plugins/pylint_lint.py +367 -0
  34. pylsp/plugins/references.py +33 -0
  35. pylsp/plugins/rope_autoimport.py +407 -0
  36. pylsp/plugins/rope_completion.py +161 -0
  37. pylsp/plugins/signature.py +81 -0
  38. pylsp/plugins/symbols.py +214 -0
  39. pylsp/plugins/yapf_format.py +199 -0
  40. pylsp/python_lsp.py +921 -0
  41. pylsp/text_edit.py +97 -0
  42. pylsp/uris.py +139 -0
  43. pylsp/workspace.py +730 -0
pylsp/_utils.py ADDED
@@ -0,0 +1,325 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import functools
5
+ import inspect
6
+ import logging
7
+ import os
8
+ import pathlib
9
+ import re
10
+ import threading
11
+ import time
12
+ from typing import List, Optional
13
+
14
+ import docstring_to_markdown
15
+ import jedi
16
+
17
+ JEDI_VERSION = jedi.__version__
18
+
19
+ # Eol chars accepted by the LSP protocol
20
+ # the ordering affects performance
21
+ EOL_CHARS = ["\r\n", "\r", "\n"]
22
+ EOL_REGEX = re.compile(f"({'|'.join(EOL_CHARS)})")
23
+
24
+ log = logging.getLogger(__name__)
25
+
26
+
27
+ def debounce(interval_s, keyed_by=None):
28
+ """Debounce calls to this function until interval_s seconds have passed."""
29
+
30
+ def wrapper(func):
31
+ timers = {}
32
+ lock = threading.Lock()
33
+
34
+ @functools.wraps(func)
35
+ def debounced(*args, **kwargs):
36
+ sig = inspect.signature(func)
37
+ call_args = sig.bind(*args, **kwargs)
38
+ key = call_args.arguments[keyed_by] if keyed_by else None
39
+
40
+ def run():
41
+ with lock:
42
+ del timers[key]
43
+ return func(*args, **kwargs)
44
+
45
+ with lock:
46
+ old_timer = timers.get(key)
47
+ if old_timer:
48
+ old_timer.cancel()
49
+
50
+ timer = threading.Timer(interval_s, run)
51
+ timers[key] = timer
52
+ timer.start()
53
+
54
+ return debounced
55
+
56
+ return wrapper
57
+
58
+
59
+ def throttle(seconds=1):
60
+ """Throttles calls to a function evey `seconds` seconds."""
61
+
62
+ def decorator(func):
63
+ @functools.wraps(func)
64
+ def wrapper(*args, **kwargs):
65
+ if not hasattr(wrapper, "last_call"):
66
+ wrapper.last_call = 0
67
+ if time.time() - wrapper.last_call >= seconds:
68
+ wrapper.last_call = time.time()
69
+ return func(*args, **kwargs)
70
+
71
+ return wrapper
72
+
73
+ return decorator
74
+
75
+
76
+ def find_parents(root, path, names):
77
+ """Find files matching the given names relative to the given path.
78
+
79
+ Args:
80
+ path (str): The file path to start searching up from.
81
+ names (List[str]): The file/directory names to look for.
82
+ root (str): The directory at which to stop recursing upwards.
83
+
84
+ Note:
85
+ The path MUST be within the root.
86
+ """
87
+ if not root:
88
+ return []
89
+
90
+ if not os.path.commonprefix((root, path)):
91
+ log.warning("Path %r not in %r", path, root)
92
+ return []
93
+
94
+ # Split the relative by directory, generate all the parent directories, then check each of them.
95
+ # This avoids running a loop that has different base-cases for unix/windows
96
+ # e.g. /a/b and /a/b/c/d/e.py -> ['/a/b', 'c', 'd']
97
+ dirs = [root] + os.path.relpath(os.path.dirname(path), root).split(os.path.sep)
98
+
99
+ # Search each of /a/b/c, /a/b, /a
100
+ while dirs:
101
+ search_dir = os.path.join(*dirs)
102
+ existing = list(
103
+ filter(os.path.exists, [os.path.join(search_dir, n) for n in names])
104
+ )
105
+ if existing:
106
+ return existing
107
+ dirs.pop()
108
+
109
+ # Otherwise nothing
110
+ return []
111
+
112
+
113
+ def path_to_dot_name(path):
114
+ """Given a path to a module, derive its dot-separated full name."""
115
+ directory = os.path.dirname(path)
116
+ module_name, _ = os.path.splitext(os.path.basename(path))
117
+ full_name = [module_name]
118
+ while os.path.exists(os.path.join(directory, "__init__.py")):
119
+ this_directory = os.path.basename(directory)
120
+ directory = os.path.dirname(directory)
121
+ full_name = [this_directory] + full_name
122
+ return ".".join(full_name)
123
+
124
+
125
+ def match_uri_to_workspace(uri, workspaces):
126
+ if uri is None:
127
+ return None
128
+ max_len, chosen_workspace = -1, None
129
+ path = pathlib.Path(uri).parts
130
+ for workspace in workspaces:
131
+ workspace_parts = pathlib.Path(workspace).parts
132
+ if len(workspace_parts) > len(path):
133
+ continue
134
+ match_len = 0
135
+ for workspace_part, path_part in zip(workspace_parts, path):
136
+ if workspace_part == path_part:
137
+ match_len += 1
138
+ if match_len > 0:
139
+ if match_len > max_len:
140
+ max_len = match_len
141
+ chosen_workspace = workspace
142
+ return chosen_workspace
143
+
144
+
145
+ def list_to_string(value):
146
+ return ",".join(value) if isinstance(value, list) else value
147
+
148
+
149
+ def merge_dicts(dict_a, dict_b):
150
+ """Recursively merge dictionary b into dictionary a.
151
+
152
+ If override_nones is True, then
153
+ """
154
+
155
+ def _merge_dicts_(a, b):
156
+ for key in set(a.keys()).union(b.keys()):
157
+ if key in a and key in b:
158
+ if isinstance(a[key], dict) and isinstance(b[key], dict):
159
+ yield (key, dict(_merge_dicts_(a[key], b[key])))
160
+ elif isinstance(a[key], list) and isinstance(b[key], list):
161
+ yield (key, list(set(a[key] + b[key])))
162
+ elif b[key] is not None:
163
+ yield (key, b[key])
164
+ else:
165
+ yield (key, a[key])
166
+ elif key in a:
167
+ yield (key, a[key])
168
+ elif b[key] is not None:
169
+ yield (key, b[key])
170
+
171
+ return dict(_merge_dicts_(dict_a, dict_b))
172
+
173
+
174
+ def escape_plain_text(contents: str) -> str:
175
+ """
176
+ Format plain text to display nicely in environments which do not respect whitespaces.
177
+ """
178
+ contents = contents.replace("\t", "\u00a0" * 4)
179
+ contents = contents.replace(" ", "\u00a0" * 2)
180
+ return contents
181
+
182
+
183
+ def escape_markdown(contents: str) -> str:
184
+ """
185
+ Format plain text to display nicely in Markdown environment.
186
+ """
187
+ # escape markdown syntax
188
+ contents = re.sub(r"([\\*_#[\]])", r"\\\1", contents)
189
+ # preserve white space characters
190
+ contents = escape_plain_text(contents)
191
+ return contents
192
+
193
+
194
+ def wrap_signature(signature):
195
+ return "```python\n" + signature + "\n```\n"
196
+
197
+
198
+ SERVER_SUPPORTED_MARKUP_KINDS = {"markdown", "plaintext"}
199
+
200
+
201
+ def choose_markup_kind(client_supported_markup_kinds: List[str]):
202
+ """Choose a markup kind supported by both client and the server.
203
+
204
+ This gives priority to the markup kinds provided earlier on the client preference list.
205
+ """
206
+ for kind in client_supported_markup_kinds:
207
+ if kind in SERVER_SUPPORTED_MARKUP_KINDS:
208
+ return kind
209
+ return "markdown"
210
+
211
+
212
+ def format_docstring(
213
+ contents: str, markup_kind: str, signatures: Optional[List[str]] = None
214
+ ):
215
+ """Transform the provided docstring into a MarkupContent object.
216
+
217
+ If `markup_kind` is 'markdown' the docstring will get converted to
218
+ markdown representation using `docstring-to-markdown`; if it is
219
+ `plaintext`, it will be returned as plain text.
220
+ Call signatures of functions (or equivalent code summaries)
221
+ provided in optional `signatures` argument will be prepended
222
+ to the provided contents of the docstring if given.
223
+ """
224
+ if not isinstance(contents, str):
225
+ contents = ""
226
+
227
+ if markup_kind == "markdown":
228
+ try:
229
+ value = docstring_to_markdown.convert(contents)
230
+ except docstring_to_markdown.UnknownFormatError:
231
+ # try to escape the Markdown syntax instead:
232
+ value = escape_markdown(contents)
233
+
234
+ if signatures:
235
+ value = wrap_signature("\n".join(signatures)) + "\n\n" + value
236
+
237
+ return {"kind": "markdown", "value": value}
238
+ value = contents
239
+ if signatures:
240
+ value = "\n".join(signatures) + "\n\n" + value
241
+ return {"kind": "plaintext", "value": escape_plain_text(value)}
242
+
243
+
244
+ def clip_column(column, lines, line_number):
245
+ """
246
+ Normalise the position as per the LSP that accepts character positions > line length
247
+
248
+ https://microsoft.github.io/language-server-protocol/specification#position
249
+ """
250
+ max_column = (
251
+ len(lines[line_number].rstrip("\r\n")) if len(lines) > line_number else 0
252
+ )
253
+ return min(column, max_column)
254
+
255
+
256
+ def position_to_jedi_linecolumn(document, position):
257
+ """
258
+ Convert the LSP format 'line', 'character' to Jedi's 'line', 'column'
259
+
260
+ https://microsoft.github.io/language-server-protocol/specification#position
261
+ """
262
+ code_position = {}
263
+ if position:
264
+ code_position = {
265
+ "line": position["line"] + 1,
266
+ "column": clip_column(
267
+ position["character"], document.lines, position["line"]
268
+ ),
269
+ }
270
+ return code_position
271
+
272
+
273
+ if os.name == "nt":
274
+ import ctypes
275
+
276
+ kernel32 = ctypes.windll.kernel32
277
+ PROCESS_QUERY_INFROMATION = 0x1000
278
+
279
+ def is_process_alive(pid):
280
+ """Check whether the process with the given pid is still alive.
281
+
282
+ Running `os.kill()` on Windows always exits the process, so it can't be used to check for an alive process.
283
+ see: https://docs.python.org/3/library/os.html?highlight=os%20kill#os.kill
284
+
285
+ Hence ctypes is used to check for the process directly via windows API avoiding any other 3rd-party dependency.
286
+
287
+ Args:
288
+ pid (int): process ID
289
+
290
+ Returns:
291
+ bool: False if the process is not alive or don't have permission to check, True otherwise.
292
+ """
293
+ process = kernel32.OpenProcess(PROCESS_QUERY_INFROMATION, 0, pid)
294
+ if process != 0:
295
+ kernel32.CloseHandle(process)
296
+ return True
297
+ return False
298
+
299
+ else:
300
+ import errno
301
+
302
+ def is_process_alive(pid):
303
+ """Check whether the process with the given pid is still alive.
304
+
305
+ Args:
306
+ pid (int): process ID
307
+
308
+ Returns:
309
+ bool: False if the process is not alive or don't have permission to check, True otherwise.
310
+ """
311
+ if pid < 0:
312
+ return False
313
+ try:
314
+ os.kill(pid, 0)
315
+ except OSError as e:
316
+ return e.errno == errno.EPERM
317
+ return True
318
+
319
+
320
+ def get_eol_chars(text):
321
+ """Get EOL chars used in text."""
322
+ match = EOL_REGEX.search(text)
323
+ if match:
324
+ return match.group(0)
325
+ return None
@@ -0,0 +1,2 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
pylsp/config/config.py ADDED
@@ -0,0 +1,202 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import logging
5
+ import sys
6
+ from functools import lru_cache
7
+ from typing import List, Mapping, Sequence, Union
8
+
9
+ import pluggy
10
+ from pluggy._hooks import HookImpl
11
+
12
+ from pylsp import PYLSP, _utils, hookspecs, uris
13
+
14
+ # See compatibility note on `group` keyword:
15
+ # https://docs.python.org/3/library/importlib.metadata.html#entry-points
16
+ if sys.version_info < (3, 10): # pragma: no cover
17
+ from importlib_metadata import entry_points
18
+ else: # pragma: no cover
19
+ from importlib.metadata import entry_points
20
+
21
+
22
+ log = logging.getLogger(__name__)
23
+
24
+ # Sources of config, first source overrides next source
25
+ DEFAULT_CONFIG_SOURCES = ["pycodestyle"]
26
+
27
+
28
+ class PluginManager(pluggy.PluginManager):
29
+ def _hookexec(
30
+ self,
31
+ hook_name: str,
32
+ methods: Sequence[HookImpl],
33
+ kwargs: Mapping[str, object],
34
+ firstresult: bool,
35
+ ) -> Union[object, List[object]]:
36
+ # called from all hookcaller instances.
37
+ # enable_tracing will set its own wrapping function at self._inner_hookexec
38
+ try:
39
+ return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
40
+ except Exception as e:
41
+ log.warning(f"Failed to load hook {hook_name}: {e}", exc_info=True)
42
+ return []
43
+
44
+
45
+ class Config:
46
+ def __init__(self, root_uri, init_opts, process_id, capabilities) -> None:
47
+ self._root_path = uris.to_fs_path(root_uri)
48
+ self._root_uri = root_uri
49
+ self._init_opts = init_opts
50
+ self._process_id = process_id
51
+ self._capabilities = capabilities
52
+
53
+ self._settings = {}
54
+ self._plugin_settings = {}
55
+
56
+ self._config_sources = {}
57
+ try:
58
+ from .flake8_conf import Flake8Config
59
+
60
+ self._config_sources["flake8"] = Flake8Config(self._root_path)
61
+ except ImportError:
62
+ pass
63
+ try:
64
+ from .pycodestyle_conf import PyCodeStyleConfig
65
+
66
+ self._config_sources["pycodestyle"] = PyCodeStyleConfig(self._root_path)
67
+ except ImportError:
68
+ pass
69
+
70
+ self._pm = PluginManager(PYLSP)
71
+ self._pm.trace.root.setwriter(log.debug)
72
+ self._pm.enable_tracing()
73
+ self._pm.add_hookspecs(hookspecs)
74
+
75
+ # Pluggy will skip loading a plugin if it throws a DistributionNotFound exception.
76
+ # However I don't want all plugins to have to catch ImportError and re-throw. So here we'll filter
77
+ # out any entry points that throw ImportError assuming one or more of their dependencies isn't present.
78
+ for entry_point in entry_points(group=PYLSP):
79
+ try:
80
+ entry_point.load()
81
+ except Exception as e:
82
+ log.info(
83
+ "Failed to load %s entry point '%s': %s", PYLSP, entry_point.name, e
84
+ )
85
+ self._pm.set_blocked(entry_point.name)
86
+
87
+ # Load the entry points into pluggy, having blocked any failing ones.
88
+ # Despite the API name, recent Pluggy versions will use ``importlib_metadata``.
89
+ self._pm.load_setuptools_entrypoints(PYLSP)
90
+
91
+ for name, plugin in self._pm.list_name_plugin():
92
+ if plugin is not None:
93
+ log.info("Loaded pylsp plugin %s from %s", name, plugin)
94
+
95
+ for plugin_conf in self._pm.hook.pylsp_settings(config=self):
96
+ self._plugin_settings = _utils.merge_dicts(
97
+ self._plugin_settings, plugin_conf
98
+ )
99
+
100
+ self._plugin_settings = _utils.merge_dicts(
101
+ self._plugin_settings, self._init_opts.get("pylsp", {})
102
+ )
103
+
104
+ self._update_disabled_plugins()
105
+
106
+ @property
107
+ def disabled_plugins(self):
108
+ return self._disabled_plugins
109
+
110
+ @property
111
+ def plugin_manager(self):
112
+ return self._pm
113
+
114
+ @property
115
+ def init_opts(self):
116
+ return self._init_opts
117
+
118
+ @property
119
+ def root_uri(self):
120
+ return self._root_uri
121
+
122
+ @property
123
+ def process_id(self):
124
+ return self._process_id
125
+
126
+ @property
127
+ def capabilities(self):
128
+ return self._capabilities
129
+
130
+ @lru_cache(maxsize=32)
131
+ def settings(self, document_path=None):
132
+ """Settings are constructed from a few sources:
133
+
134
+ 1. User settings, found in user's home directory
135
+ 2. Plugin settings, reported by PyLS plugins
136
+ 3. LSP settings, given to us from didChangeConfiguration
137
+ 4. Project settings, found in config files in the current project.
138
+
139
+ Since this function is nondeterministic, it is important to call
140
+ settings.cache_clear() when the config is updated
141
+ """
142
+ settings = {}
143
+ sources = self._settings.get("configurationSources", DEFAULT_CONFIG_SOURCES)
144
+
145
+ # Plugin configuration
146
+ settings = _utils.merge_dicts(settings, self._plugin_settings)
147
+
148
+ # LSP configuration
149
+ settings = _utils.merge_dicts(settings, self._settings)
150
+
151
+ # User configuration
152
+ for source_name in reversed(sources):
153
+ source = self._config_sources.get(source_name)
154
+ if not source:
155
+ continue
156
+ source_conf = source.user_config()
157
+ log.debug(
158
+ "Got user config from %s: %s", source.__class__.__name__, source_conf
159
+ )
160
+ settings = _utils.merge_dicts(settings, source_conf)
161
+
162
+ # Project configuration
163
+ for source_name in reversed(sources):
164
+ source = self._config_sources.get(source_name)
165
+ if not source:
166
+ continue
167
+ source_conf = source.project_config(document_path or self._root_path)
168
+ log.debug(
169
+ "Got project config from %s: %s", source.__class__.__name__, source_conf
170
+ )
171
+ settings = _utils.merge_dicts(settings, source_conf)
172
+
173
+ log.debug("With configuration: %s", settings)
174
+
175
+ return settings
176
+
177
+ def find_parents(self, path, names):
178
+ root_path = uris.to_fs_path(self._root_uri)
179
+ return _utils.find_parents(root_path, path, names)
180
+
181
+ def plugin_settings(self, plugin, document_path=None):
182
+ return (
183
+ self.settings(document_path=document_path)
184
+ .get("plugins", {})
185
+ .get(plugin, {})
186
+ )
187
+
188
+ def update(self, settings) -> None:
189
+ """Recursively merge the given settings into the current settings."""
190
+ self.settings.cache_clear()
191
+ self._settings = settings
192
+ log.info("Updated settings to %s", self._settings)
193
+ self._update_disabled_plugins()
194
+
195
+ def _update_disabled_plugins(self) -> None:
196
+ # All plugins default to enabled
197
+ self._disabled_plugins = [
198
+ plugin
199
+ for name, plugin in self.plugin_manager.list_name_plugin()
200
+ if not self.settings().get("plugins", {}).get(name, {}).get("enabled", True)
201
+ ]
202
+ log.info("Disabled plugins: %s", self._disabled_plugins)
@@ -0,0 +1,64 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import logging
5
+ import os
6
+
7
+ from pylsp._utils import find_parents
8
+
9
+ from .source import ConfigSource
10
+
11
+ log = logging.getLogger(__name__)
12
+
13
+ CONFIG_KEY = "flake8"
14
+ PROJECT_CONFIGS = [".flake8", "setup.cfg", "tox.ini"]
15
+
16
+ OPTIONS = [
17
+ # mccabe
18
+ ("max-complexity", "plugins.mccabe.threshold", int),
19
+ # pycodestyle
20
+ ("exclude", "plugins.pycodestyle.exclude", list),
21
+ ("filename", "plugins.pycodestyle.filename", list),
22
+ ("hang-closing", "plugins.pycodestyle.hangClosing", bool),
23
+ ("ignore", "plugins.pycodestyle.ignore", list),
24
+ ("max-line-length", "plugins.pycodestyle.maxLineLength", int),
25
+ ("indent-size", "plugins.pycodestyle.indentSize", int),
26
+ ("select", "plugins.pycodestyle.select", list),
27
+ # flake8
28
+ ("exclude", "plugins.flake8.exclude", list),
29
+ ("extend-ignore", "plugins.flake8.extendIgnore", list),
30
+ ("extend-select", "plugins.flake8.extendSelect", list),
31
+ ("filename", "plugins.flake8.filename", list),
32
+ ("hang-closing", "plugins.flake8.hangClosing", bool),
33
+ ("ignore", "plugins.flake8.ignore", list),
34
+ ("max-complexity", "plugins.flake8.maxComplexity", int),
35
+ ("max-line-length", "plugins.flake8.maxLineLength", int),
36
+ ("indent-size", "plugins.flake8.indentSize", int),
37
+ ("select", "plugins.flake8.select", list),
38
+ ("per-file-ignores", "plugins.flake8.perFileIgnores", list),
39
+ ]
40
+
41
+
42
+ class Flake8Config(ConfigSource):
43
+ """Parse flake8 configurations."""
44
+
45
+ def user_config(self):
46
+ config_file = self._user_config_file()
47
+ config = self.read_config_from_files([config_file])
48
+ return self.parse_config(config, CONFIG_KEY, OPTIONS)
49
+
50
+ def _user_config_file(self):
51
+ if self.is_windows:
52
+ return os.path.expanduser("~\\.flake8")
53
+ return os.path.join(self.xdg_home, "flake8")
54
+
55
+ def project_config(self, document_path):
56
+ files = find_parents(self.root_path, document_path, PROJECT_CONFIGS)
57
+ config = self.read_config_from_files(files)
58
+ return self.parse_config(config, CONFIG_KEY, OPTIONS)
59
+
60
+ @classmethod
61
+ def _parse_list_opt(cls, string):
62
+ if string.startswith("\n"):
63
+ return [s.strip().rstrip(",") for s in string.split("\n") if s.strip()]
64
+ return [s.strip() for s in string.split(",") if s.strip()]
@@ -0,0 +1,34 @@
1
+ # Copyright 2017-2020 Palantir Technologies, Inc.
2
+ # Copyright 2021- Python Language Server Contributors.
3
+
4
+ import pycodestyle
5
+
6
+ from pylsp._utils import find_parents
7
+
8
+ from .source import ConfigSource
9
+
10
+ CONFIG_KEY = "pycodestyle"
11
+ USER_CONFIGS = [pycodestyle.USER_CONFIG] if pycodestyle.USER_CONFIG else []
12
+ PROJECT_CONFIGS = ["pycodestyle.cfg", "setup.cfg", "tox.ini"]
13
+
14
+ OPTIONS = [
15
+ ("exclude", "plugins.pycodestyle.exclude", list),
16
+ ("filename", "plugins.pycodestyle.filename", list),
17
+ ("hang-closing", "plugins.pycodestyle.hangClosing", bool),
18
+ ("ignore", "plugins.pycodestyle.ignore", list),
19
+ ("max-line-length", "plugins.pycodestyle.maxLineLength", int),
20
+ ("indent-size", "plugins.pycodestyle.indentSize", int),
21
+ ("select", "plugins.pycodestyle.select", list),
22
+ ("aggressive", "plugins.pycodestyle.aggressive", int),
23
+ ]
24
+
25
+
26
+ class PyCodeStyleConfig(ConfigSource):
27
+ def user_config(self):
28
+ config = self.read_config_from_files(USER_CONFIGS)
29
+ return self.parse_config(config, CONFIG_KEY, OPTIONS)
30
+
31
+ def project_config(self, document_path):
32
+ files = find_parents(self.root_path, document_path, PROJECT_CONFIGS)
33
+ config = self.read_config_from_files(files)
34
+ return self.parse_config(config, CONFIG_KEY, OPTIONS)