sqlakit-lsp 0.1.2__tar.gz → 0.2.0__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.1.2 → sqlakit_lsp-0.2.0}/PKG-INFO +1 -1
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/pyproject.toml +1 -1
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/pyproject.toml.orig +1 -1
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/sqlakit_lsp/_server.py +98 -21
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/LICENSE +0 -0
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/README.md +0 -0
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/sqlakit_lsp/__init__.py +0 -0
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/sqlakit_lsp/__main__.py +0 -0
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/sqlakit_lsp/_cli.py +0 -0
- {sqlakit_lsp-0.1.2 → sqlakit_lsp-0.2.0}/sqlakit_lsp/py.typed +0 -0
|
@@ -23,6 +23,7 @@ import asyncio
|
|
|
23
23
|
import bisect
|
|
24
24
|
import difflib
|
|
25
25
|
import inspect
|
|
26
|
+
import os
|
|
26
27
|
import re
|
|
27
28
|
import tempfile
|
|
28
29
|
import uuid
|
|
@@ -476,9 +477,27 @@ class _Assistant:
|
|
|
476
477
|
def names(self) -> list[str]:
|
|
477
478
|
"""Every template's name, read once until `forget_files`."""
|
|
478
479
|
if self._names is None:
|
|
479
|
-
self._names = self.
|
|
480
|
+
self._names = sorted(self._on_disk())
|
|
480
481
|
return self._names
|
|
481
482
|
|
|
483
|
+
def _on_disk(self) -> dict[str, Path]:
|
|
484
|
+
"""Return each template's name and file, as `Templates.names` finds them.
|
|
485
|
+
|
|
486
|
+
One walk of the directories, in strings: a file made has the next
|
|
487
|
+
keystroke read them again, and paths cost more than the walk.
|
|
488
|
+
"""
|
|
489
|
+
skipped = {str(path) for path in self._macro_files | self._macro_sql}
|
|
490
|
+
found: dict[str, Path] = {}
|
|
491
|
+
for root in self._template_roots:
|
|
492
|
+
start = len(str(root)) + 1
|
|
493
|
+
for directory, _, files in os.walk(root):
|
|
494
|
+
for file in files:
|
|
495
|
+
written = os.path.join(directory, file) # noqa: PTH118 - strings, for speed
|
|
496
|
+
if file.endswith(".sql") and written not in skipped:
|
|
497
|
+
name = written[start:].replace(os.sep, "/")
|
|
498
|
+
found.setdefault(name, Path(written))
|
|
499
|
+
return found
|
|
500
|
+
|
|
482
501
|
def defines_macros(self, path: Path) -> bool:
|
|
483
502
|
"""Whether a file defines a macro the project calls, its own or built in.
|
|
484
503
|
|
|
@@ -1031,7 +1050,7 @@ class _Assistant:
|
|
|
1031
1050
|
return next(
|
|
1032
1051
|
(
|
|
1033
1052
|
(start, end)
|
|
1034
|
-
for start, end, _ in
|
|
1053
|
+
for start, end, _ in self._files_named(source, path)
|
|
1035
1054
|
if start <= offset <= end
|
|
1036
1055
|
),
|
|
1037
1056
|
defined,
|
|
@@ -1129,25 +1148,46 @@ class _Assistant:
|
|
|
1129
1148
|
def python_definition(
|
|
1130
1149
|
self, source: str, offset: int, path: Path | None = None
|
|
1131
1150
|
) -> Target | None:
|
|
1132
|
-
"""Return the
|
|
1151
|
+
"""Return the file the name under the offset reads.
|
|
1133
1152
|
|
|
1134
|
-
|
|
1153
|
+
That is a template in `db.sql("...")`, or the SQL of a macro in
|
|
1154
|
+
`@sql_macro("...")`. On the name of a `def` that defines a macro, it is
|
|
1155
|
+
the name itself.
|
|
1135
1156
|
"""
|
|
1136
|
-
for start, end,
|
|
1157
|
+
for start, end, found in self._files_named(source, path):
|
|
1137
1158
|
if start <= offset <= end:
|
|
1138
|
-
found
|
|
1139
|
-
return None if found is None else Target(found, 0)
|
|
1159
|
+
return Target(found, 0)
|
|
1140
1160
|
return None if path is None else self._itself(path, source, offset)
|
|
1141
1161
|
|
|
1162
|
+
def _files_named(
|
|
1163
|
+
self, source: str, path: Path | None
|
|
1164
|
+
) -> list[tuple[int, int, Path]]:
|
|
1165
|
+
"""Return each file the Python names, where the name is, and the file.
|
|
1166
|
+
|
|
1167
|
+
A template is looked for under the template directories, and the SQL of
|
|
1168
|
+
`@sql_macro("...")` next to the module, as `sql_macro` reads it.
|
|
1169
|
+
"""
|
|
1170
|
+
found = [
|
|
1171
|
+
(start, end, target)
|
|
1172
|
+
for start, end, name in _template_names(source)
|
|
1173
|
+
if (target := self.project.path_of(name)) is not None
|
|
1174
|
+
]
|
|
1175
|
+
if path is not None:
|
|
1176
|
+
found.extend(
|
|
1177
|
+
(start, end, target)
|
|
1178
|
+
for start, end, name in _sql_macro_files(source)
|
|
1179
|
+
if (target := path.parent / name).is_file()
|
|
1180
|
+
)
|
|
1181
|
+
return found
|
|
1182
|
+
|
|
1142
1183
|
def links(self, path: Path, source: str) -> list[tuple[int, int, Path]]:
|
|
1143
|
-
"""Return each
|
|
1144
|
-
if
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
]
|
|
1184
|
+
"""Return each file the text names, where the name is, and the file."""
|
|
1185
|
+
if path.suffix == ".py":
|
|
1186
|
+
return self._files_named(source, path)
|
|
1187
|
+
named = [
|
|
1188
|
+
(found.start(1), found.end(1), found.group(1))
|
|
1189
|
+
for found in self._include_path.finditer(source)
|
|
1190
|
+
]
|
|
1151
1191
|
return [
|
|
1152
1192
|
(start, end, target)
|
|
1153
1193
|
for start, end, name in named
|
|
@@ -1397,12 +1437,9 @@ class _Assistant:
|
|
|
1397
1437
|
The paths are resolved, as the editor's open files are keyed.
|
|
1398
1438
|
"""
|
|
1399
1439
|
if self._files is None:
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
if (root / name).is_file():
|
|
1404
|
-
named[root / name] = name
|
|
1405
|
-
break
|
|
1440
|
+
templates = self._on_disk()
|
|
1441
|
+
self._names = sorted(templates)
|
|
1442
|
+
named = {path: name for name, path in templates.items()}
|
|
1406
1443
|
sql_files = [
|
|
1407
1444
|
*named,
|
|
1408
1445
|
*(path.resolve() for path in self.project.macro_files()),
|
|
@@ -1644,6 +1681,46 @@ def _template_names(source: str) -> list[tuple[int, int, str]]:
|
|
|
1644
1681
|
return found
|
|
1645
1682
|
|
|
1646
1683
|
|
|
1684
|
+
def _sql_macro_files(source: str) -> list[tuple[int, int, str]]:
|
|
1685
|
+
"""Return where `@sql_macro("file.sql")` names its SQL, the quotes left out."""
|
|
1686
|
+
if "sql_macro" not in source:
|
|
1687
|
+
return []
|
|
1688
|
+
try:
|
|
1689
|
+
tree = ast.parse(source)
|
|
1690
|
+
except SyntaxError:
|
|
1691
|
+
return []
|
|
1692
|
+
starts = _line_starts(source)
|
|
1693
|
+
found = []
|
|
1694
|
+
for node in ast.walk(tree):
|
|
1695
|
+
if not isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
1696
|
+
continue
|
|
1697
|
+
for decorator in node.decorator_list:
|
|
1698
|
+
if not (isinstance(decorator, ast.Call) and decorator.args):
|
|
1699
|
+
continue
|
|
1700
|
+
func = decorator.func
|
|
1701
|
+
called = (
|
|
1702
|
+
func.attr
|
|
1703
|
+
if isinstance(func, ast.Attribute)
|
|
1704
|
+
else getattr(func, "id", "")
|
|
1705
|
+
)
|
|
1706
|
+
name = decorator.args[0]
|
|
1707
|
+
if not (
|
|
1708
|
+
called == "sql_macro"
|
|
1709
|
+
and isinstance(name, ast.Constant)
|
|
1710
|
+
and isinstance(name.value, str)
|
|
1711
|
+
and name.value.endswith(".sql")
|
|
1712
|
+
and name.end_lineno is not None
|
|
1713
|
+
and name.end_col_offset is not None
|
|
1714
|
+
):
|
|
1715
|
+
continue
|
|
1716
|
+
start = _char_offset(source, starts, name.lineno, name.col_offset)
|
|
1717
|
+
end = _char_offset(source, starts, name.end_lineno, name.end_col_offset)
|
|
1718
|
+
quote = source.find(name.value, start, end)
|
|
1719
|
+
if quote >= 0:
|
|
1720
|
+
found.append((quote, quote + len(name.value), name.value))
|
|
1721
|
+
return found
|
|
1722
|
+
|
|
1723
|
+
|
|
1647
1724
|
def _char_offset(source: str, starts: list[int], line: int, column: int) -> int:
|
|
1648
1725
|
"""Return the offset of a position `ast` gives, its column in UTF-8 bytes."""
|
|
1649
1726
|
begin = starts[line - 1]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|