execsql2 2.26.0__py3-none-any.whl → 2.27.0__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.
- execsql/cli/commands/__init__.py +1 -0
- execsql/cli/commands/listing.py +32 -5
- execsql/cli/commands/lsp.py +40 -0
- execsql/cli/dispatch.py +1 -1
- execsql/cli/help.py +44 -96
- execsql/cli/lint.py +13 -0
- execsql/lsp/__init__.py +6 -0
- execsql/lsp/code_actions.py +217 -0
- execsql/lsp/completion.py +203 -0
- execsql/lsp/diagnostics.py +66 -0
- execsql/lsp/document.py +206 -0
- execsql/lsp/folding.py +59 -0
- execsql/lsp/formatting.py +37 -0
- execsql/lsp/hover.py +119 -0
- execsql/lsp/navigation.py +169 -0
- execsql/lsp/rename.py +91 -0
- execsql/lsp/server.py +358 -0
- execsql/metacommands/dispatch.py +7 -0
- execsql/metacommands/reference.py +1404 -0
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/METADATA +32 -18
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/RECORD +39 -26
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/README.md +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.26.0.data → execsql2-2.27.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/WHEEL +0 -0
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/entry_points.txt +0 -0
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.26.0.dist-info → execsql2-2.27.0.dist-info}/licenses/NOTICE +0 -0
execsql/lsp/server.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"""The pygls server behind ``execsql lsp``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from lsprotocol import types
|
|
13
|
+
from pygls.lsp.server import LanguageServer
|
|
14
|
+
from pygls.uris import to_fs_path
|
|
15
|
+
|
|
16
|
+
from execsql import __version__
|
|
17
|
+
from execsql.lsp.code_actions import code_actions
|
|
18
|
+
from execsql.lsp.completion import completions
|
|
19
|
+
from execsql.lsp.diagnostics import diagnostics, too_deep_diagnostic
|
|
20
|
+
from execsql.lsp.document import ScriptIndex, index_script, read_included, same_file_key
|
|
21
|
+
from execsql.lsp.folding import folding_ranges
|
|
22
|
+
from execsql.lsp.formatting import format_document
|
|
23
|
+
from execsql.lsp.hover import hover
|
|
24
|
+
from execsql.lsp.rename import RenameError, prepare_rename, rename
|
|
25
|
+
from execsql.lsp.navigation import definition, document_links, document_symbols, references
|
|
26
|
+
|
|
27
|
+
__all__ = ["ExecsqlLanguageServer", "create_server"]
|
|
28
|
+
|
|
29
|
+
# Pause after the last keystroke before linting, so a burst of typing lints once.
|
|
30
|
+
LINT_DELAY_SECONDS = 0.2
|
|
31
|
+
|
|
32
|
+
# Files whose change can alter an open script's findings or index: the config
|
|
33
|
+
# file (its [lint] and [format] sections) and any script it may INCLUDE.
|
|
34
|
+
WATCHED_FILES = ("**/execsql.conf", "**/*.sql")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _DropLateCancels(logging.Filter):
|
|
38
|
+
"""Drops pygls's warning about a cancel for a request already answered.
|
|
39
|
+
|
|
40
|
+
Editors cancel requests they no longer need (a hover after the cursor
|
|
41
|
+
moves); the handlers answer in milliseconds, so the cancel usually
|
|
42
|
+
arrives late. That is normal, but the warning reaches the editor's
|
|
43
|
+
output as an error.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
def filter(self, record: logging.LogRecord) -> bool:
|
|
47
|
+
return not record.getMessage().startswith("Cancel notification for unknown message id")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Settings:
|
|
52
|
+
"""What the configuration sets for one workspace folder: lint rule selection and format options."""
|
|
53
|
+
|
|
54
|
+
select: tuple[str, ...] = ()
|
|
55
|
+
ignore: tuple[str, ...] = ()
|
|
56
|
+
format_options: dict[str, Any] = field(default_factory=dict) # format_document keywords, from [format]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ExecsqlLanguageServer(LanguageServer):
|
|
60
|
+
"""Holds what the handlers share: each workspace folder's settings and pending lint tasks."""
|
|
61
|
+
|
|
62
|
+
def __init__(self) -> None:
|
|
63
|
+
super().__init__("execsql", __version__)
|
|
64
|
+
self.settings: dict[str, Settings] = {} # by same_file_key of each workspace folder
|
|
65
|
+
self.fallback = Settings() # for a file in no workspace folder
|
|
66
|
+
self.pending: dict[str, asyncio.Task[Any]] = {}
|
|
67
|
+
|
|
68
|
+
def load_settings(self) -> None:
|
|
69
|
+
"""Read ``[lint]`` and ``[format]`` for each workspace folder, as ``execsql lint`` / ``execsql format`` run there would.
|
|
70
|
+
|
|
71
|
+
execsql reads config files from the working directory, so each
|
|
72
|
+
folder's settings are read from inside it. The server itself works
|
|
73
|
+
from the workspace root: a file in no folder gets the settings read
|
|
74
|
+
there, and an unsaved script's relative ``INCLUDE`` resolves there.
|
|
75
|
+
"""
|
|
76
|
+
root = self.workspace.root_path
|
|
77
|
+
if root and os.path.isdir(root):
|
|
78
|
+
os.chdir(root)
|
|
79
|
+
self.fallback = self._read_settings(None)
|
|
80
|
+
self.settings = {}
|
|
81
|
+
for folder in self.workspace.folders.values():
|
|
82
|
+
path = to_fs_path(folder.uri)
|
|
83
|
+
if path and os.path.isdir(path):
|
|
84
|
+
self.settings[same_file_key(path)] = self._read_settings(path)
|
|
85
|
+
|
|
86
|
+
def _read_settings(self, folder: str | None) -> Settings:
|
|
87
|
+
"""The settings ``execsql lint`` run from *folder* (the working directory if ``None``) reads.
|
|
88
|
+
|
|
89
|
+
A config error is shown to the user and leaves every rule on.
|
|
90
|
+
"""
|
|
91
|
+
from execsql.cli.lint import resolve_selectors
|
|
92
|
+
from execsql.cli.run import _tool_config
|
|
93
|
+
|
|
94
|
+
here = os.getcwd()
|
|
95
|
+
try:
|
|
96
|
+
if folder is not None:
|
|
97
|
+
os.chdir(folder)
|
|
98
|
+
conf = _tool_config(None)
|
|
99
|
+
return Settings(
|
|
100
|
+
select=resolve_selectors([conf.lint_select]) if conf.lint_select else (),
|
|
101
|
+
ignore=resolve_selectors([conf.lint_ignore]) if conf.lint_ignore else (),
|
|
102
|
+
format_options={
|
|
103
|
+
"indent": conf.format_indent,
|
|
104
|
+
"use_sql": conf.format_sql,
|
|
105
|
+
"leading_comma": conf.format_leading_comma,
|
|
106
|
+
"rewrite_sql": conf.format_rewrite_sql,
|
|
107
|
+
},
|
|
108
|
+
)
|
|
109
|
+
except Exception as exc: # a bad config must not take the server down
|
|
110
|
+
self.window_show_message(
|
|
111
|
+
types.ShowMessageParams(type=types.MessageType.Warning, message=f"execsql config: {exc}"),
|
|
112
|
+
)
|
|
113
|
+
return Settings()
|
|
114
|
+
finally:
|
|
115
|
+
os.chdir(here)
|
|
116
|
+
|
|
117
|
+
def settings_for(self, uri: str) -> Settings:
|
|
118
|
+
"""The settings of the innermost workspace folder holding *uri*'s file, else :attr:`fallback`."""
|
|
119
|
+
path = to_fs_path(uri)
|
|
120
|
+
if path is None:
|
|
121
|
+
return self.fallback
|
|
122
|
+
key = same_file_key(path)
|
|
123
|
+
holding = [f for f in self.settings if key == f or key.startswith(f.rstrip(os.sep) + os.sep)]
|
|
124
|
+
return self.settings[max(holding, key=len)] if holding else self.fallback
|
|
125
|
+
|
|
126
|
+
def index(self, uri: str) -> ScriptIndex:
|
|
127
|
+
"""The current document's index (rebuilt per request: a few milliseconds).
|
|
128
|
+
|
|
129
|
+
A script the parser cannot handle (nesting past Python's recursion
|
|
130
|
+
limit) gets an empty index, so features fall back to what needs none.
|
|
131
|
+
"""
|
|
132
|
+
document = self.workspace.get_text_document(uri)
|
|
133
|
+
path = to_fs_path(uri)
|
|
134
|
+
try:
|
|
135
|
+
index = index_script(document.source, path, self.read_included)
|
|
136
|
+
except RecursionError:
|
|
137
|
+
index = ScriptIndex(path=path)
|
|
138
|
+
index.open_uris = self.open_uris()
|
|
139
|
+
return index
|
|
140
|
+
|
|
141
|
+
def open_uris(self) -> dict[str, str]:
|
|
142
|
+
"""The open documents' URIs as the client spelled them, by :func:`same_file_key` of their paths.
|
|
143
|
+
|
|
144
|
+
A location in another open file is reported under this URI: one
|
|
145
|
+
rebuilt from the path can differ (``file:///c:/``, ``file:///C:/`` on
|
|
146
|
+
Windows), and a client may not match the two.
|
|
147
|
+
"""
|
|
148
|
+
found: dict[str, str] = {}
|
|
149
|
+
for uri in self.workspace.text_documents:
|
|
150
|
+
fs_path = to_fs_path(uri)
|
|
151
|
+
if fs_path is not None:
|
|
152
|
+
found.setdefault(same_file_key(fs_path), uri)
|
|
153
|
+
return found
|
|
154
|
+
|
|
155
|
+
def read_included(self, path: Path) -> str | None:
|
|
156
|
+
"""An ``INCLUDE``d file's text: the editor's copy if it is open (saved or not), else the file's.
|
|
157
|
+
|
|
158
|
+
Open documents are matched by path, not URI: clients spell the same
|
|
159
|
+
file differently (``file:///c%3A/``, ``file:///C:/`` on Windows).
|
|
160
|
+
"""
|
|
161
|
+
uri = self.open_uris().get(same_file_key(str(path)))
|
|
162
|
+
if uri is not None:
|
|
163
|
+
return self.workspace.get_text_document(uri).source
|
|
164
|
+
return read_included(path)
|
|
165
|
+
|
|
166
|
+
def supports_snippets(self) -> bool:
|
|
167
|
+
caps = self.client_capabilities.text_document
|
|
168
|
+
item = caps.completion.completion_item if caps and caps.completion else None
|
|
169
|
+
return bool(item and item.snippet_support)
|
|
170
|
+
|
|
171
|
+
def publish(self, uri: str) -> None:
|
|
172
|
+
"""Lint the document's current text and publish the result."""
|
|
173
|
+
document = self.workspace.get_text_document(uri)
|
|
174
|
+
settings = self.settings_for(uri)
|
|
175
|
+
try:
|
|
176
|
+
found = diagnostics(document.source, to_fs_path(uri), settings.select, settings.ignore)
|
|
177
|
+
except RecursionError:
|
|
178
|
+
found = [too_deep_diagnostic()]
|
|
179
|
+
self.text_document_publish_diagnostics(
|
|
180
|
+
types.PublishDiagnosticsParams(uri=uri, version=document.version, diagnostics=found),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def publish_all(self) -> None:
|
|
184
|
+
"""Lint every open document again: a file one of them includes, or the config, changed."""
|
|
185
|
+
for uri in list(self.workspace.text_documents):
|
|
186
|
+
self.publish(uri)
|
|
187
|
+
|
|
188
|
+
def watch_files(self) -> None:
|
|
189
|
+
"""Ask the client to report changes to :data:`WATCHED_FILES`, if it can."""
|
|
190
|
+
caps = self.client_capabilities.workspace
|
|
191
|
+
watched = caps.did_change_watched_files if caps else None
|
|
192
|
+
if not (watched and watched.dynamic_registration):
|
|
193
|
+
return
|
|
194
|
+
self.client_register_capability(
|
|
195
|
+
types.RegistrationParams(
|
|
196
|
+
registrations=[
|
|
197
|
+
types.Registration(
|
|
198
|
+
id="execsql-watched-files",
|
|
199
|
+
method=types.WORKSPACE_DID_CHANGE_WATCHED_FILES,
|
|
200
|
+
register_options=types.DidChangeWatchedFilesRegistrationOptions(
|
|
201
|
+
watchers=[types.FileSystemWatcher(glob_pattern=g) for g in WATCHED_FILES],
|
|
202
|
+
),
|
|
203
|
+
),
|
|
204
|
+
],
|
|
205
|
+
),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def publish_later(self, uri: str) -> None:
|
|
209
|
+
"""Lint after :data:`LINT_DELAY_SECONDS` without another change."""
|
|
210
|
+
previous = self.pending.pop(uri, None)
|
|
211
|
+
if previous is not None:
|
|
212
|
+
previous.cancel()
|
|
213
|
+
|
|
214
|
+
async def run() -> None:
|
|
215
|
+
await asyncio.sleep(LINT_DELAY_SECONDS)
|
|
216
|
+
self.pending.pop(uri, None)
|
|
217
|
+
self.publish(uri)
|
|
218
|
+
|
|
219
|
+
self.pending[uri] = asyncio.ensure_future(run())
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def create_server() -> ExecsqlLanguageServer:
|
|
223
|
+
"""A server with every feature registered."""
|
|
224
|
+
server = ExecsqlLanguageServer()
|
|
225
|
+
rpc_logger = logging.getLogger("pygls.protocol.json_rpc")
|
|
226
|
+
if not any(isinstance(f, _DropLateCancels) for f in rpc_logger.filters):
|
|
227
|
+
rpc_logger.addFilter(_DropLateCancels())
|
|
228
|
+
|
|
229
|
+
@server.feature(types.INITIALIZED)
|
|
230
|
+
def initialized(ls: ExecsqlLanguageServer, params: types.InitializedParams) -> None:
|
|
231
|
+
ls.load_settings()
|
|
232
|
+
ls.watch_files()
|
|
233
|
+
|
|
234
|
+
@server.feature(types.TEXT_DOCUMENT_DID_OPEN)
|
|
235
|
+
def did_open(ls: ExecsqlLanguageServer, params: types.DidOpenTextDocumentParams) -> None:
|
|
236
|
+
ls.publish(params.text_document.uri)
|
|
237
|
+
|
|
238
|
+
@server.feature(types.TEXT_DOCUMENT_DID_CHANGE)
|
|
239
|
+
def did_change(ls: ExecsqlLanguageServer, params: types.DidChangeTextDocumentParams) -> None:
|
|
240
|
+
ls.publish_later(params.text_document.uri)
|
|
241
|
+
|
|
242
|
+
@server.feature(types.TEXT_DOCUMENT_DID_SAVE)
|
|
243
|
+
def did_save(ls: ExecsqlLanguageServer, params: types.DidSaveTextDocumentParams) -> None:
|
|
244
|
+
# Another open script may INCLUDE this one; a client that cannot watch files reports nothing else.
|
|
245
|
+
ls.publish_all()
|
|
246
|
+
|
|
247
|
+
@server.feature(types.WORKSPACE_DID_CHANGE_WATCHED_FILES)
|
|
248
|
+
def did_change_watched_files(ls: ExecsqlLanguageServer, params: types.DidChangeWatchedFilesParams) -> None:
|
|
249
|
+
if any(Path(to_fs_path(change.uri) or "").name.lower() == "execsql.conf" for change in params.changes):
|
|
250
|
+
ls.load_settings()
|
|
251
|
+
ls.publish_all()
|
|
252
|
+
|
|
253
|
+
@server.feature(types.WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
|
|
254
|
+
def did_change_folders(ls: ExecsqlLanguageServer, params: types.DidChangeWorkspaceFoldersParams) -> None:
|
|
255
|
+
# pygls has already updated ls.workspace.folders.
|
|
256
|
+
ls.load_settings()
|
|
257
|
+
ls.publish_all()
|
|
258
|
+
|
|
259
|
+
@server.feature(types.TEXT_DOCUMENT_DID_CLOSE)
|
|
260
|
+
def did_close(ls: ExecsqlLanguageServer, params: types.DidCloseTextDocumentParams) -> None:
|
|
261
|
+
uri = params.text_document.uri
|
|
262
|
+
pending = ls.pending.pop(uri, None)
|
|
263
|
+
if pending is not None:
|
|
264
|
+
pending.cancel()
|
|
265
|
+
ls.text_document_publish_diagnostics(types.PublishDiagnosticsParams(uri=uri, diagnostics=[]))
|
|
266
|
+
|
|
267
|
+
@server.feature(
|
|
268
|
+
types.TEXT_DOCUMENT_COMPLETION,
|
|
269
|
+
types.CompletionOptions(trigger_characters=["!", "(", "{", "$", " "]),
|
|
270
|
+
)
|
|
271
|
+
def completion(ls: ExecsqlLanguageServer, params: types.CompletionParams) -> types.CompletionList:
|
|
272
|
+
uri = params.text_document.uri
|
|
273
|
+
source = ls.workspace.get_text_document(uri).source
|
|
274
|
+
return completions(
|
|
275
|
+
ls.index(uri),
|
|
276
|
+
source,
|
|
277
|
+
params.position.line,
|
|
278
|
+
params.position.character,
|
|
279
|
+
snippets=ls.supports_snippets(),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@server.feature(types.TEXT_DOCUMENT_HOVER)
|
|
283
|
+
def on_hover(ls: ExecsqlLanguageServer, params: types.HoverParams) -> types.Hover | None:
|
|
284
|
+
uri = params.text_document.uri
|
|
285
|
+
source = ls.workspace.get_text_document(uri).source
|
|
286
|
+
return hover(ls.index(uri), source, params.position.line, params.position.character)
|
|
287
|
+
|
|
288
|
+
@server.feature(types.TEXT_DOCUMENT_DEFINITION)
|
|
289
|
+
def on_definition(ls: ExecsqlLanguageServer, params: types.DefinitionParams) -> list[types.Location]:
|
|
290
|
+
uri = params.text_document.uri
|
|
291
|
+
return definition(ls.index(uri), uri, params.position.line, params.position.character)
|
|
292
|
+
|
|
293
|
+
@server.feature(types.TEXT_DOCUMENT_REFERENCES)
|
|
294
|
+
def on_references(ls: ExecsqlLanguageServer, params: types.ReferenceParams) -> list[types.Location]:
|
|
295
|
+
uri = params.text_document.uri
|
|
296
|
+
return references(
|
|
297
|
+
ls.index(uri),
|
|
298
|
+
uri,
|
|
299
|
+
params.position.line,
|
|
300
|
+
params.position.character,
|
|
301
|
+
include_declaration=params.context.include_declaration,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
@server.feature(types.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
305
|
+
def on_symbols(ls: ExecsqlLanguageServer, params: types.DocumentSymbolParams) -> list[types.DocumentSymbol]:
|
|
306
|
+
uri = params.text_document.uri
|
|
307
|
+
try:
|
|
308
|
+
return document_symbols(ls.index(uri), ls.workspace.get_text_document(uri).source)
|
|
309
|
+
except RecursionError: # nesting deeper than the outline can walk: no outline
|
|
310
|
+
return []
|
|
311
|
+
|
|
312
|
+
@server.feature(
|
|
313
|
+
types.TEXT_DOCUMENT_CODE_ACTION,
|
|
314
|
+
types.CodeActionOptions(code_action_kinds=[types.CodeActionKind.QuickFix]),
|
|
315
|
+
)
|
|
316
|
+
def on_code_action(ls: ExecsqlLanguageServer, params: types.CodeActionParams) -> list[types.CodeAction]:
|
|
317
|
+
uri = params.text_document.uri
|
|
318
|
+
source = ls.workspace.get_text_document(uri).source
|
|
319
|
+
return code_actions(ls.index(uri), uri, source, params.context.diagnostics)
|
|
320
|
+
|
|
321
|
+
@server.feature(types.TEXT_DOCUMENT_FORMATTING)
|
|
322
|
+
def on_format(ls: ExecsqlLanguageServer, params: types.DocumentFormattingParams) -> list[types.TextEdit] | None:
|
|
323
|
+
uri = params.text_document.uri
|
|
324
|
+
source = ls.workspace.get_text_document(uri).source
|
|
325
|
+
try:
|
|
326
|
+
return format_document(source, **ls.settings_for(uri).format_options)
|
|
327
|
+
except Exception as exc: # e.g. the formatter extra missing: say so, change nothing
|
|
328
|
+
ls.window_show_message(
|
|
329
|
+
types.ShowMessageParams(type=types.MessageType.Error, message=f"execsql format: {exc}"),
|
|
330
|
+
)
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
@server.feature(types.TEXT_DOCUMENT_PREPARE_RENAME)
|
|
334
|
+
def on_prepare_rename(ls: ExecsqlLanguageServer, params: types.PrepareRenameParams) -> types.Range | None:
|
|
335
|
+
return prepare_rename(ls.index(params.text_document.uri), params.position.line, params.position.character)
|
|
336
|
+
|
|
337
|
+
@server.feature(types.TEXT_DOCUMENT_RENAME, types.RenameOptions(prepare_provider=True))
|
|
338
|
+
def on_rename(ls: ExecsqlLanguageServer, params: types.RenameParams) -> types.WorkspaceEdit | None:
|
|
339
|
+
uri = params.text_document.uri
|
|
340
|
+
try:
|
|
341
|
+
return rename(ls.index(uri), uri, params.position.line, params.position.character, params.new_name)
|
|
342
|
+
except RenameError as exc:
|
|
343
|
+
ls.window_show_message(types.ShowMessageParams(type=types.MessageType.Warning, message=str(exc)))
|
|
344
|
+
return None
|
|
345
|
+
|
|
346
|
+
@server.feature(types.TEXT_DOCUMENT_FOLDING_RANGE)
|
|
347
|
+
def on_folding(ls: ExecsqlLanguageServer, params: types.FoldingRangeParams) -> list[types.FoldingRange]:
|
|
348
|
+
uri = params.text_document.uri
|
|
349
|
+
try:
|
|
350
|
+
return folding_ranges(ls.workspace.get_text_document(uri).source, to_fs_path(uri))
|
|
351
|
+
except RecursionError: # nesting deeper than the parser can walk: no folding
|
|
352
|
+
return []
|
|
353
|
+
|
|
354
|
+
@server.feature(types.TEXT_DOCUMENT_DOCUMENT_LINK)
|
|
355
|
+
def on_links(ls: ExecsqlLanguageServer, params: types.DocumentLinkParams) -> list[types.DocumentLink]:
|
|
356
|
+
return document_links(ls.index(params.text_document.uri))
|
|
357
|
+
|
|
358
|
+
return server
|
execsql/metacommands/dispatch.py
CHANGED
|
@@ -705,6 +705,13 @@ def build_dispatch_table() -> MetaCommandList:
|
|
|
705
705
|
description="QUOTE_ALL_TEXT",
|
|
706
706
|
category="config_option",
|
|
707
707
|
)
|
|
708
|
+
# The documented form; upstream accepted only bare MAX_INT, which stays.
|
|
709
|
+
mcl.add(
|
|
710
|
+
r"^\s*CONFIG\s+MAX_INT\s+(?P<maxint>[0-9]+)\s*$",
|
|
711
|
+
x_max_int,
|
|
712
|
+
description="MAX_INT",
|
|
713
|
+
category="config_option",
|
|
714
|
+
)
|
|
708
715
|
mcl.add(
|
|
709
716
|
r"^\s*CONFIG\s+IMPORT_ROW_BUFFER\s+(?P<rows>[1-9][0-9]*)\s*$",
|
|
710
717
|
x_import_row_buffer,
|