sqlakit-lsp 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/PKG-INFO +3 -2
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/README.md +2 -1
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/pyproject.toml +1 -1
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/pyproject.toml.orig +1 -1
- sqlakit_lsp-0.3.2/sqlakit_lsp/_live.py +92 -0
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/sqlakit_lsp/_server.py +196 -19
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/LICENSE +0 -0
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/sqlakit_lsp/__init__.py +0 -0
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/sqlakit_lsp/__main__.py +0 -0
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/sqlakit_lsp/_cli.py +0 -0
- {sqlakit_lsp-0.3.0 → sqlakit_lsp-0.3.2}/sqlakit_lsp/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sqlakit-lsp
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: The language server for SQLAKit templates.
|
|
5
5
|
Keywords: sqlalchemy,sqlakit,lsp,language-server,sql
|
|
6
6
|
Author: Anton Ruhlov
|
|
@@ -41,7 +41,8 @@ templates. In a `.sql` template:
|
|
|
41
41
|
- the macros' calls and the parameters coloured
|
|
42
42
|
- a parameter no call of the project passes, marked, with a quick fix for a
|
|
43
43
|
name close to one the calls pass
|
|
44
|
-
- **Show rendered SQL**, the whole template as the SQL it writes
|
|
44
|
+
- **Show rendered SQL**, the whole template as the SQL it writes, with `:name`
|
|
45
|
+
or with `?` for each parameter
|
|
45
46
|
- the outline of a template, and a search for a macro or a template
|
|
46
47
|
|
|
47
48
|
In Python, the template name in `db.sql("...")` completes and links to its
|
|
@@ -14,7 +14,8 @@ templates. In a `.sql` template:
|
|
|
14
14
|
- the macros' calls and the parameters coloured
|
|
15
15
|
- a parameter no call of the project passes, marked, with a quick fix for a
|
|
16
16
|
name close to one the calls pass
|
|
17
|
-
- **Show rendered SQL**, the whole template as the SQL it writes
|
|
17
|
+
- **Show rendered SQL**, the whole template as the SQL it writes, with `:name`
|
|
18
|
+
or with `?` for each parameter
|
|
18
19
|
- the outline of a template, and a search for a macro or a template
|
|
19
20
|
|
|
20
21
|
In Python, the template name in `db.sql("...")` completes and links to its
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Render a template with the project's own macros, in the project's Python.
|
|
2
|
+
|
|
3
|
+
The server reads the project's code and never runs it, so a macro written in
|
|
4
|
+
Python stays a call when it renders a template. **Show rendered SQL** asks for
|
|
5
|
+
more: the server runs this file with the project's interpreter, which imports
|
|
6
|
+
the modules of those macros, and prints the SQL as JSON.
|
|
7
|
+
|
|
8
|
+
It reads its request as JSON on stdin, and imports nothing of the server: the
|
|
9
|
+
project's environment has `sqlakit`, and not the server.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _Example:
|
|
21
|
+
"""A value to render a template with: any path reads another of it."""
|
|
22
|
+
|
|
23
|
+
def __getattr__(self, _: str) -> _Example:
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def __getitem__(self, _: object) -> _Example:
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def __iter__(self) -> Any: # noqa: ANN401
|
|
30
|
+
return iter((self,))
|
|
31
|
+
|
|
32
|
+
def __len__(self) -> int:
|
|
33
|
+
return 1
|
|
34
|
+
|
|
35
|
+
def __str__(self) -> str:
|
|
36
|
+
return "x"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _module(path: Path) -> tuple[Path, str]:
|
|
40
|
+
"""Return the directory a module is imported from, and its dotted name."""
|
|
41
|
+
parts = [path.stem]
|
|
42
|
+
folder = path.parent
|
|
43
|
+
while (folder / "__init__.py").is_file():
|
|
44
|
+
parts.append(folder.name)
|
|
45
|
+
folder = folder.parent
|
|
46
|
+
return folder, ".".join(reversed(parts))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _preparer(dialect: str) -> Any: # noqa: ANN401
|
|
50
|
+
import sqlalchemy as sa # noqa: PLC0415
|
|
51
|
+
import sqlalchemy.engine.default # noqa: PLC0415
|
|
52
|
+
import sqlalchemy.exc # noqa: PLC0415
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
return sa.engine.make_url(f"{dialect}://").get_dialect()().identifier_preparer
|
|
56
|
+
except sa.exc.NoSuchModuleError:
|
|
57
|
+
default = sa.engine.default.DefaultDialect()
|
|
58
|
+
default.name = dialect
|
|
59
|
+
return default.identifier_preparer
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def main() -> None:
|
|
63
|
+
request = json.load(sys.stdin)
|
|
64
|
+
from sqlakit._project import load_project # noqa: PLC0415
|
|
65
|
+
from sqlakit._sql import Context, calls_kept, registered # noqa: PLC0415
|
|
66
|
+
|
|
67
|
+
project = load_project(Path(request["root"]))
|
|
68
|
+
for file in request["modules"]:
|
|
69
|
+
folder, name = _module(Path(file))
|
|
70
|
+
if str(folder) not in sys.path:
|
|
71
|
+
sys.path.insert(0, str(folder))
|
|
72
|
+
try:
|
|
73
|
+
found = registered([name])
|
|
74
|
+
except Exception as error: # noqa: BLE001 - the project's code raises anything
|
|
75
|
+
sys.stdout.write(
|
|
76
|
+
json.dumps({"error": f"{name} cannot be imported: {error}"}) + "\n"
|
|
77
|
+
)
|
|
78
|
+
return
|
|
79
|
+
project.templates.macros.update(
|
|
80
|
+
(key, macro) for key, macro in found.items() if key in request["macros"]
|
|
81
|
+
)
|
|
82
|
+
template = project.load(request["name"], request["source"])
|
|
83
|
+
values: dict[str, Any] = dict.fromkeys(template.parameters(), _Example())
|
|
84
|
+
values.update(dict.fromkeys(request["not_given"]))
|
|
85
|
+
dialect = request["dialect"]
|
|
86
|
+
with calls_kept():
|
|
87
|
+
sql = template.render(Context(dialect, _preparer(dialect), values)).strip()
|
|
88
|
+
sys.stdout.write(json.dumps({"sql": sql}) + "\n")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
main()
|
|
@@ -23,8 +23,10 @@ import asyncio
|
|
|
23
23
|
import bisect
|
|
24
24
|
import difflib
|
|
25
25
|
import inspect
|
|
26
|
+
import json
|
|
26
27
|
import os
|
|
27
28
|
import re
|
|
29
|
+
import subprocess
|
|
28
30
|
import tempfile
|
|
29
31
|
import uuid
|
|
30
32
|
from collections import OrderedDict
|
|
@@ -157,6 +159,66 @@ def _innermost(parts: Sequence[Any], offset: int) -> Any: # noqa: ANN401
|
|
|
157
159
|
return None
|
|
158
160
|
|
|
159
161
|
|
|
162
|
+
_BOUND = re.compile(
|
|
163
|
+
rf"{_LITERAL}|(?<![:\w\\]):(?P<bound>[A-Za-z_]\w*)(?![\w:])", re.DOTALL
|
|
164
|
+
)
|
|
165
|
+
"""A placeholder of rendered SQL, and the strings and comments to skip."""
|
|
166
|
+
|
|
167
|
+
_LIVE = Path(__file__).with_name("_live.py")
|
|
168
|
+
"""The script the project's interpreter renders a template with."""
|
|
169
|
+
|
|
170
|
+
_LIVE_SECONDS = 20
|
|
171
|
+
"""How long a render with the project's macros may take: it imports them."""
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _interpreter(root: Path) -> Path | None:
|
|
175
|
+
"""Return the Python of the project's `.venv`, if it has one."""
|
|
176
|
+
for found in (
|
|
177
|
+
root / ".venv" / "bin" / "python",
|
|
178
|
+
root / ".venv" / "Scripts" / "python.exe",
|
|
179
|
+
):
|
|
180
|
+
if found.is_file():
|
|
181
|
+
return found
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _run_live(root: Path, request: dict[str, Any]) -> tuple[str | None, str]:
|
|
186
|
+
"""Render a template in the project's interpreter: the SQL, or why not."""
|
|
187
|
+
python = _interpreter(root)
|
|
188
|
+
if python is None:
|
|
189
|
+
return None, "the project has no .venv to run its macros in."
|
|
190
|
+
try:
|
|
191
|
+
done = subprocess.run( # noqa: S603 - the project's own interpreter
|
|
192
|
+
[str(python), str(_LIVE)],
|
|
193
|
+
input=json.dumps(request),
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
196
|
+
cwd=root,
|
|
197
|
+
timeout=_LIVE_SECONDS,
|
|
198
|
+
check=False,
|
|
199
|
+
)
|
|
200
|
+
except (OSError, subprocess.TimeoutExpired) as error:
|
|
201
|
+
return None, f"running {_relative(python, root)} failed: {error}"
|
|
202
|
+
try:
|
|
203
|
+
answer = json.loads(done.stdout.strip().splitlines()[-1])
|
|
204
|
+
except (IndexError, ValueError):
|
|
205
|
+
last = (done.stderr.strip().splitlines() or ["no output"])[-1]
|
|
206
|
+
return None, f"{_relative(python, root)} failed: {last}"
|
|
207
|
+
if "sql" in answer:
|
|
208
|
+
return answer["sql"], ""
|
|
209
|
+
return None, str(answer.get("error", "no SQL came back"))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _calls(parts: Sequence[Any]) -> Iterator[Any]:
|
|
213
|
+
"""Yield every macro call of a read template, the ones in arguments too."""
|
|
214
|
+
for part in parts:
|
|
215
|
+
if isinstance(part, str):
|
|
216
|
+
continue
|
|
217
|
+
yield part
|
|
218
|
+
for argument in part.args:
|
|
219
|
+
yield from _calls(argument.parts)
|
|
220
|
+
|
|
221
|
+
|
|
160
222
|
def _rendered(template: Any, dialect: str, values: dict[str, Any]) -> str | None: # noqa: ANN401
|
|
161
223
|
"""Return the SQL a read template writes for these values, if it can.
|
|
162
224
|
|
|
@@ -327,6 +389,9 @@ _WORD = re.compile(r"\w+")
|
|
|
327
389
|
SHOW = "Show rendered SQL"
|
|
328
390
|
"""The title of the action that shows a whole template as SQL."""
|
|
329
391
|
|
|
392
|
+
SHOW_MARKED = "Show rendered SQL with ?"
|
|
393
|
+
"""The title of the action that shows it with `?` for each parameter."""
|
|
394
|
+
|
|
330
395
|
RENDER = "sqlakit.render"
|
|
331
396
|
"""The command that writes a whole template as SQL, for an editor to open."""
|
|
332
397
|
|
|
@@ -815,7 +880,13 @@ class _Assistant:
|
|
|
815
880
|
"""Return the whole template as the database gets it, on the project's dialect.
|
|
816
881
|
|
|
817
882
|
Every parameter counts as given, so every optional part is there, and
|
|
818
|
-
stays a placeholder. A
|
|
883
|
+
stays a placeholder. A parameter a built-in macro cannot take made up,
|
|
884
|
+
the column `tpl.order_by` sorts by, counts as not given.
|
|
885
|
+
|
|
886
|
+
The server reads the project's Python macros and does not run them, so
|
|
887
|
+
when the template calls one, it is rendered again by the project's own
|
|
888
|
+
interpreter, `.venv/bin/python`, which imports them. Without one, or
|
|
889
|
+
when that fails, the calls stay calls, and a comment on top says why.
|
|
819
890
|
"""
|
|
820
891
|
name = self.project.name_of(path) or path.name
|
|
821
892
|
dialect = self.project.dialect or "postgresql"
|
|
@@ -823,11 +894,102 @@ class _Assistant:
|
|
|
823
894
|
template = self.compiled(name, source)
|
|
824
895
|
except SQLAKitError as error:
|
|
825
896
|
return f"-- {name} cannot be read: {error}\n"
|
|
826
|
-
|
|
897
|
+
calls = list(_calls(template.parts))
|
|
898
|
+
values: dict[str, Any] = dict.fromkeys(template.parameters(), _Example())
|
|
899
|
+
values.update(dict.fromkeys(self._not_given(name, source, calls, dialect)))
|
|
827
900
|
sql = _rendered(template, dialect, values)
|
|
828
901
|
if sql is None:
|
|
829
902
|
return f"-- {name} cannot be written with made-up values.\n"
|
|
830
|
-
|
|
903
|
+
namespace = self.project.templates.namespace
|
|
904
|
+
# A call of an included template is in the SQL too, and not in `calls`.
|
|
905
|
+
static = {
|
|
906
|
+
key: macro
|
|
907
|
+
for key, macro in self.project.templates.macros.items()
|
|
908
|
+
if isinstance(macro, StaticMacro)
|
|
909
|
+
}
|
|
910
|
+
kept = {
|
|
911
|
+
key: macro
|
|
912
|
+
for key, macro in static.items()
|
|
913
|
+
if re.search(rf"(?<![\w.]){namespace}\.{key}\s*\(", sql, re.IGNORECASE)
|
|
914
|
+
}
|
|
915
|
+
if not kept:
|
|
916
|
+
return f"-- {name} on {dialect}\n{sql}\n"
|
|
917
|
+
request = {
|
|
918
|
+
"root": str(self.project.root),
|
|
919
|
+
"name": name,
|
|
920
|
+
"source": source,
|
|
921
|
+
"dialect": dialect,
|
|
922
|
+
# Every one, as a macro may call another inside, as a file macro's SQL does.
|
|
923
|
+
"modules": sorted({str(macro.path) for macro in static.values()}),
|
|
924
|
+
"macros": sorted(static),
|
|
925
|
+
"not_given": sorted(key for key, value in values.items() if value is None),
|
|
926
|
+
}
|
|
927
|
+
run, problem = _run_live(self.project.root, request)
|
|
928
|
+
if run is not None:
|
|
929
|
+
return f"-- {name} on {dialect}\n{run}\n"
|
|
930
|
+
listed = ", ".join(f"{namespace}.{key}" for key in sorted(kept))
|
|
931
|
+
return f"-- {name} on {dialect}\n-- {listed} stays a call: {problem}\n{sql}\n"
|
|
932
|
+
|
|
933
|
+
def rendered_marked(self, path: Path, source: str) -> str:
|
|
934
|
+
"""Return the rendered template with `?` for each parameter, in order.
|
|
935
|
+
|
|
936
|
+
A comment on top lists what each `?` stands for, as a driver that
|
|
937
|
+
takes `?` is given the values in that order.
|
|
938
|
+
"""
|
|
939
|
+
text = self.rendered(path, source)
|
|
940
|
+
names: list[str] = []
|
|
941
|
+
|
|
942
|
+
def marked(found: re.Match[str]) -> str:
|
|
943
|
+
name = found.group("bound")
|
|
944
|
+
if name is None:
|
|
945
|
+
return found.group(0)
|
|
946
|
+
names.append(name)
|
|
947
|
+
return "?"
|
|
948
|
+
|
|
949
|
+
text = _BOUND.sub(marked, text)
|
|
950
|
+
if not names:
|
|
951
|
+
return text
|
|
952
|
+
first, _, rest = text.partition("\n")
|
|
953
|
+
listed = ", ".join(f":{name}" for name in names)
|
|
954
|
+
return f"{first}\n-- ? in order: {listed}\n{rest}"
|
|
955
|
+
|
|
956
|
+
def _not_given(
|
|
957
|
+
self, name: str, source: str, calls: Sequence[Any], dialect: str
|
|
958
|
+
) -> set[str]:
|
|
959
|
+
"""Return the parameters to render as not given.
|
|
960
|
+
|
|
961
|
+
They are those of a call of a built-in macro that cannot be made with a
|
|
962
|
+
made-up value, and can with none: `:sort` of `tpl.order_by`, whose value
|
|
963
|
+
must be a column it lists.
|
|
964
|
+
"""
|
|
965
|
+
found: set[str] = set()
|
|
966
|
+
for call in calls:
|
|
967
|
+
if isinstance(self.project.templates.macros.get(call.name), StaticMacro):
|
|
968
|
+
continue
|
|
969
|
+
text = source[call.span[0] : call.span[1]]
|
|
970
|
+
try:
|
|
971
|
+
alone = self.compiled(name, text)
|
|
972
|
+
except SQLAKitError:
|
|
973
|
+
continue
|
|
974
|
+
names = alone.parameters()
|
|
975
|
+
given = _rendered(alone, dialect, dict.fromkeys(names, _Example()))
|
|
976
|
+
if names and self._kept(given, call.name):
|
|
977
|
+
missing = _rendered(alone, dialect, dict.fromkeys(names))
|
|
978
|
+
if not self._kept(missing, call.name):
|
|
979
|
+
found.update(names)
|
|
980
|
+
return found
|
|
981
|
+
|
|
982
|
+
def _kept(self, sql: str | None, macro: str) -> bool:
|
|
983
|
+
"""Whether a call rendered alone could not be made, and wrote itself.
|
|
984
|
+
|
|
985
|
+
It writes itself on one line, whatever lines it was written on.
|
|
986
|
+
"""
|
|
987
|
+
if sql is None:
|
|
988
|
+
return True
|
|
989
|
+
namespace = re.escape(self.project.templates.namespace)
|
|
990
|
+
return bool(
|
|
991
|
+
re.match(rf"{namespace}\.{re.escape(macro)}\s*\(", sql, re.IGNORECASE)
|
|
992
|
+
)
|
|
831
993
|
|
|
832
994
|
def holds_macros(self, path: Path) -> bool:
|
|
833
995
|
"""Whether a file defines macros: SQL macros, or the SQL of a file macro."""
|
|
@@ -911,7 +1073,7 @@ class _Assistant:
|
|
|
911
1073
|
missing = _rendered(alone, dialect, dict.fromkeys(names))
|
|
912
1074
|
# A call that cannot be made writes itself, which says nothing new.
|
|
913
1075
|
given, missing = (
|
|
914
|
-
None if one
|
|
1076
|
+
None if self._kept(one, call.name) else one for one in (given, missing)
|
|
915
1077
|
)
|
|
916
1078
|
if not names or given == missing:
|
|
917
1079
|
one = given or missing
|
|
@@ -2454,21 +2616,30 @@ def _server() -> Any: # noqa: ANN401, C901, PLR0915 - a handler for each reques
|
|
|
2454
2616
|
support = None if action is None else action.resolve_support
|
|
2455
2617
|
return bool(support is not None and "edit" in support.properties)
|
|
2456
2618
|
|
|
2457
|
-
def rendered_file(
|
|
2619
|
+
def rendered_file(
|
|
2620
|
+
ls: LanguageServer, uri: str, *, marked: bool = False
|
|
2621
|
+
) -> tuple[Path, str]:
|
|
2458
2622
|
"""Return a new file for the rendered template, and what goes in it."""
|
|
2459
2623
|
helper = assistant()
|
|
2460
2624
|
path = _path(uri)
|
|
2461
2625
|
source = ls.workspace.get_text_document(uri).source
|
|
2462
2626
|
name = helper.project.name_of(path) or path.name if helper else path.name
|
|
2463
|
-
|
|
2627
|
+
if helper is None:
|
|
2628
|
+
text = ""
|
|
2629
|
+
elif marked:
|
|
2630
|
+
text = helper.rendered_marked(path, source)
|
|
2631
|
+
else:
|
|
2632
|
+
text = helper.rendered(path, source)
|
|
2464
2633
|
# A new file each time: the editor may still hold the one before.
|
|
2465
2634
|
written = _RENDERED / uuid.uuid4().hex[:8] / name
|
|
2466
2635
|
written.parent.mkdir(parents=True, exist_ok=True)
|
|
2467
2636
|
return written, text
|
|
2468
2637
|
|
|
2469
|
-
def rendered_edit(
|
|
2638
|
+
def rendered_edit(
|
|
2639
|
+
ls: LanguageServer, uri: str, *, marked: bool = False
|
|
2640
|
+
) -> types.WorkspaceEdit:
|
|
2470
2641
|
"""Return an edit that makes the file of the rendered template."""
|
|
2471
|
-
written, text = rendered_file(ls, uri)
|
|
2642
|
+
written, text = rendered_file(ls, uri, marked=marked)
|
|
2472
2643
|
target = written.as_uri()
|
|
2473
2644
|
start = types.Position(0, 0)
|
|
2474
2645
|
return types.WorkspaceEdit(
|
|
@@ -2502,14 +2673,18 @@ def _server() -> Any: # noqa: ANN401, C901, PLR0915 - a handler for each reques
|
|
|
2502
2673
|
return actions or None
|
|
2503
2674
|
if not path.name.endswith(".sql"):
|
|
2504
2675
|
return actions or None
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2676
|
+
for title, marked in ((SHOW, False), (SHOW_MARKED, True)):
|
|
2677
|
+
action = types.CodeAction(title=title, kind=types.CodeActionKind.Empty)
|
|
2678
|
+
if shows_documents(ls):
|
|
2679
|
+
action.command = types.Command(
|
|
2680
|
+
title=title, command=RENDER, arguments=[uri, marked]
|
|
2681
|
+
)
|
|
2682
|
+
elif resolves_edits(ls):
|
|
2683
|
+
action.data = {"uri": uri, "marked": marked}
|
|
2684
|
+
else:
|
|
2685
|
+
action.edit = rendered_edit(ls, uri, marked=marked)
|
|
2686
|
+
actions.append(action)
|
|
2687
|
+
return actions
|
|
2513
2688
|
|
|
2514
2689
|
def fixes(ls: LanguageServer, params: Any) -> list[types.CodeAction]: # noqa: ANN401
|
|
2515
2690
|
"""Return a quick fix for each problem of ours the editor sends along.
|
|
@@ -2552,13 +2727,15 @@ def _server() -> Any: # noqa: ANN401, C901, PLR0915 - a handler for each reques
|
|
|
2552
2727
|
"""Work out the edit of an action once the editor has chosen it."""
|
|
2553
2728
|
data = action.data or {}
|
|
2554
2729
|
if "uri" in data:
|
|
2555
|
-
action.edit = rendered_edit(
|
|
2730
|
+
action.edit = rendered_edit(
|
|
2731
|
+
ls, data["uri"], marked=bool(data.get("marked"))
|
|
2732
|
+
)
|
|
2556
2733
|
return action
|
|
2557
2734
|
|
|
2558
2735
|
@server.command(RENDER)
|
|
2559
|
-
async def render(ls: LanguageServer, uri: str) -> None:
|
|
2736
|
+
async def render(ls: LanguageServer, uri: str, marked: bool = False) -> None: # noqa: FBT001, FBT002 - a command's arguments come in order
|
|
2560
2737
|
"""Write the rendered template to a file, and have the editor open it."""
|
|
2561
|
-
written, text = rendered_file(ls, uri)
|
|
2738
|
+
written, text = rendered_file(ls, uri, marked=marked)
|
|
2562
2739
|
written.write_text(text, encoding="utf-8")
|
|
2563
2740
|
await ls.window_show_document_async(
|
|
2564
2741
|
types.ShowDocumentParams(written.as_uri(), take_focus=True)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|