Skip to content

Commit e648648

Browse files
committed
feat(lsp): include model descriptions in completions
1 parent 7e252f8 commit e648648

File tree

5 files changed

+158
-55
lines changed

5 files changed

+158
-55
lines changed

sqlmesh/lsp/completions.py

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from sqlmesh import macro
55
import typing as t
66
from sqlmesh.lsp.context import AuditTarget, LSPContext, ModelTarget
7+
from sqlmesh.lsp.custom import ModelCompletion
8+
from sqlmesh.lsp.description import generate_markdown_description
79
from sqlmesh.lsp.uri import URI
810

911

@@ -21,7 +23,9 @@ def get_sql_completions(
2123
# Get keywords from file content if provided
2224
file_keywords = set()
2325
if content:
24-
file_keywords = extract_keywords_from_content(content, get_dialect(context, file_uri))
26+
file_keywords = extract_keywords_from_content(
27+
content, get_dialect(context, file_uri)
28+
)
2529

2630
# Combine keywords - SQL keywords first, then file keywords
2731
all_keywords = list(sql_keywords) + list(file_keywords - sql_keywords)
@@ -33,31 +37,33 @@ def get_sql_completions(
3337
)
3438

3539

36-
def get_models(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) -> t.Set[str]:
40+
def get_models(
41+
context: t.Optional[LSPContext], file_uri: t.Optional[URI]
42+
) -> t.List[ModelCompletion]:
3743
"""
3844
Return a list of models for a given file.
3945
4046
If there is no context, return an empty list.
4147
If there is a context, return a list of all models bar the ones the file itself defines.
4248
"""
4349
if context is None:
44-
return set()
50+
return []
4551

46-
all_models = set()
47-
# Extract model names from ModelInfo objects
48-
for file_info in context.map.values():
49-
if isinstance(file_info, ModelTarget):
50-
all_models.update(file_info.names)
52+
current_path = file_uri.to_path() if file_uri is not None else None
5153

52-
# Remove models from the current file
53-
path = file_uri.to_path() if file_uri is not None else None
54-
if path is not None and path in context.map:
55-
file_info = context.map[path]
56-
if isinstance(file_info, ModelTarget):
57-
for model in file_info.names:
58-
all_models.discard(model)
54+
completions: t.List[ModelCompletion] = []
55+
for model in context.context.models.values():
56+
if current_path is not None and model._path == current_path:
57+
continue
58+
description = None
59+
try:
60+
description = generate_markdown_description(model)
61+
except Exception:
62+
description = getattr(model, "description", None)
5963

60-
return all_models
64+
completions.append(ModelCompletion(name=model.name, description=description))
65+
66+
return completions
6167

6268

6369
def get_macros(
@@ -79,7 +85,9 @@ def get_macros(
7985
return [MacroCompletion(name=name, description=doc) for name, doc in macros.items()]
8086

8187

82-
def get_keywords(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) -> t.Set[str]:
88+
def get_keywords(
89+
context: t.Optional[LSPContext], file_uri: t.Optional[URI]
90+
) -> t.Set[str]:
8391
"""
8492
Return a list of sql keywords for a given file.
8593
If no context is provided, return ANSI SQL keywords.
@@ -90,7 +98,11 @@ def get_keywords(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) ->
9098
If both a context and a file_uri are provided, returns the keywords
9199
for the dialect of the model that the file belongs to.
92100
"""
93-
if file_uri is not None and context is not None and file_uri.to_path() in context.map:
101+
if (
102+
file_uri is not None
103+
and context is not None
104+
and file_uri.to_path() in context.map
105+
):
94106
file_info = context.map[file_uri.to_path()]
95107

96108
# Handle ModelInfo objects
@@ -133,11 +145,17 @@ def get_keywords_from_tokenizer(dialect: t.Optional[str] = None) -> t.Set[str]:
133145
return expanded_keywords
134146

135147

136-
def get_dialect(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) -> t.Optional[str]:
148+
def get_dialect(
149+
context: t.Optional[LSPContext], file_uri: t.Optional[URI]
150+
) -> t.Optional[str]:
137151
"""
138152
Get the dialect for a given file.
139153
"""
140-
if file_uri is not None and context is not None and file_uri.to_path() in context.map:
154+
if (
155+
file_uri is not None
156+
and context is not None
157+
and file_uri.to_path() in context.map
158+
):
141159
file_info = context.map[file_uri.to_path()]
142160

143161
# Handle ModelInfo objects
@@ -158,7 +176,9 @@ def get_dialect(context: t.Optional[LSPContext], file_uri: t.Optional[URI]) -> t
158176
return None
159177

160178

161-
def extract_keywords_from_content(content: str, dialect: t.Optional[str] = None) -> t.Set[str]:
179+
def extract_keywords_from_content(
180+
content: str, dialect: t.Optional[str] = None
181+
) -> t.Set[str]:
162182
"""
163183
Extract identifiers from SQL content using the tokenizer.
164184

sqlmesh/lsp/custom.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,19 @@ class MacroCompletion(PydanticModel):
3030
description: t.Optional[str] = None
3131

3232

33+
class ModelCompletion(PydanticModel):
34+
"""Information about a model for autocompletion."""
35+
36+
name: str
37+
description: t.Optional[str] = None
38+
39+
3340
class AllModelsResponse(CustomMethodResponseBaseClass):
3441
"""
3542
Response to get all the models that are in the current project.
3643
"""
3744

38-
models: t.List[str]
45+
models: t.List[ModelCompletion]
3946
keywords: t.List[str]
4047
macros: t.List[MacroCompletion]
4148

0 commit comments

Comments
 (0)