avrae-ls 0.6.4__py3-none-any.whl → 0.7.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.
avrae_ls/server.py CHANGED
@@ -17,12 +17,12 @@ from .context import ContextBuilder
17
17
  from .diagnostics import DiagnosticProvider
18
18
  from .runtime import MockExecutor
19
19
  from .alias_preview import render_alias_command, simulate_command
20
- from .parser import find_draconic_blocks
20
+ from .parser import is_alias_module_path
21
+ from .source_context import build_source_context, block_for_line
21
22
  from .signature_help import load_signatures, signature_help_for_code
22
23
  from .completions import gather_suggestions, completion_items_for_position, hover_for_position
23
24
  from .code_actions import code_actions_for_document
24
25
  from .symbols import build_symbol_table, document_symbols, find_definition_range, find_references, range_for_word
25
- from .argument_parsing import apply_argument_parsing
26
26
 
27
27
  # Prefer package metadata so the server version matches the installed wheel.
28
28
  try:
@@ -134,14 +134,14 @@ async def did_change_config(server: AvraeLanguageServer, params: types.DidChange
134
134
  @ls.feature(types.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
135
135
  def on_document_symbol(server: AvraeLanguageServer, params: types.DocumentSymbolParams):
136
136
  doc = server.workspace.get_text_document(params.text_document.uri)
137
- symbols = document_symbols(doc.source)
137
+ symbols = document_symbols(doc.source, treat_as_module=_is_alias_module_document(doc))
138
138
  return symbols
139
139
 
140
140
 
141
141
  @ls.feature(types.TEXT_DOCUMENT_DEFINITION)
142
142
  def on_definition(server: AvraeLanguageServer, params: types.DefinitionParams):
143
143
  doc = server.workspace.get_text_document(params.text_document.uri)
144
- table = build_symbol_table(doc.source)
144
+ table = build_symbol_table(doc.source, treat_as_module=_is_alias_module_document(doc))
145
145
  word = doc.word_at_position(params.position)
146
146
  rng = find_definition_range(table, word)
147
147
  if rng is None:
@@ -152,19 +152,26 @@ def on_definition(server: AvraeLanguageServer, params: types.DefinitionParams):
152
152
  @ls.feature(types.TEXT_DOCUMENT_REFERENCES)
153
153
  def on_references(server: AvraeLanguageServer, params: types.ReferenceParams):
154
154
  doc = server.workspace.get_text_document(params.text_document.uri)
155
- table = build_symbol_table(doc.source)
155
+ is_module = _is_alias_module_document(doc)
156
+ table = build_symbol_table(doc.source, treat_as_module=is_module)
156
157
  word = doc.word_at_position(params.position)
157
158
  if not word or not table.lookup(word):
158
159
  return []
159
160
 
160
- ranges = find_references(table, doc.source, word, include_declaration=params.context.include_declaration)
161
+ ranges = find_references(
162
+ table,
163
+ doc.source,
164
+ word,
165
+ include_declaration=params.context.include_declaration,
166
+ treat_as_module=is_module,
167
+ )
161
168
  return [types.Location(uri=params.text_document.uri, range=rng) for rng in ranges]
162
169
 
163
170
 
164
171
  @ls.feature(types.TEXT_DOCUMENT_PREPARE_RENAME)
165
172
  def on_prepare_rename(server: AvraeLanguageServer, params: types.PrepareRenameParams):
166
173
  doc = server.workspace.get_text_document(params.text_document.uri)
167
- table = build_symbol_table(doc.source)
174
+ table = build_symbol_table(doc.source, treat_as_module=_is_alias_module_document(doc))
168
175
  word = doc.word_at_position(params.position)
169
176
  if not word or not table.lookup(word):
170
177
  return None
@@ -174,12 +181,13 @@ def on_prepare_rename(server: AvraeLanguageServer, params: types.PrepareRenamePa
174
181
  @ls.feature(types.TEXT_DOCUMENT_RENAME)
175
182
  def on_rename(server: AvraeLanguageServer, params: types.RenameParams):
176
183
  doc = server.workspace.get_text_document(params.text_document.uri)
177
- table = build_symbol_table(doc.source)
184
+ is_module = _is_alias_module_document(doc)
185
+ table = build_symbol_table(doc.source, treat_as_module=is_module)
178
186
  word = doc.word_at_position(params.position)
179
187
  if not word or not table.lookup(word) or not params.new_name:
180
188
  return None
181
189
 
182
- ranges = find_references(table, doc.source, word, include_declaration=True)
190
+ ranges = find_references(table, doc.source, word, include_declaration=True, treat_as_module=is_module)
183
191
  if not ranges:
184
192
  return None
185
193
  edits = [types.TextEdit(range=rng, new_text=params.new_name) for rng in ranges]
@@ -191,7 +199,7 @@ def on_workspace_symbol(server: AvraeLanguageServer, params: types.WorkspaceSymb
191
199
  symbols: list[types.SymbolInformation] = []
192
200
  query = (params.query or "").lower()
193
201
  for uri, doc in server.workspace.text_documents.items():
194
- table = build_symbol_table(doc.source)
202
+ table = build_symbol_table(doc.source, treat_as_module=_is_alias_module_document(doc))
195
203
  for entry in table.entries:
196
204
  if query and query not in entry.name.lower():
197
205
  continue
@@ -208,20 +216,18 @@ def on_workspace_symbol(server: AvraeLanguageServer, params: types.WorkspaceSymb
208
216
  @ls.feature(types.TEXT_DOCUMENT_SIGNATURE_HELP)
209
217
  def on_signature_help(server: AvraeLanguageServer, params: types.SignatureHelpParams):
210
218
  doc = server.workspace.get_text_document(params.text_document.uri)
211
- source = apply_argument_parsing(doc.source)
212
- blocks = find_draconic_blocks(source)
219
+ is_module = _is_alias_module_document(doc)
220
+ source_ctx = build_source_context(doc.source, is_module)
213
221
  pos = params.position
214
- if not blocks:
215
- return signature_help_for_code(source, pos.line, pos.character, server._signatures)
216
-
217
- for block in blocks:
218
- start = block.line_offset
219
- end = block.line_offset + block.line_count
220
- if start <= pos.line <= end:
221
- rel_line = pos.line - start
222
- help_ = signature_help_for_code(block.code, rel_line, pos.character, server._signatures)
223
- if help_:
224
- return help_
222
+ if not source_ctx.blocks:
223
+ return signature_help_for_code(source_ctx.prepared, pos.line, pos.character, server._signatures)
224
+
225
+ block = block_for_line(source_ctx.blocks, pos.line)
226
+ if block:
227
+ rel_line = pos.line - block.line_offset
228
+ help_ = signature_help_for_code(block.code, rel_line, pos.character, server._signatures)
229
+ if help_:
230
+ return help_
225
231
  return None
226
232
 
227
233
 
@@ -233,18 +239,16 @@ def on_completion(server: AvraeLanguageServer, params: types.CompletionParams):
233
239
  doc = server.workspace.get_text_document(params.text_document.uri)
234
240
  ctx_data = server.state.context_builder.build()
235
241
  suggestions = gather_suggestions(ctx_data, server.state.context_builder.gvar_resolver, server._signatures)
236
- source = apply_argument_parsing(doc.source)
237
- blocks = find_draconic_blocks(source)
242
+ is_module = _is_alias_module_document(doc)
243
+ source_ctx = build_source_context(doc.source, is_module)
238
244
  pos = params.position
239
- if not blocks:
240
- return completion_items_for_position(source, pos.line, pos.character, suggestions)
241
-
242
- for block in blocks:
243
- start = block.line_offset
244
- end = block.line_offset + block.line_count
245
- if start <= pos.line <= end:
246
- rel_line = pos.line - start
247
- return completion_items_for_position(block.code, rel_line, pos.character, suggestions)
245
+ if not source_ctx.blocks:
246
+ return completion_items_for_position(source_ctx.prepared, pos.line, pos.character, suggestions)
247
+
248
+ block = block_for_line(source_ctx.blocks, pos.line)
249
+ if block:
250
+ rel_line = pos.line - block.line_offset
251
+ return completion_items_for_position(block.code, rel_line, pos.character, suggestions)
248
252
  return []
249
253
 
250
254
 
@@ -253,24 +257,41 @@ def on_hover(server: AvraeLanguageServer, params: types.HoverParams):
253
257
  doc = server.workspace.get_text_document(params.text_document.uri)
254
258
  ctx_data = server.state.context_builder.build()
255
259
  pos = params.position
256
- source = apply_argument_parsing(doc.source)
257
- blocks = find_draconic_blocks(source)
258
- if not blocks:
259
- return hover_for_position(source, pos.line, pos.character, server._signatures, ctx_data, server.state.context_builder.gvar_resolver)
260
-
261
- for block in blocks:
262
- start = block.line_offset
263
- end = block.line_offset + block.line_count
264
- if start <= pos.line <= end:
265
- rel_line = pos.line - start
266
- return hover_for_position(block.code, rel_line, pos.character, server._signatures, ctx_data, server.state.context_builder.gvar_resolver)
260
+ is_module = _is_alias_module_document(doc)
261
+ source_ctx = build_source_context(doc.source, is_module)
262
+ if not source_ctx.blocks:
263
+ return hover_for_position(
264
+ source_ctx.prepared,
265
+ pos.line,
266
+ pos.character,
267
+ server._signatures,
268
+ ctx_data,
269
+ server.state.context_builder.gvar_resolver,
270
+ )
271
+
272
+ block = block_for_line(source_ctx.blocks, pos.line)
273
+ if block:
274
+ rel_line = pos.line - block.line_offset
275
+ return hover_for_position(
276
+ block.code,
277
+ rel_line,
278
+ pos.character,
279
+ server._signatures,
280
+ ctx_data,
281
+ server.state.context_builder.gvar_resolver,
282
+ )
267
283
  return None
268
284
 
269
285
 
270
286
  @ls.feature(types.TEXT_DOCUMENT_CODE_ACTION)
271
287
  def on_code_action(server: AvraeLanguageServer, params: types.CodeActionParams):
272
288
  doc = server.workspace.get_text_document(params.text_document.uri)
273
- return code_actions_for_document(doc.source, params, server.workspace_root)
289
+ return code_actions_for_document(
290
+ doc.source,
291
+ params,
292
+ server.workspace_root,
293
+ treat_as_module=_is_alias_module_document(doc),
294
+ )
274
295
 
275
296
 
276
297
  @ls.command(RUN_ALIAS_COMMAND)
@@ -372,7 +393,10 @@ async def _publish_diagnostics(
372
393
  doc = server.workspace.get_text_document(uri)
373
394
  ctx_data = server.state.context_builder.build(profile)
374
395
  diags = await server.state.diagnostics.analyze(
375
- doc.source, ctx_data, server.state.context_builder.gvar_resolver
396
+ doc.source,
397
+ ctx_data,
398
+ server.state.context_builder.gvar_resolver,
399
+ treat_as_module=_is_alias_module_document(doc),
376
400
  )
377
401
  if extra:
378
402
  diags.extend(extra)
@@ -443,5 +467,19 @@ def _find_using_range(source: str, module: str | None) -> types.Range | None:
443
467
  return None
444
468
 
445
469
 
470
+ def _is_alias_module_document(doc: Any) -> bool:
471
+ language_id = getattr(doc, "language_id", None)
472
+ if language_id == "avrae-module":
473
+ return True
474
+ uri = getattr(doc, "uri", None)
475
+ if not isinstance(uri, str):
476
+ return False
477
+ try:
478
+ path = Path(uris.to_fs_path(uri))
479
+ except Exception:
480
+ return uri.endswith(".alias-module")
481
+ return is_alias_module_path(path)
482
+
483
+
446
484
  def create_server() -> AvraeLanguageServer:
447
485
  return ls
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Sequence
5
+
6
+ from .argument_parsing import apply_argument_parsing
7
+ from .parser import DraconicBlock, find_draconic_blocks
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class SourceContext:
12
+ source: str
13
+ prepared: str
14
+ blocks: list[DraconicBlock]
15
+ treat_as_module: bool
16
+
17
+
18
+ def build_source_context(source: str, treat_as_module: bool, *, apply_args: bool = True) -> SourceContext:
19
+ prepared = apply_argument_parsing(source) if apply_args and not treat_as_module else source
20
+ blocks = find_draconic_blocks(prepared, treat_as_module=treat_as_module)
21
+ return SourceContext(source=source, prepared=prepared, blocks=blocks, treat_as_module=treat_as_module)
22
+
23
+
24
+ def block_for_line(blocks: Sequence[DraconicBlock], line: int) -> DraconicBlock | None:
25
+ for block in blocks:
26
+ start = block.line_offset
27
+ end = block.line_offset + block.line_count
28
+ if start <= line <= end:
29
+ return block
30
+ return None
avrae_ls/symbols.py CHANGED
@@ -8,8 +8,9 @@ from typing import Dict, Iterable, List, Optional
8
8
  import draconic
9
9
  from lsprotocol import types
10
10
 
11
- from .argument_parsing import apply_argument_parsing
12
- from .parser import find_draconic_blocks
11
+ from .source_context import build_source_context
12
+ from .parser import wrap_draconic
13
+ from .lsp_utils import range_from_positions, shift_range
13
14
 
14
15
  log = logging.getLogger(__name__)
15
16
 
@@ -35,20 +36,19 @@ class SymbolTable:
35
36
  return self._index.get(name)
36
37
 
37
38
 
38
- def build_symbol_table(source: str) -> SymbolTable:
39
+ def build_symbol_table(source: str, *, treat_as_module: bool = False) -> SymbolTable:
39
40
  entries: list[SymbolEntry] = []
40
- parsed_source = apply_argument_parsing(source)
41
- blocks = find_draconic_blocks(parsed_source)
42
- if not blocks:
43
- entries.extend(_symbols_from_code(parsed_source, 0, 0))
41
+ source_ctx = build_source_context(source, treat_as_module)
42
+ if not source_ctx.blocks:
43
+ entries.extend(_symbols_from_code(source_ctx.prepared, 0, 0))
44
44
  else:
45
- for block in blocks:
45
+ for block in source_ctx.blocks:
46
46
  entries.extend(_symbols_from_code(block.code, block.line_offset, block.char_offset))
47
47
  return SymbolTable(entries)
48
48
 
49
49
 
50
- def document_symbols(source: str) -> List[types.DocumentSymbol]:
51
- table = build_symbol_table(source)
50
+ def document_symbols(source: str, *, treat_as_module: bool = False) -> List[types.DocumentSymbol]:
51
+ table = build_symbol_table(source, treat_as_module=treat_as_module)
52
52
  return [
53
53
  types.DocumentSymbol(
54
54
  name=entry.name,
@@ -68,9 +68,13 @@ def find_definition_range(table: SymbolTable, name: str) -> types.Range | None:
68
68
 
69
69
 
70
70
  def find_references(
71
- table: SymbolTable, source: str, name: str, include_declaration: bool = True
71
+ table: SymbolTable,
72
+ source: str,
73
+ name: str,
74
+ include_declaration: bool = True,
75
+ *,
76
+ treat_as_module: bool = False,
72
77
  ) -> List[types.Range]:
73
- parsed_source = apply_argument_parsing(source)
74
78
  ranges: list[types.Range] = []
75
79
  entry = table.lookup(name)
76
80
  include_stores = include_declaration and entry is None
@@ -78,11 +82,11 @@ def find_references(
78
82
  if entry:
79
83
  ranges.append(entry.selection_range)
80
84
 
81
- blocks = find_draconic_blocks(parsed_source)
82
- if not blocks:
83
- ranges.extend(_references_from_code(parsed_source, name, 0, 0, include_stores))
85
+ source_ctx = build_source_context(source, treat_as_module)
86
+ if not source_ctx.blocks:
87
+ ranges.extend(_references_from_code(source_ctx.prepared, name, 0, 0, include_stores))
84
88
  else:
85
- for block in blocks:
89
+ for block in source_ctx.blocks:
86
90
  ranges.extend(
87
91
  _references_from_code(block.code, name, block.line_offset, block.char_offset, include_stores)
88
92
  )
@@ -158,13 +162,14 @@ def _entry_from_node(node: ast.AST, line_offset: int = 0, char_offset: int = 0)
158
162
  else:
159
163
  return None
160
164
 
161
- rng = _range_from_positions(
165
+ rng = range_from_positions(
162
166
  getattr(node, "lineno", 1),
163
167
  getattr(node, "col_offset", 0),
164
168
  getattr(node, "end_lineno", None),
165
169
  getattr(node, "end_col_offset", None),
170
+ ensure_nonempty=True,
166
171
  )
167
- rng = _shift_range(rng, line_offset, char_offset)
172
+ rng = shift_range(rng, line_offset, char_offset)
168
173
  return SymbolEntry(name=name, kind=kind, range=rng, selection_range=rng)
169
174
 
170
175
 
@@ -179,11 +184,12 @@ class _ReferenceCollector(ast.NodeVisitor):
179
184
  if node.id == self._target:
180
185
  if isinstance(node.ctx, ast.Store) and not self._include_stores:
181
186
  return
182
- rng = _range_from_positions(
187
+ rng = range_from_positions(
183
188
  getattr(node, "lineno", 1),
184
189
  getattr(node, "col_offset", 0),
185
190
  getattr(node, "end_lineno", None),
186
191
  getattr(node, "end_col_offset", None),
192
+ ensure_nonempty=True,
187
193
  )
188
194
  self.ranges.append(rng)
189
195
  self.generic_visit(node)
@@ -205,7 +211,7 @@ def _references_from_code(
205
211
  collector.visit(node)
206
212
 
207
213
  local_offset = line_offset + offset_adjust
208
- return [_shift_range(rng, local_offset, char_offset) for rng in collector.ranges]
214
+ return [shift_range(rng, local_offset, char_offset) for rng in collector.ranges]
209
215
 
210
216
 
211
217
  def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
@@ -213,7 +219,7 @@ def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
213
219
  try:
214
220
  return parser.parse(code), 0
215
221
  except draconic.DraconicSyntaxError:
216
- wrapped, added = _wrap_draconic(code)
222
+ wrapped, added = wrap_draconic(code)
217
223
  try:
218
224
  return parser.parse(wrapped), -added
219
225
  except draconic.DraconicSyntaxError:
@@ -223,45 +229,6 @@ def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
223
229
  return [], 0
224
230
 
225
231
 
226
- def _range_from_positions(
227
- lineno: int | None,
228
- col_offset: int | None,
229
- end_lineno: int | None,
230
- end_col_offset: int | None,
231
- ) -> types.Range:
232
- start_line = max((lineno or 1) - 1, 0)
233
- start_char = max(col_offset or 0, 0)
234
- end_line = max(((end_lineno or lineno or 1) - 1), 0)
235
- raw_end_char = end_col_offset if end_col_offset is not None else col_offset
236
- end_char = max(raw_end_char or start_char, start_char)
237
- if end_char <= start_char:
238
- end_char = start_char + 1
239
- return types.Range(
240
- start=types.Position(line=start_line, character=start_char),
241
- end=types.Position(line=end_line, character=end_char),
242
- )
243
-
244
-
245
- def _shift_range(rng: types.Range, line_offset: int, char_offset: int = 0) -> types.Range:
246
- start_char = rng.start.character + (char_offset if rng.start.line == 0 else 0)
247
- end_char = rng.end.character + (char_offset if rng.end.line == 0 else 0)
248
- if line_offset == 0:
249
- return types.Range(
250
- start=types.Position(line=rng.start.line, character=start_char),
251
- end=types.Position(line=rng.end.line, character=end_char),
252
- )
253
- return types.Range(
254
- start=types.Position(line=max(rng.start.line + line_offset, 0), character=max(start_char, 0)),
255
- end=types.Position(line=max(rng.end.line + line_offset, 0), character=max(end_char, 0)),
256
- )
257
-
258
-
259
- def _wrap_draconic(code: str) -> tuple[str, int]:
260
- indented = "\n".join(f" {line}" for line in code.splitlines())
261
- wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
262
- return wrapped, 1
263
-
264
-
265
232
  def _dedupe_ranges(ranges: Iterable[types.Range]) -> List[types.Range]:
266
233
  seen = set()
267
234
  unique: list[types.Range] = []