opencode-sql-lsp-server 0.1.0__tar.gz → 0.1.1__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.
- opencode_sql_lsp_server-0.1.1/LICENSE +21 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/PKG-INFO +5 -3
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/pyproject.toml +8 -3
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server/__init__.py +1 -1
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server/cli.py +0 -1
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server/config.py +17 -3
- opencode_sql_lsp_server-0.1.1/src/opencode_sql_lsp_server/server.py +361 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/PKG-INFO +5 -3
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/SOURCES.txt +1 -0
- opencode_sql_lsp_server-0.1.0/src/opencode_sql_lsp_server/server.py +0 -108
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/README.md +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/setup.cfg +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server/sqlfluff_adapter.py +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/dependency_links.txt +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/entry_points.txt +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/requires.txt +0 -0
- {opencode_sql_lsp_server-0.1.0 → opencode_sql_lsp_server-0.1.1}/src/opencode_sql_lsp_server.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencode-sql-lsp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: SQL Language Server for OpenCode with Trino/StarRocks dialect support
|
|
5
5
|
Author: OpenCode SQL LSP port
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://opencode.ai
|
|
7
8
|
Keywords: opencode,lsp,sql,sqlfluff,trino,starrocks
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Environment :: Console
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
14
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
17
18
|
Requires-Dist: pygls>=1.3.1
|
|
18
19
|
Requires-Dist: lsprotocol>=2023.0.1
|
|
19
20
|
Requires-Dist: sqlfluff>=3.0.0
|
|
21
|
+
Dynamic: license-file
|
|
20
22
|
|
|
21
23
|
# opencode-sql-lsp-server
|
|
22
24
|
|
|
@@ -4,28 +4,33 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "opencode-sql-lsp-server"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "SQL Language Server for OpenCode with Trino/StarRocks dialect support"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
|
-
license =
|
|
11
|
+
license = "MIT"
|
|
12
12
|
authors = [{ name = "OpenCode SQL LSP port" }]
|
|
13
13
|
keywords = ["opencode", "lsp", "sql", "sqlfluff", "trino", "starrocks"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Environment :: Console",
|
|
17
17
|
"Intended Audience :: Developers",
|
|
18
|
-
"License :: OSI Approved :: MIT License",
|
|
19
18
|
"Programming Language :: Python :: 3",
|
|
20
19
|
"Programming Language :: Python :: 3 :: Only",
|
|
21
20
|
"Topic :: Software Development :: Quality Assurance",
|
|
22
21
|
]
|
|
22
|
+
|
|
23
|
+
license-files = ["LICENSE"]
|
|
24
|
+
|
|
23
25
|
dependencies = [
|
|
24
26
|
"pygls>=1.3.1",
|
|
25
27
|
"lsprotocol>=2023.0.1",
|
|
26
28
|
"sqlfluff>=3.0.0",
|
|
27
29
|
]
|
|
28
30
|
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://opencode.ai"
|
|
33
|
+
|
|
29
34
|
[project.scripts]
|
|
30
35
|
opencode-sql-lsp = "opencode_sql_lsp_server.cli:main"
|
|
31
36
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.1.
|
|
2
|
+
__version__ = "0.1.1"
|
|
@@ -7,17 +7,29 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class SqlLspConfigLoadError(RuntimeError):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
10
14
|
@dataclass(frozen=True)
|
|
11
15
|
class SqlLspConfig:
|
|
12
16
|
default_dialect: str
|
|
13
17
|
overrides: dict[str, str]
|
|
14
18
|
|
|
19
|
+
@staticmethod
|
|
20
|
+
def default() -> "SqlLspConfig":
|
|
21
|
+
return SqlLspConfig(default_dialect="starrocks", overrides={})
|
|
22
|
+
|
|
15
23
|
@staticmethod
|
|
16
24
|
def load(root: Path) -> "SqlLspConfig":
|
|
17
25
|
cfg_path = root / ".opencode" / "sql-lsp.json"
|
|
18
26
|
if not cfg_path.exists():
|
|
19
|
-
return SqlLspConfig(
|
|
20
|
-
|
|
27
|
+
return SqlLspConfig.default()
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
data: Any = json.loads(cfg_path.read_text(encoding="utf-8"))
|
|
31
|
+
except Exception as e:
|
|
32
|
+
raise SqlLspConfigLoadError(f"Failed to load config {cfg_path}: {e}") from e
|
|
21
33
|
default_dialect = data.get("defaultDialect")
|
|
22
34
|
if not isinstance(default_dialect, str) or not default_dialect.strip():
|
|
23
35
|
default_dialect = "starrocks"
|
|
@@ -35,7 +47,9 @@ class SqlLspConfig:
|
|
|
35
47
|
return SqlLspConfig(default_dialect=default_dialect, overrides=overrides)
|
|
36
48
|
|
|
37
49
|
def dialect_for_path(self, relative_path: str) -> str:
|
|
50
|
+
relative_path_posix = relative_path.replace("\\", "/")
|
|
38
51
|
for pattern, dialect in self.overrides.items():
|
|
39
|
-
|
|
52
|
+
pattern_posix = pattern.replace("\\", "/")
|
|
53
|
+
if fnmatch(relative_path_posix, pattern_posix):
|
|
40
54
|
return dialect
|
|
41
55
|
return self.default_dialect
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from lsprotocol.types import (
|
|
8
|
+
Diagnostic,
|
|
9
|
+
DiagnosticSeverity,
|
|
10
|
+
DocumentFormattingParams,
|
|
11
|
+
InitializeParams,
|
|
12
|
+
INITIALIZE,
|
|
13
|
+
PublishDiagnosticsParams,
|
|
14
|
+
TEXT_DOCUMENT_DID_CHANGE,
|
|
15
|
+
TEXT_DOCUMENT_DID_OPEN,
|
|
16
|
+
TEXT_DOCUMENT_DID_SAVE,
|
|
17
|
+
TEXT_DOCUMENT_FORMATTING,
|
|
18
|
+
Position,
|
|
19
|
+
Range,
|
|
20
|
+
TextEdit,
|
|
21
|
+
)
|
|
22
|
+
from pygls.lsp.server import LanguageServer
|
|
23
|
+
from pygls.uris import to_fs_path
|
|
24
|
+
|
|
25
|
+
from pygls.exceptions import PyglsError
|
|
26
|
+
|
|
27
|
+
from .config import SqlLspConfig
|
|
28
|
+
from .sqlfluff_adapter import format_sql, lint_issues
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
_DID_CHANGE_DEBOUNCE_S = 0.25
|
|
32
|
+
_MAX_LINT_LINES = 5_000
|
|
33
|
+
_MAX_LINT_BYTES = 200_000
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class _ConfigCacheEntry:
|
|
38
|
+
mtime_ns: int | None
|
|
39
|
+
config: SqlLspConfig
|
|
40
|
+
last_error_mtime_ns: int | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class _DocState:
|
|
45
|
+
version: int | None = None
|
|
46
|
+
pending_timer: asyncio.TimerHandle | None = None
|
|
47
|
+
pending_task: asyncio.Task[None] | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class OpenCodeSqlLanguageServer(LanguageServer):
|
|
51
|
+
def __init__(self) -> None:
|
|
52
|
+
super().__init__("opencode-sql-lsp", "0.1.1", max_workers=2)
|
|
53
|
+
self._workspace_roots: list[Path] = []
|
|
54
|
+
self._config_cache: dict[Path, _ConfigCacheEntry] = {}
|
|
55
|
+
self._doc_state: dict[str, _DocState] = {}
|
|
56
|
+
|
|
57
|
+
def set_workspace_root(self, root: str | None) -> None:
|
|
58
|
+
self.set_workspace_roots([root] if root else [])
|
|
59
|
+
|
|
60
|
+
def set_workspace_roots(self, roots: list[str]) -> None:
|
|
61
|
+
self._workspace_roots = [Path(r).resolve() for r in roots if r]
|
|
62
|
+
self._config_cache.clear()
|
|
63
|
+
|
|
64
|
+
def _best_root_for_uri(self, doc_uri: str) -> Path | None:
|
|
65
|
+
if not self._workspace_roots:
|
|
66
|
+
return None
|
|
67
|
+
try:
|
|
68
|
+
fs_path = to_fs_path(doc_uri)
|
|
69
|
+
if not fs_path:
|
|
70
|
+
return None
|
|
71
|
+
p = Path(fs_path).resolve()
|
|
72
|
+
except Exception:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
best: Path | None = None
|
|
76
|
+
best_len = -1
|
|
77
|
+
for r in self._workspace_roots:
|
|
78
|
+
try:
|
|
79
|
+
p.relative_to(r)
|
|
80
|
+
except Exception:
|
|
81
|
+
continue
|
|
82
|
+
l = len(str(r))
|
|
83
|
+
if l > best_len:
|
|
84
|
+
best = r
|
|
85
|
+
best_len = l
|
|
86
|
+
return best
|
|
87
|
+
|
|
88
|
+
def _config_for_root(self, root: Path) -> SqlLspConfig:
|
|
89
|
+
cfg_path = root / ".opencode" / "sql-lsp.json"
|
|
90
|
+
try:
|
|
91
|
+
st = cfg_path.stat()
|
|
92
|
+
mtime_ns: int | None = st.st_mtime_ns
|
|
93
|
+
except FileNotFoundError:
|
|
94
|
+
mtime_ns = None
|
|
95
|
+
except Exception as e:
|
|
96
|
+
self.report_server_error(e, PyglsError)
|
|
97
|
+
return SqlLspConfig.default()
|
|
98
|
+
|
|
99
|
+
cached = self._config_cache.get(root)
|
|
100
|
+
if cached and cached.mtime_ns == mtime_ns:
|
|
101
|
+
return cached.config
|
|
102
|
+
|
|
103
|
+
if mtime_ns is None:
|
|
104
|
+
cfg = SqlLspConfig.default()
|
|
105
|
+
self._config_cache[root] = _ConfigCacheEntry(mtime_ns=None, config=cfg)
|
|
106
|
+
return cfg
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
cfg = SqlLspConfig.load(root)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
if cached and cached.config:
|
|
112
|
+
if cached.last_error_mtime_ns != mtime_ns:
|
|
113
|
+
cached.last_error_mtime_ns = mtime_ns
|
|
114
|
+
self.report_server_error(e, PyglsError)
|
|
115
|
+
return cached.config
|
|
116
|
+
self.report_server_error(e, PyglsError)
|
|
117
|
+
cfg = SqlLspConfig.default()
|
|
118
|
+
|
|
119
|
+
self._config_cache[root] = _ConfigCacheEntry(mtime_ns=mtime_ns, config=cfg)
|
|
120
|
+
return cfg
|
|
121
|
+
|
|
122
|
+
def dialect_for_document(self, doc_uri: str) -> str:
|
|
123
|
+
root = self._best_root_for_uri(doc_uri)
|
|
124
|
+
if not root:
|
|
125
|
+
return SqlLspConfig.default().default_dialect
|
|
126
|
+
cfg = self._config_for_root(root)
|
|
127
|
+
try:
|
|
128
|
+
fs_path = to_fs_path(doc_uri)
|
|
129
|
+
if not fs_path:
|
|
130
|
+
return cfg.default_dialect
|
|
131
|
+
p = Path(fs_path).resolve()
|
|
132
|
+
rel = p.relative_to(root)
|
|
133
|
+
return cfg.dialect_for_path(str(rel))
|
|
134
|
+
except Exception:
|
|
135
|
+
return cfg.default_dialect
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
server = OpenCodeSqlLanguageServer()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@server.feature(INITIALIZE)
|
|
142
|
+
def initialize(ls: OpenCodeSqlLanguageServer, params: InitializeParams):
|
|
143
|
+
roots: list[str] = []
|
|
144
|
+
try:
|
|
145
|
+
wf = getattr(params, "workspace_folders", None)
|
|
146
|
+
if isinstance(wf, list):
|
|
147
|
+
for f in wf:
|
|
148
|
+
uri = getattr(f, "uri", None)
|
|
149
|
+
if isinstance(uri, str) and uri:
|
|
150
|
+
try:
|
|
151
|
+
fs_path = to_fs_path(uri)
|
|
152
|
+
if fs_path:
|
|
153
|
+
roots.append(fs_path)
|
|
154
|
+
except Exception:
|
|
155
|
+
continue
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
root_uri = getattr(params, "root_uri", None)
|
|
160
|
+
if isinstance(root_uri, str) and root_uri:
|
|
161
|
+
try:
|
|
162
|
+
fs_path = to_fs_path(root_uri)
|
|
163
|
+
if fs_path:
|
|
164
|
+
roots.append(fs_path)
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
deduped: list[str] = []
|
|
169
|
+
seen: set[str] = set()
|
|
170
|
+
for r in roots:
|
|
171
|
+
if r not in seen:
|
|
172
|
+
seen.add(r)
|
|
173
|
+
deduped.append(r)
|
|
174
|
+
ls.set_workspace_roots(deduped)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _is_large_document(source: str) -> bool:
|
|
178
|
+
if len(source.encode("utf-8", errors="ignore")) > _MAX_LINT_BYTES:
|
|
179
|
+
return True
|
|
180
|
+
return source.count("\n") > _MAX_LINT_LINES
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _publish_skipped_diagnostics(ls: OpenCodeSqlLanguageServer, uri: str) -> None:
|
|
184
|
+
ls.text_document_publish_diagnostics(
|
|
185
|
+
PublishDiagnosticsParams(
|
|
186
|
+
uri=uri,
|
|
187
|
+
diagnostics=[
|
|
188
|
+
Diagnostic(
|
|
189
|
+
range=Range(
|
|
190
|
+
start=Position(line=0, character=0),
|
|
191
|
+
end=Position(line=0, character=0),
|
|
192
|
+
),
|
|
193
|
+
message="Lint skipped (file too large)",
|
|
194
|
+
severity=DiagnosticSeverity.Warning,
|
|
195
|
+
source="opencode-sql-lsp",
|
|
196
|
+
)
|
|
197
|
+
],
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _safe_position(doc, line_1: int, character: int) -> Position:
|
|
203
|
+
if not getattr(doc, "lines", None):
|
|
204
|
+
return Position(line=0, character=0)
|
|
205
|
+
max_line = max(0, len(doc.lines) - 1)
|
|
206
|
+
line_idx = min(max(0, line_1 - 1), max_line)
|
|
207
|
+
line_text = doc.lines[line_idx] if doc.lines else ""
|
|
208
|
+
char_idx = min(max(0, character), len(line_text))
|
|
209
|
+
return Position(line=line_idx, character=char_idx)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _issue_range(doc, issue) -> Range:
|
|
213
|
+
start = _safe_position(doc, issue.line, issue.character)
|
|
214
|
+
end_char = start.character + 1
|
|
215
|
+
if getattr(doc, "lines", None):
|
|
216
|
+
line_text = doc.lines[start.line]
|
|
217
|
+
end_char = min(end_char, len(line_text))
|
|
218
|
+
end = Position(line=start.line, character=end_char)
|
|
219
|
+
return Range(start=start, end=end)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
async def _run_lint_and_publish(
|
|
223
|
+
ls: OpenCodeSqlLanguageServer,
|
|
224
|
+
uri: str,
|
|
225
|
+
expected_version: int | None,
|
|
226
|
+
) -> None:
|
|
227
|
+
state = ls._doc_state.setdefault(uri, _DocState())
|
|
228
|
+
try:
|
|
229
|
+
doc = ls.workspace.get_text_document(uri)
|
|
230
|
+
except Exception as e:
|
|
231
|
+
ls.report_server_error(e, PyglsError)
|
|
232
|
+
return
|
|
233
|
+
|
|
234
|
+
source = doc.source
|
|
235
|
+
dialect = ls.dialect_for_document(uri)
|
|
236
|
+
loop = asyncio.get_running_loop()
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
issues = await loop.run_in_executor(
|
|
240
|
+
ls.thread_pool, lambda: lint_issues(source, dialect=dialect)
|
|
241
|
+
)
|
|
242
|
+
except Exception as e:
|
|
243
|
+
ls.report_server_error(e, PyglsError)
|
|
244
|
+
issues = []
|
|
245
|
+
|
|
246
|
+
if expected_version is not None and state.version != expected_version:
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
diagnostics: list[Diagnostic] = []
|
|
250
|
+
for issue in issues:
|
|
251
|
+
diagnostics.append(
|
|
252
|
+
Diagnostic(
|
|
253
|
+
range=_issue_range(doc, issue),
|
|
254
|
+
message=issue.message,
|
|
255
|
+
severity=DiagnosticSeverity.Error,
|
|
256
|
+
source="sqlfluff",
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
ls.text_document_publish_diagnostics(
|
|
261
|
+
PublishDiagnosticsParams(uri=uri, diagnostics=diagnostics)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _schedule_diagnostics(
|
|
266
|
+
ls: OpenCodeSqlLanguageServer,
|
|
267
|
+
uri: str,
|
|
268
|
+
version: int | None,
|
|
269
|
+
*,
|
|
270
|
+
debounce_s: float,
|
|
271
|
+
) -> None:
|
|
272
|
+
state = ls._doc_state.setdefault(uri, _DocState())
|
|
273
|
+
state.version = version
|
|
274
|
+
|
|
275
|
+
if state.pending_timer is not None:
|
|
276
|
+
state.pending_timer.cancel()
|
|
277
|
+
state.pending_timer = None
|
|
278
|
+
|
|
279
|
+
if state.pending_task is not None and not state.pending_task.done():
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
loop = asyncio.get_running_loop()
|
|
283
|
+
|
|
284
|
+
def kickoff() -> None:
|
|
285
|
+
state.pending_timer = None
|
|
286
|
+
state.pending_task = asyncio.create_task(
|
|
287
|
+
_run_lint_and_publish(ls, uri, expected_version=version)
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
if debounce_s <= 0:
|
|
291
|
+
kickoff()
|
|
292
|
+
else:
|
|
293
|
+
state.pending_timer = loop.call_later(debounce_s, kickoff)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@server.feature(TEXT_DOCUMENT_DID_OPEN)
|
|
297
|
+
def did_open(ls: OpenCodeSqlLanguageServer, params):
|
|
298
|
+
uri = params.text_document.uri
|
|
299
|
+
version = getattr(params.text_document, "version", None)
|
|
300
|
+
try:
|
|
301
|
+
doc = ls.workspace.get_text_document(uri)
|
|
302
|
+
except Exception as e:
|
|
303
|
+
ls.report_server_error(e, PyglsError)
|
|
304
|
+
else:
|
|
305
|
+
if _is_large_document(doc.source):
|
|
306
|
+
_publish_skipped_diagnostics(ls, uri)
|
|
307
|
+
return
|
|
308
|
+
_schedule_diagnostics(ls, uri, version, debounce_s=0.0)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@server.feature(TEXT_DOCUMENT_DID_CHANGE)
|
|
312
|
+
def did_change(ls: OpenCodeSqlLanguageServer, params):
|
|
313
|
+
uri = params.text_document.uri
|
|
314
|
+
version = getattr(params.text_document, "version", None)
|
|
315
|
+
try:
|
|
316
|
+
doc = ls.workspace.get_text_document(uri)
|
|
317
|
+
except Exception as e:
|
|
318
|
+
ls.report_server_error(e, PyglsError)
|
|
319
|
+
else:
|
|
320
|
+
if _is_large_document(doc.source):
|
|
321
|
+
_publish_skipped_diagnostics(ls, uri)
|
|
322
|
+
return
|
|
323
|
+
_schedule_diagnostics(ls, uri, version, debounce_s=_DID_CHANGE_DEBOUNCE_S)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@server.feature(TEXT_DOCUMENT_DID_SAVE)
|
|
327
|
+
def did_save(ls: OpenCodeSqlLanguageServer, params):
|
|
328
|
+
uri = params.text_document.uri
|
|
329
|
+
version = getattr(params.text_document, "version", None)
|
|
330
|
+
try:
|
|
331
|
+
doc = ls.workspace.get_text_document(uri)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
ls.report_server_error(e, PyglsError)
|
|
334
|
+
else:
|
|
335
|
+
if _is_large_document(doc.source):
|
|
336
|
+
_publish_skipped_diagnostics(ls, uri)
|
|
337
|
+
return
|
|
338
|
+
_schedule_diagnostics(ls, uri, version, debounce_s=0.0)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
@server.feature(TEXT_DOCUMENT_FORMATTING)
|
|
342
|
+
def formatting(ls: OpenCodeSqlLanguageServer, params: DocumentFormattingParams):
|
|
343
|
+
uri = params.text_document.uri
|
|
344
|
+
try:
|
|
345
|
+
doc = ls.workspace.get_text_document(uri)
|
|
346
|
+
except Exception as e:
|
|
347
|
+
ls.report_server_error(e, PyglsError)
|
|
348
|
+
return []
|
|
349
|
+
|
|
350
|
+
dialect = ls.dialect_for_document(uri)
|
|
351
|
+
try:
|
|
352
|
+
formatted = format_sql(doc.source, dialect=dialect)
|
|
353
|
+
except Exception:
|
|
354
|
+
return []
|
|
355
|
+
last_line = max(0, len(doc.lines) - 1)
|
|
356
|
+
last_char = len(doc.lines[last_line]) if doc.lines else 0
|
|
357
|
+
edit_range = Range(
|
|
358
|
+
start=Position(line=0, character=0),
|
|
359
|
+
end=Position(line=last_line, character=last_char),
|
|
360
|
+
)
|
|
361
|
+
return [TextEdit(range=edit_range, new_text=formatted)]
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencode-sql-lsp-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: SQL Language Server for OpenCode with Trino/StarRocks dialect support
|
|
5
5
|
Author: OpenCode SQL LSP port
|
|
6
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://opencode.ai
|
|
7
8
|
Keywords: opencode,lsp,sql,sqlfluff,trino,starrocks
|
|
8
9
|
Classifier: Development Status :: 3 - Alpha
|
|
9
10
|
Classifier: Environment :: Console
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
14
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
17
18
|
Requires-Dist: pygls>=1.3.1
|
|
18
19
|
Requires-Dist: lsprotocol>=2023.0.1
|
|
19
20
|
Requires-Dist: sqlfluff>=3.0.0
|
|
21
|
+
Dynamic: license-file
|
|
20
22
|
|
|
21
23
|
# opencode-sql-lsp-server
|
|
22
24
|
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
5
|
-
|
|
6
|
-
from lsprotocol.types import (
|
|
7
|
-
Diagnostic,
|
|
8
|
-
DiagnosticSeverity,
|
|
9
|
-
DocumentFormattingParams,
|
|
10
|
-
InitializeParams,
|
|
11
|
-
INITIALIZE,
|
|
12
|
-
PublishDiagnosticsParams,
|
|
13
|
-
TEXT_DOCUMENT_DID_CHANGE,
|
|
14
|
-
TEXT_DOCUMENT_DID_OPEN,
|
|
15
|
-
TEXT_DOCUMENT_FORMATTING,
|
|
16
|
-
Position,
|
|
17
|
-
Range,
|
|
18
|
-
TextEdit,
|
|
19
|
-
)
|
|
20
|
-
from pygls.lsp.server import LanguageServer
|
|
21
|
-
|
|
22
|
-
from .config import SqlLspConfig
|
|
23
|
-
from .sqlfluff_adapter import format_sql, lint_issues
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class OpenCodeSqlLanguageServer(LanguageServer):
|
|
27
|
-
def __init__(self) -> None:
|
|
28
|
-
super().__init__("opencode-sql-lsp", "0.1.0")
|
|
29
|
-
self._workspace_root: Optional[Path] = None
|
|
30
|
-
self._config: Optional[SqlLspConfig] = None
|
|
31
|
-
|
|
32
|
-
def set_workspace_root(self, root: Optional[str]) -> None:
|
|
33
|
-
self._workspace_root = Path(root).resolve() if root else None
|
|
34
|
-
self._config = (
|
|
35
|
-
SqlLspConfig.load(self._workspace_root) if self._workspace_root else None
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def dialect_for_document(self, doc_uri: str) -> str:
|
|
39
|
-
if not self._workspace_root or not self._config:
|
|
40
|
-
return "starrocks"
|
|
41
|
-
try:
|
|
42
|
-
p = Path(self.uri_to_path(doc_uri)).resolve()
|
|
43
|
-
rel = p.relative_to(self._workspace_root)
|
|
44
|
-
return self._config.dialect_for_path(str(rel))
|
|
45
|
-
except Exception:
|
|
46
|
-
return self._config.default_dialect
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
server = OpenCodeSqlLanguageServer()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@server.feature(INITIALIZE)
|
|
53
|
-
def initialize(ls: OpenCodeSqlLanguageServer, params: InitializeParams):
|
|
54
|
-
root_uri = getattr(params, "root_uri", None)
|
|
55
|
-
if isinstance(root_uri, str) and root_uri:
|
|
56
|
-
try:
|
|
57
|
-
ls.set_workspace_root(ls.uri_to_path(root_uri))
|
|
58
|
-
except Exception:
|
|
59
|
-
pass
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def _publish_diagnostics(ls: OpenCodeSqlLanguageServer, uri: str) -> None:
|
|
63
|
-
doc = ls.workspace.get_text_document(uri)
|
|
64
|
-
dialect = ls.dialect_for_document(uri)
|
|
65
|
-
issues = lint_issues(doc.source, dialect=dialect)
|
|
66
|
-
diagnostics: list[Diagnostic] = []
|
|
67
|
-
for issue in issues:
|
|
68
|
-
start = Position(line=issue.line - 1, character=issue.character)
|
|
69
|
-
end = Position(line=issue.line - 1, character=issue.character + 1)
|
|
70
|
-
diagnostics.append(
|
|
71
|
-
Diagnostic(
|
|
72
|
-
range=Range(start=start, end=end),
|
|
73
|
-
message=issue.message,
|
|
74
|
-
severity=DiagnosticSeverity.Error,
|
|
75
|
-
source="sqlglot",
|
|
76
|
-
)
|
|
77
|
-
)
|
|
78
|
-
ls.text_document_publish_diagnostics(
|
|
79
|
-
PublishDiagnosticsParams(uri=uri, diagnostics=diagnostics)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@server.feature(TEXT_DOCUMENT_DID_OPEN)
|
|
84
|
-
def did_open(ls: OpenCodeSqlLanguageServer, params):
|
|
85
|
-
_publish_diagnostics(ls, params.text_document.uri)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@server.feature(TEXT_DOCUMENT_DID_CHANGE)
|
|
89
|
-
def did_change(ls: OpenCodeSqlLanguageServer, params):
|
|
90
|
-
_publish_diagnostics(ls, params.text_document.uri)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
@server.feature(TEXT_DOCUMENT_FORMATTING)
|
|
94
|
-
def formatting(ls: OpenCodeSqlLanguageServer, params: DocumentFormattingParams):
|
|
95
|
-
doc = ls.workspace.get_text_document(params.text_document.uri)
|
|
96
|
-
dialect = ls.dialect_for_document(params.text_document.uri)
|
|
97
|
-
try:
|
|
98
|
-
formatted = format_sql(doc.source, dialect=dialect)
|
|
99
|
-
except Exception:
|
|
100
|
-
return []
|
|
101
|
-
# Replace entire document
|
|
102
|
-
last_line = max(0, len(doc.lines) - 1)
|
|
103
|
-
last_char = len(doc.lines[last_line]) if doc.lines else 0
|
|
104
|
-
edit_range = Range(
|
|
105
|
-
start=Position(line=0, character=0),
|
|
106
|
-
end=Position(line=last_line, character=last_char),
|
|
107
|
-
)
|
|
108
|
-
return [TextEdit(range=edit_range, new_text=formatted)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|