avrae-ls 0.3.1__py3-none-any.whl → 0.4.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
@@ -18,7 +18,7 @@ from .alias_preview import render_alias_command, simulate_command
18
18
  from .parser import find_draconic_blocks
19
19
  from .signature_help import load_signatures, signature_help_for_code
20
20
  from .completions import gather_suggestions, completion_items_for_position, hover_for_position
21
- from .symbols import build_symbol_table, document_symbols, find_definition_range
21
+ from .symbols import build_symbol_table, document_symbols, find_definition_range, find_references, range_for_word
22
22
  from .argument_parsing import apply_argument_parsing
23
23
 
24
24
  __version__ = "0.1.0"
@@ -142,6 +142,43 @@ def on_definition(server: AvraeLanguageServer, params: types.DefinitionParams):
142
142
  return types.Location(uri=params.text_document.uri, range=rng)
143
143
 
144
144
 
145
+ @ls.feature(types.TEXT_DOCUMENT_REFERENCES)
146
+ def on_references(server: AvraeLanguageServer, params: types.ReferenceParams):
147
+ doc = server.workspace.get_text_document(params.text_document.uri)
148
+ table = build_symbol_table(doc.source)
149
+ word = doc.word_at_position(params.position)
150
+ if not word or not table.lookup(word):
151
+ return []
152
+
153
+ ranges = find_references(table, doc.source, word, include_declaration=params.context.include_declaration)
154
+ return [types.Location(uri=params.text_document.uri, range=rng) for rng in ranges]
155
+
156
+
157
+ @ls.feature(types.TEXT_DOCUMENT_PREPARE_RENAME)
158
+ def on_prepare_rename(server: AvraeLanguageServer, params: types.PrepareRenameParams):
159
+ doc = server.workspace.get_text_document(params.text_document.uri)
160
+ table = build_symbol_table(doc.source)
161
+ word = doc.word_at_position(params.position)
162
+ if not word or not table.lookup(word):
163
+ return None
164
+ return range_for_word(doc.source, params.position)
165
+
166
+
167
+ @ls.feature(types.TEXT_DOCUMENT_RENAME)
168
+ def on_rename(server: AvraeLanguageServer, params: types.RenameParams):
169
+ doc = server.workspace.get_text_document(params.text_document.uri)
170
+ table = build_symbol_table(doc.source)
171
+ word = doc.word_at_position(params.position)
172
+ if not word or not table.lookup(word) or not params.new_name:
173
+ return None
174
+
175
+ ranges = find_references(table, doc.source, word, include_declaration=True)
176
+ if not ranges:
177
+ return None
178
+ edits = [types.TextEdit(range=rng, new_text=params.new_name) for rng in ranges]
179
+ return types.WorkspaceEdit(changes={params.text_document.uri: edits})
180
+
181
+
145
182
  @ls.feature(types.WORKSPACE_SYMBOL)
146
183
  def on_workspace_symbol(server: AvraeLanguageServer, params: types.WorkspaceSymbolParams):
147
184
  symbols: list[types.SymbolInformation] = []
avrae_ls/symbols.py CHANGED
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import ast
4
4
  import logging
5
5
  from dataclasses import dataclass
6
- from typing import Dict, List, Optional
6
+ from typing import Dict, Iterable, List, Optional
7
7
 
8
8
  import draconic
9
9
  from lsprotocol import types
@@ -40,10 +40,10 @@ def build_symbol_table(source: str) -> SymbolTable:
40
40
  parsed_source = apply_argument_parsing(source)
41
41
  blocks = find_draconic_blocks(parsed_source)
42
42
  if not blocks:
43
- entries.extend(_symbols_from_code(parsed_source, 0))
43
+ entries.extend(_symbols_from_code(parsed_source, 0, 0))
44
44
  else:
45
45
  for block in blocks:
46
- entries.extend(_symbols_from_code(block.code, block.line_offset))
46
+ entries.extend(_symbols_from_code(block.code, block.line_offset, block.char_offset))
47
47
  return SymbolTable(entries)
48
48
 
49
49
 
@@ -67,31 +67,72 @@ def find_definition_range(table: SymbolTable, name: str) -> types.Range | None:
67
67
  return None
68
68
 
69
69
 
70
- def _symbols_from_code(code: str, line_offset: int) -> List[SymbolEntry]:
71
- parser = draconic.DraconicInterpreter()
72
- local_offset = line_offset
73
- try:
74
- body = parser.parse(code)
75
- except draconic.DraconicSyntaxError:
76
- wrapped, added = _wrap_draconic(code)
77
- try:
78
- body = parser.parse(wrapped)
79
- local_offset += -added
80
- except draconic.DraconicSyntaxError:
81
- return []
82
- except Exception as exc: # pragma: no cover - defensive
83
- log.debug("Symbol extraction failed: %s", exc)
70
+ def find_references(
71
+ table: SymbolTable, source: str, name: str, include_declaration: bool = True
72
+ ) -> List[types.Range]:
73
+ parsed_source = apply_argument_parsing(source)
74
+ ranges: list[types.Range] = []
75
+ entry = table.lookup(name)
76
+ include_stores = include_declaration and entry is None
77
+ if include_declaration:
78
+ if entry:
79
+ ranges.append(entry.selection_range)
80
+
81
+ blocks = find_draconic_blocks(parsed_source)
82
+ if not blocks:
83
+ ranges.extend(_references_from_code(parsed_source, name, 0, 0, include_stores))
84
+ else:
85
+ for block in blocks:
86
+ ranges.extend(
87
+ _references_from_code(block.code, name, block.line_offset, block.char_offset, include_stores)
88
+ )
89
+ return _dedupe_ranges(ranges)
90
+
91
+
92
+ def range_for_word(source: str, position: types.Position) -> types.Range | None:
93
+ lines = source.splitlines()
94
+ if position.line >= len(lines):
95
+ return None
96
+ line = lines[position.line]
97
+ if position.character > len(line):
98
+ return None
99
+
100
+ def _is_ident(ch: str) -> bool:
101
+ return ch.isalnum() or ch == "_"
102
+
103
+ start_idx = position.character
104
+ while start_idx > 0 and _is_ident(line[start_idx - 1]):
105
+ start_idx -= 1
106
+
107
+ end_idx = position.character
108
+ while end_idx < len(line) and _is_ident(line[end_idx]):
109
+ end_idx += 1
110
+
111
+ if start_idx == end_idx:
112
+ return None
113
+
114
+ return types.Range(
115
+ start=types.Position(line=position.line, character=start_idx),
116
+ end=types.Position(line=position.line, character=end_idx),
117
+ )
118
+
119
+
120
+ def _symbols_from_code(code: str, line_offset: int, char_offset: int) -> List[SymbolEntry]:
121
+ body, offset_adjust = _parse_draconic(code)
122
+ if not body:
84
123
  return []
85
124
 
125
+ local_offset = line_offset + offset_adjust
126
+
86
127
  entries: list[SymbolEntry] = []
87
128
  for node in body:
88
- entry = _entry_from_node(node, local_offset)
129
+ entry = _entry_from_node(node, local_offset, char_offset)
89
130
  if entry:
90
131
  entries.append(entry)
91
132
  return entries
92
133
 
93
134
 
94
- def _entry_from_node(node: ast.AST, line_offset: int = 0) -> SymbolEntry | None:
135
+ def _entry_from_node(node: ast.AST, line_offset: int = 0, char_offset: int = 0) -> SymbolEntry | None:
95
136
  if isinstance(node, ast.FunctionDef):
96
137
  kind = types.SymbolKind.Function
97
138
  name = node.name
@@ -103,6 +144,7 @@ def _entry_from_node(node: ast.AST, line_offset: int = 0) -> SymbolEntry | None:
103
144
  if isinstance(target, ast.Name):
104
145
  kind = types.SymbolKind.Variable
105
146
  name = target.id
147
+ node = target
106
148
  else:
107
149
  return None
108
150
  else:
@@ -110,37 +152,99 @@ def _entry_from_node(node: ast.AST, line_offset: int = 0) -> SymbolEntry | None:
110
152
 
111
153
  rng = _range_from_positions(
112
154
  getattr(node, "lineno", 1),
113
- getattr(node, "col_offset", 0) + 1,
155
+ getattr(node, "col_offset", 0),
114
156
  getattr(node, "end_lineno", None),
115
157
  getattr(node, "end_col_offset", None),
116
158
  )
117
- rng = _shift_range(rng, line_offset)
159
+ rng = _shift_range(rng, line_offset, char_offset)
118
160
  return SymbolEntry(name=name, kind=kind, range=rng, selection_range=rng)
119
161
 
120
162
 
163
+ class _ReferenceCollector(ast.NodeVisitor):
164
+ def __init__(self, target: str, include_stores: bool):
165
+ super().__init__()
166
+ self._target = target
167
+ self._include_stores = include_stores
168
+ self.ranges: list[types.Range] = []
169
+
170
+ def visit_Name(self, node: ast.Name): # type: ignore[override]
171
+ if node.id == self._target:
172
+ if isinstance(node.ctx, ast.Store) and not self._include_stores:
173
+ return
174
+ rng = _range_from_positions(
175
+ getattr(node, "lineno", 1),
176
+ getattr(node, "col_offset", 0),
177
+ getattr(node, "end_lineno", None),
178
+ getattr(node, "end_col_offset", None),
179
+ )
180
+ self.ranges.append(rng)
181
+ self.generic_visit(node)
182
+
183
+
184
+ def _references_from_code(
185
+ code: str,
186
+ name: str,
187
+ line_offset: int,
188
+ char_offset: int,
189
+ include_stores: bool,
190
+ ) -> List[types.Range]:
191
+ body, offset_adjust = _parse_draconic(code)
192
+ if not body:
193
+ return []
194
+
195
+ collector = _ReferenceCollector(name, include_stores)
196
+ for node in body:
197
+ collector.visit(node)
198
+
199
+ local_offset = line_offset + offset_adjust
200
+ return [_shift_range(rng, local_offset, char_offset) for rng in collector.ranges]
201
+
202
+
203
+ def _parse_draconic(code: str) -> tuple[list[ast.AST], int]:
204
+ parser = draconic.DraconicInterpreter()
205
+ try:
206
+ return parser.parse(code), 0
207
+ except draconic.DraconicSyntaxError:
208
+ wrapped, added = _wrap_draconic(code)
209
+ try:
210
+ return parser.parse(wrapped), -added
211
+ except draconic.DraconicSyntaxError:
212
+ return [], 0
213
+ except Exception as exc: # pragma: no cover - defensive
214
+ log.debug("Symbol extraction failed: %s", exc)
215
+ return [], 0
216
+
217
+
121
218
  def _range_from_positions(
122
219
  lineno: int | None,
123
220
  col_offset: int | None,
124
221
  end_lineno: int | None,
125
222
  end_col_offset: int | None,
126
223
  ) -> types.Range:
127
- start = types.Position(
128
- line=max((lineno or 1) - 1, 0),
129
- character=max((col_offset or 1) - 1, 0),
130
- )
131
- end = types.Position(
132
- line=max(((end_lineno or lineno or 1) - 1), 0),
133
- character=max(((end_col_offset or col_offset or 1) - 1), 0),
224
+ start_line = max((lineno or 1) - 1, 0)
225
+ start_char = max(col_offset or 0, 0)
226
+ end_line = max(((end_lineno or lineno or 1) - 1), 0)
227
+ raw_end_char = end_col_offset if end_col_offset is not None else col_offset
228
+ end_char = max(raw_end_char or start_char, start_char)
229
+ if end_char <= start_char:
230
+ end_char = start_char + 1
231
+ return types.Range(
232
+ start=types.Position(line=start_line, character=start_char),
233
+ end=types.Position(line=end_line, character=end_char),
134
234
  )
135
- return types.Range(start=start, end=end)
136
235
 
137
236
 
138
- def _shift_range(rng: types.Range, line_offset: int) -> types.Range:
237
+ def _shift_range(rng: types.Range, line_offset: int, char_offset: int = 0) -> types.Range:
238
+ start_char = rng.start.character + (char_offset if rng.start.line == 0 else 0)
239
+ end_char = rng.end.character + (char_offset if rng.end.line == 0 else 0)
139
240
  if line_offset == 0:
140
- return rng
241
+ return types.Range(
242
+ start=types.Position(line=rng.start.line, character=start_char),
243
+ end=types.Position(line=rng.end.line, character=end_char),
244
+ )
141
245
  return types.Range(
142
- start=types.Position(line=max(rng.start.line + line_offset, 0), character=rng.start.character),
143
- end=types.Position(line=max(rng.end.line + line_offset, 0), character=rng.end.character),
246
+ start=types.Position(line=max(rng.start.line + line_offset, 0), character=max(start_char, 0)),
247
+ end=types.Position(line=max(rng.end.line + line_offset, 0), character=max(end_char, 0)),
144
248
  )
145
249
 
146
250
 
@@ -148,3 +252,15 @@ def _wrap_draconic(code: str) -> tuple[str, int]:
148
252
  indented = "\n".join(f" {line}" for line in code.splitlines())
149
253
  wrapped = f"def __alias_main__():\n{indented}\n__alias_main__()"
150
254
  return wrapped, 1
255
+
256
+
257
+ def _dedupe_ranges(ranges: Iterable[types.Range]) -> List[types.Range]:
258
+ seen = set()
259
+ unique: list[types.Range] = []
260
+ for rng in ranges:
261
+ key = (rng.start.line, rng.start.character, rng.end.line, rng.end.character)
262
+ if key in seen:
263
+ continue
264
+ seen.add(key)
265
+ unique.append(rng)
266
+ return unique
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: avrae-ls
3
+ Version: 0.4.0
4
+ Summary: Language server for Avrae draconic aliases
5
+ Author: 1drturtle
6
+ Requires-Python: >=3.11
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: pygls>=1.3.1
10
+ Requires-Dist: lsprotocol>=2023.0.1
11
+ Requires-Dist: httpx>=0.27
12
+ Requires-Dist: d20>=1.1.2
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=8.3; extra == "dev"
15
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
16
+ Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
17
+ Requires-Dist: ruff>=0.6; extra == "dev"
18
+ Dynamic: license-file
19
+
20
+ # Avrae Draconic Alias Language Server
21
+
22
+ Language Server Protocol (LSP) implementation targeting Avrae-style draconic aliases. It provides syntax/semantic diagnostics, a mocked execution command, and a thin configuration layer driven by a workspace `.avraels.json` file. Credit to Avrae team for all code yoinked!
23
+
24
+ ## Install (released package)
25
+
26
+ - CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `avrae-ls --help` to see stdio/TCP options (same as `python -m avrae_ls`). The VS Code extension uses this invocation by default. The draconic interpreter is vendored, so no Git deps are needed.
27
+
28
+ ## VS Code extension (released)
29
+
30
+ - Install from VSIX: download `avrae-ls-client.vsix` from the GitHub releases page, then in VS Code run “Extensions: Install from VSIX” and select the file.
31
+ - Open your alias workspace; commands like `Avrae: Show Alias Preview` and `Avrae: Run Alias` will be available.
32
+
33
+ ## Developing locally
34
+
35
+ - Prereqs: [uv](https://github.com/astral-sh/uv) and Node.js.
36
+ - Install deps: `uv sync --all-extras` then `make vscode-deps`.
37
+ - Build everything locally: `make package` (wheel + VSIX in `dist/`).
38
+ - Run tests/lint: `make check`.
39
+ - Run via uv tool from source: `uv tool install --from . avrae-ls`.
40
+ - Run diagnostics for a single file (stdout + stderr logs): `avrae-ls --analyze path/to/alias.txt --log-level DEBUG`.
41
+
42
+ ## How to test
43
+
44
+ - Quick check (ruff + pytest): `make check` (uses `uv run ruff` and `uv run pytest` under the hood).
45
+ - Lint only: `make lint` or `uv run ruff check src tests`.
46
+ - Tests only (with coverage): `make test` or `uv run pytest tests --cov=src`.
47
+ - CLI smoke test without installing: `uv run python -m avrae_ls --analyze path/to/alias.txt`.
48
+
49
+ ## Runtime differences (mock vs. live Avrae)
50
+
51
+ - Mock execution never writes back to Avrae: cvar/uvar/gvar mutations only live for the current run and reset before the next.
52
+ - Network is limited to gvar fetches (when `enableGvarFetch` is true) and `verify_signature`; other Avrae/Discord calls are replaced with mocked context data from `.avraels.json`.
53
+ - `get_gvar`/`using` values are pulled from local var files first; remote fetches go to `https://api.avrae.io/customizations/gvars/<id>` (or your `avraeService.baseUrl`) using `avraeService.token` and are cached for the session.
54
+ - `signature()` returns a mock string (`mock-signature:<int>`). `verify_signature()` POSTs to `/bot/signature/verify`, respects `verifySignatureTimeout`/`verifySignatureRetries`, reuses the last successful response per signature, and includes `avraeService.token` if present.
55
+
56
+ ## Troubleshooting gvar fetch / verify_signature
57
+
58
+ - `get_gvar` returns `None` or `using(...)` raises `ModuleNotFoundError`: ensure the workspace `.avraels.json` sets `enableGvarFetch: true`, includes a valid `avraeService.token`, or seed the gvar in a var file referenced by `varFiles`.
59
+ - HTTP 401/403/404 from fetch/verify calls: check the token (401/403) and the gvar/signature id (404). Override `avraeService.baseUrl` if you mirror the API.
60
+ - Slow or flaky calls: tune `verifySignatureTimeout` / `verifySignatureRetries`, or disable remote fetches by flipping `enableGvarFetch` off to rely purely on local vars.
61
+
62
+ ## Other editors (stdio)
63
+
64
+ - Any client can launch the server with stdio: `avrae-ls --stdio` (flag accepted for client compatibility) or `python -m avrae_ls`. The server will also auto-discover `.avraels.json` in parent folders.
65
+ - Neovim (nvim-lspconfig example):
66
+ ```lua
67
+ require("lspconfig").avraels.setup({
68
+ cmd = { "avrae-ls", "--stdio" },
69
+ filetypes = { "avrae" },
70
+ root_dir = require("lspconfig.util").root_pattern(".avraels.json", ".git"),
71
+ })
72
+ ```
73
+ - Emacs (lsp-mode snippet):
74
+ ```elisp
75
+ (lsp-register-client
76
+ (make-lsp-client
77
+ :new-connection (lsp-stdio-connection '("avrae-ls" "--stdio"))
78
+ :major-modes '(fundamental-mode) ;; bind to your Avrae alias mode
79
+ :server-id 'avrae-ls))
80
+ ```
81
+ - VS Code commands to mirror: `Avrae: Run Alias (Mock)`, `Avrae: Show Alias Preview`, and `Avrae: Reload Workspace Config` run against the same server binary.
82
+
83
+ ## Releasing (maintainers)
84
+
85
+ 1. Bump `pyproject.toml` / `package.json`
86
+ 2. Create Github release
@@ -1,21 +1,21 @@
1
1
  avrae_ls/__init__.py,sha256=BmjrnksGkbG7TPqwbyQvgYj9uei8pFSFpfkRpaGVdJU,63
2
2
  avrae_ls/__main__.py,sha256=ch287lWe11go5xHAE9OkVppixt0vRF401E4zTs2tqQ0,3557
3
3
  avrae_ls/alias_preview.py,sha256=Wy_ZNRq73ojPiLFf6P6fJ7rG7DeFFsFLkLlhFrAjkKI,6117
4
- avrae_ls/api.py,sha256=sGbwWs5o6M49lyk3qZi6gNdSH_u_BMdghsTlAjpviqQ,64852
5
- avrae_ls/argparser.py,sha256=-6RKrXavbSjEyyEeaoz8hRamnB-MEmJt3Cw2smRbmdI,13483
4
+ avrae_ls/api.py,sha256=_AHvIEIlz34YeWdZDpXd1EwgdQUGk8-nqNIEN4qdNR8,65093
5
+ avrae_ls/argparser.py,sha256=DRptXGyK4f0r7NsuV1Sg4apG-qihIClOX408jgk4HH4,13762
6
6
  avrae_ls/argument_parsing.py,sha256=ezKl65VwuNEDxt6KlYwVQcpy1110UDvf4BqZqgZTcqk,2122
7
- avrae_ls/completions.py,sha256=WSyP967_t_oBhRMjLk-QQKyXWiNh4sJTH560VdMBQeg,34336
8
- avrae_ls/config.py,sha256=C51wfG6-82uX4dsffqcDLMiXbZZL9JQgfpulAr8eiqs,15267
9
- avrae_ls/context.py,sha256=w0uVSR6Pis3q1fF3kSPsqbTirKOE6n3k2XMO8UZI7sk,5719
7
+ avrae_ls/completions.py,sha256=ht0J7JQX5QS8cz85itfozLBP9GPr1BjmNyttFEmJN-0,34514
8
+ avrae_ls/config.py,sha256=ULEfWbzIO05cllHBLUeGY_CHLpX6tHyCJYIEQgKSoXc,17383
9
+ avrae_ls/context.py,sha256=KcCKqzqJCG2xlEtou5HWz1OWISApGl8IfwOvVnNi9nc,8464
10
10
  avrae_ls/cvars.py,sha256=0tcVbUHx_CKJ6aou3kEsKX37LRWAjkUWlqqIuSRFlXk,3197
11
- avrae_ls/diagnostics.py,sha256=EAdbckvvWu0pswopaZ6JyVGCHXUl5ZvGBhP2PDVCrVw,16180
11
+ avrae_ls/diagnostics.py,sha256=2wn9Gu5vDGWt32zap9rhduv4q4B9c_X3baI2_yDHGYg,23074
12
12
  avrae_ls/dice.py,sha256=DY7V7L-EwAXaCgddgVe9xU1s9lVtiw5Zc2reipNgdTk,874
13
- avrae_ls/parser.py,sha256=AuNxNkkfquN9dcyTpmzAZxWcAQ7CV3PQLHUDYLMz_7U,1148
14
- avrae_ls/runtime.py,sha256=sSN_C48TrF7F5NtsUpgHr_cZyYX6HCQHBfCpAYxsDJo,20792
15
- avrae_ls/server.py,sha256=L3qGTpCDOa64hWFmfWfoKbrDZ3262gmu5wYhHkdLBPY,13070
13
+ avrae_ls/parser.py,sha256=UQDwjupuJVU6KvoF4D8r3azPT7ksP_nTZsm5FUhGnWE,1395
14
+ avrae_ls/runtime.py,sha256=PDnjZXcU5DqM9jKSc4S0jHObBE7SWKte17PWWXGQ34M,23356
15
+ avrae_ls/server.py,sha256=jKypzveP2iRZjEdK0xJxfcn6QfbjrDe03XnXNhsga9Q,14709
16
16
  avrae_ls/signature_help.py,sha256=JheaEzINV4FO72t5U0AJfL2ZX15y3-gcA6xk3M1jHcY,6980
17
- avrae_ls/symbols.py,sha256=8aMalHBDnRsRvhwdbmf0nOazio7G185qw9le45Xb5Mk,4449
18
- avrae_ls-0.3.1.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
17
+ avrae_ls/symbols.py,sha256=Lm5LH-F60JYNuAtSjRhHeyHEWAWxpnNb0w6KR0u1Zac,8422
18
+ avrae_ls-0.4.0.dist-info/licenses/LICENSE,sha256=O-0zMbcEi6wXz1DiSdVgzMlQjJcNqNe5KDv08uYzqR0,1055
19
19
  draconic/LICENSE,sha256=Fzvu32_DafLKKn2mzxhEdlmrKZzAsigDZ87O7uoVqZI,1067
20
20
  draconic/__init__.py,sha256=YPH420Pcn_nTkfB62hJy_YqC5kpJdzSa78jP8n4z_xY,109
21
21
  draconic/exceptions.py,sha256=siahnHIsumbaUhKBDSrw_DmLZ-0oZks8L5oytPH8hD4,3753
@@ -25,8 +25,8 @@ draconic/string.py,sha256=kGrRc6wNHRq1y5xw8Os-fBhfINDtIY2nBWQWkyLSfQI,2858
25
25
  draconic/types.py,sha256=1Lsr6z8bW5agglGI4hLt_nPtYuZOIf_ueSpPDB4WDrs,13686
26
26
  draconic/utils.py,sha256=D4vJ-txqS2-rlqsEpXAC46_j1sZX4UjY-9zIgElo96k,3122
27
27
  draconic/versions.py,sha256=CUEsgUWjAmjez0432WwiBwZlIzWPIObwZUf8Yld18EE,84
28
- avrae_ls-0.3.1.dist-info/METADATA,sha256=TRJQs5mkNC4TPeYBkO2rXDnvkJhjf2jws1ginLXaXq4,2107
29
- avrae_ls-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
- avrae_ls-0.3.1.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
31
- avrae_ls-0.3.1.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
32
- avrae_ls-0.3.1.dist-info/RECORD,,
28
+ avrae_ls-0.4.0.dist-info/METADATA,sha256=z-bp7Y2zrilkbNmLujQYnHOW6rRlAyPMdQpJBDUB4cU,4713
29
+ avrae_ls-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
+ avrae_ls-0.4.0.dist-info/entry_points.txt,sha256=OtYXipMQzqmxpMoApgo0MeJYFmMbkbFN51Ibhpb8hF4,52
31
+ avrae_ls-0.4.0.dist-info/top_level.txt,sha256=TL68uzGHmB2R2ID32_s2zocmcNnpMJVQ6_4NBvyo8a4,18
32
+ avrae_ls-0.4.0.dist-info/RECORD,,
@@ -1,47 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: avrae-ls
3
- Version: 0.3.1
4
- Summary: Language server for Avrae draconic aliases
5
- Author: 1drturtle
6
- Requires-Python: >=3.11
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Requires-Dist: pygls>=1.3.1
10
- Requires-Dist: lsprotocol>=2023.0.1
11
- Requires-Dist: httpx>=0.27
12
- Requires-Dist: d20>=1.1.2
13
- Provides-Extra: dev
14
- Requires-Dist: pytest>=8.3; extra == "dev"
15
- Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
16
- Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
17
- Requires-Dist: ruff>=0.6; extra == "dev"
18
- Dynamic: license-file
19
-
20
- # Avrae Draconic Alias Language Server
21
-
22
- Language Server Protocol (LSP) implementation targeting Avrae-style draconic aliases. It provides syntax/semantic diagnostics, a mocked execution command, and a thin configuration layer driven by a workspace `.avraels.json` file. Credit to Avrae team for all code yoinked!
23
-
24
- ## Install (released package)
25
-
26
- - CLI/server via `uv tool` (preferred): `uv tool install avrae-ls` then `avrae-ls --help` to see stdio/TCP options (same as `python -m avrae_ls`). The VS Code extension uses this invocation by default. The draconic interpreter is vendored, so no Git deps are needed.
27
-
28
- ## VS Code extension (released)
29
-
30
- - Install from VSIX: download `avrae-ls-client.vsix` from the GitHub releases page, then in VS Code run “Extensions: Install from VSIX” and select the file.
31
- - Open your alias workspace; commands like `Avrae: Show Alias Preview` and `Avrae: Run Alias` will be available.
32
-
33
- ## Developing locally
34
-
35
- - Prereqs: [uv](https://github.com/astral-sh/uv) and Node.js.
36
- - Install deps: `uv sync --all-extras` then `make vscode-deps`.
37
- - Build everything locally: `make package` (wheel + VSIX in `dist/`).
38
- - Run tests/lint: `make check`.
39
- - Run via uv tool from source: `uv tool install --from . avrae-ls`.
40
- - Run diagnostics for a single file (stdout + stderr logs): `avrae-ls --analyze path/to/alias.txt --log-level DEBUG`.
41
-
42
- ## Releasing (maintainers)
43
-
44
- 1. Bump `pyproject.toml` version.
45
- 2. `make release` (clean, build, upload to PyPI).
46
- 3. Build and attach the VSIX to the GitHub release (`make vsix`).
47
- 4. Tag and push.