renpy-language-server 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.

Potentially problematic release.


This version of renpy-language-server might be problematic. Click here for more details.

Files changed (44) hide show
  1. renpy_language_server-0.2.0/LICENSE +21 -0
  2. renpy_language_server-0.2.0/NOTICE.md +41 -0
  3. renpy_language_server-0.2.0/PKG-INFO +434 -0
  4. renpy_language_server-0.2.0/README.md +403 -0
  5. renpy_language_server-0.2.0/pyproject.toml +68 -0
  6. renpy_language_server-0.2.0/pyproject.toml.orig +59 -0
  7. renpy_language_server-0.2.0/src/renpy_language_server/__init__.py +6 -0
  8. renpy_language_server-0.2.0/src/renpy_language_server/__main__.py +5 -0
  9. renpy_language_server-0.2.0/src/renpy_language_server/analysis.py +230 -0
  10. renpy_language_server-0.2.0/src/renpy_language_server/assets.py +104 -0
  11. renpy_language_server-0.2.0/src/renpy_language_server/builtins.py +54 -0
  12. renpy_language_server-0.2.0/src/renpy_language_server/cli.py +190 -0
  13. renpy_language_server-0.2.0/src/renpy_language_server/code_actions.py +130 -0
  14. renpy_language_server-0.2.0/src/renpy_language_server/completion.py +1191 -0
  15. renpy_language_server-0.2.0/src/renpy_language_server/completion_data.py +74 -0
  16. renpy_language_server-0.2.0/src/renpy_language_server/dap.py +824 -0
  17. renpy_language_server-0.2.0/src/renpy_language_server/data/__init__.py +1 -0
  18. renpy_language_server-0.2.0/src/renpy_language_server/data/completion.json +756 -0
  19. renpy_language_server-0.2.0/src/renpy_language_server/data/renpy_api.json +8073 -0
  20. renpy_language_server-0.2.0/src/renpy_language_server/definition.py +77 -0
  21. renpy_language_server-0.2.0/src/renpy_language_server/diagnostics.py +895 -0
  22. renpy_language_server-0.2.0/src/renpy_language_server/docs.py +21 -0
  23. renpy_language_server-0.2.0/src/renpy_language_server/edits.py +70 -0
  24. renpy_language_server-0.2.0/src/renpy_language_server/folding.py +107 -0
  25. renpy_language_server-0.2.0/src/renpy_language_server/formatter.py +288 -0
  26. renpy_language_server-0.2.0/src/renpy_language_server/hover.py +238 -0
  27. renpy_language_server-0.2.0/src/renpy_language_server/index.py +1002 -0
  28. renpy_language_server-0.2.0/src/renpy_language_server/positions.py +28 -0
  29. renpy_language_server-0.2.0/src/renpy_language_server/py.typed +0 -0
  30. renpy_language_server-0.2.0/src/renpy_language_server/python_format.py +141 -0
  31. renpy_language_server-0.2.0/src/renpy_language_server/python_index.py +371 -0
  32. renpy_language_server-0.2.0/src/renpy_language_server/quotes.py +143 -0
  33. renpy_language_server-0.2.0/src/renpy_language_server/references.py +40 -0
  34. renpy_language_server-0.2.0/src/renpy_language_server/rename.py +293 -0
  35. renpy_language_server-0.2.0/src/renpy_language_server/resolve.py +807 -0
  36. renpy_language_server-0.2.0/src/renpy_language_server/scanner.py +86 -0
  37. renpy_language_server-0.2.0/src/renpy_language_server/semantic.py +488 -0
  38. renpy_language_server-0.2.0/src/renpy_language_server/server.py +770 -0
  39. renpy_language_server-0.2.0/src/renpy_language_server/settings.py +187 -0
  40. renpy_language_server-0.2.0/src/renpy_language_server/signature.py +371 -0
  41. renpy_language_server-0.2.0/src/renpy_language_server/spacing.py +261 -0
  42. renpy_language_server-0.2.0/src/renpy_language_server/strings.py +86 -0
  43. renpy_language_server-0.2.0/src/renpy_language_server/symbols.py +163 -0
  44. renpy_language_server-0.2.0/src/renpy_language_server/targets.py +51 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oleksandr (rockettap)
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.
@@ -0,0 +1,41 @@
1
+ # Third-party notices
2
+
3
+ ## Ren'Py API data
4
+
5
+ `src/renpy_language_server/data/renpy_api.json` is derived from the `src/renpy.json` file in
6
+ [`renpy/vscode-language-renpy`](https://github.com/renpy/vscode-language-renpy).
7
+
8
+ The original data was converted from nested categories into a flat table, and
9
+ each entry keeps only its name, kind, argument list, Python type, category and
10
+ documentation. Names, signatures and documentation are otherwise unchanged.
11
+
12
+ `src/renpy_language_server/data/completion.json` also includes the `keyword_values` table
13
+ from `src/renpyauto.json` in the same project. The surrounding screen-element,
14
+ property, transform and transition lists are a curated snapshot of Ren'Py's
15
+ public screen language.
16
+
17
+ ```text
18
+ The MIT License
19
+
20
+ Original work Copyright (c) 2018 Daniel Luque <danielluque14@gmail.com>
21
+ Modified work Copyright (c) 2024 Tom Rothamel <pytom@bishoujo.us>
22
+
23
+ Permission is hereby granted, free of charge, to any person obtaining
24
+ a copy of this software and associated documentation files (the
25
+ "Software"), to deal in the Software without restriction, including
26
+ without limitation the rights to use, copy, modify, merge, publish,
27
+ distribute, sublicense, and/or sell copies of the Software, and to
28
+ permit persons to whom the Software is furnished to do so, subject to
29
+ the following conditions:
30
+
31
+ The above copyright notice and this permission notice shall be
32
+ included in all copies or substantial portions of the Software.
33
+
34
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
+ ```
@@ -0,0 +1,434 @@
1
+ Metadata-Version: 2.4
2
+ Name: renpy-language-server
3
+ Version: 0.2.0
4
+ Summary: Language server and formatter for Ren'Py scripts
5
+ Keywords: renpy,lsp,language-server,formatter
6
+ Author: Олександр
7
+ Author-email: Олександр <67645530+rockettap@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ License-File: NOTICE.md
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Text Editors :: Integrated Development Environments (IDE)
21
+ Requires-Dist: pygls>=2.1,<3
22
+ Requires-Dist: typer>=0.27,<1
23
+ Requires-Dist: ruff>=0.16,<1 ; extra == 'python-format'
24
+ Requires-Python: >=3.10
25
+ Project-URL: Homepage, https://github.com/rockettap/renpy-language-server
26
+ Project-URL: Repository, https://github.com/rockettap/renpy-language-server
27
+ Project-URL: Issues, https://github.com/rockettap/renpy-language-server/issues
28
+ Project-URL: Changelog, https://github.com/rockettap/renpy-language-server/releases
29
+ Provides-Extra: python-format
30
+ Description-Content-Type: text/markdown
31
+
32
+ # renpy-language-server
33
+
34
+ Language server and formatter for [Ren'Py](https://www.renpy.org) scripts, built with
35
+ [pygls](https://github.com/openlawlibrary/pygls) and managed with [uv](https://docs.astral.sh/uv/).
36
+ Part of [renpy-zed](https://github.com/rockettap/renpy-zed) (Ren'Py support
37
+ for Zed), but it speaks standard LSP and works with any client.
38
+
39
+ Requires Python 3.10 or newer.
40
+
41
+ > Unofficial project, not affiliated with Ren'Py.
42
+
43
+ ## Features
44
+
45
+ - `textDocument/formatting` and `textDocument/rangeFormatting`, returned as
46
+ minimal line edits (the cursor does not jump)
47
+ - `textDocument/completion` for statements, labels, images (including automatic
48
+ images from `game/images`), screens, characters, transforms, transitions,
49
+ audio channels/assets and the bundled Ren'Py API namespaces. Screen bodies
50
+ complete elements, properties and property values, dialogue completes image
51
+ attributes, and `$` / conditions / declarations complete Python keywords and
52
+ built-ins
53
+ - `textDocument/documentSymbol` for labels, screens, transforms, images,
54
+ characters, styles, layered images, `define` / `default` variables, and for
55
+ Python classes, functions, methods, properties, fields, stores, persistent
56
+ variables, test cases and translations. Class and store members are nested in
57
+ the outline
58
+ - `textDocument/definition` for labels, screens, transforms, images, layered
59
+ images, characters, styles and variables, including local labels (`.local`)
60
+ resolved against the most recent global label and images after `show` /
61
+ `scene` / `hide` (declared images or automatic image files)
62
+ - Definition also follows Python member access where it is unambiguous:
63
+ `self.field`, `Class.member`, `persistent.value`, `store.value` and class
64
+ base names
65
+ - `textDocument/hover` for labels, screens, transforms, images, characters and
66
+ other declarations, for references such as `jump`, `show`, `use` and `at`,
67
+ for automatic images from `game/images`, and for the bundled Ren'Py API
68
+ (signature, documentation and source location)
69
+ - Hover includes comment-block documentation above a declaration and works for
70
+ references across files
71
+ - `textDocument/prepareRename` and `textDocument/rename` for labels (global,
72
+ dotted and local), screens, transforms, images, layered images, characters,
73
+ styles and `define` / `default` variables, across every indexed file.
74
+ Class/function/member rename is deliberately deferred
75
+ - `textDocument/documentHighlight` for the symbol under the cursor, so Zed
76
+ shades declarations and references while editing and renaming
77
+ - `textDocument/references` for the same families as rename, across every
78
+ indexed file, with `includeDeclaration` respected
79
+ - `workspace/symbol` for workspace-wide symbol search across labels, screens,
80
+ images, characters, styles, variables, classes, members, stores, persistent
81
+ variables, test cases and translations
82
+ - `textDocument/signatureHelp` for bundled Ren'Py API calls, labels, screens,
83
+ user classes/functions and `self.` / `Class.` methods
84
+ - `textDocument/codeAction` quick fixes for comparison typos, stray `$`,
85
+ leading tabs and unknown labels
86
+ - `textDocument/semanticTokens/full` (opt-in) for declarations, references,
87
+ built-ins, obsolete APIs, keywords, parameters and literals
88
+ - Completion of class members after `Class.` and `self.`, persistent fields
89
+ after `persistent.`, variables after `store.`, and API names after
90
+ `renpy.`, `renpy.music.`, `renpy.audio.`, `config.` and `gui.`
91
+ - Callable built-ins insert their parentheses (`Character()`), and completion
92
+ is available from spaces, `.`, `(`, `@` and `-`
93
+ - Completion documentation is sent as Markdown, so signature fences and inline
94
+ code render as code blocks in the editor
95
+ - configurable diagnostics for indentation, tabs, filenames, variable names,
96
+ comparisons, stray `$`, obsolete APIs, unknown labels and images, undefined
97
+ screens, persistent/store variables and duplicate labels, with per-rule
98
+ severity and open-file or workspace modes (see [Settings](#settings))
99
+ - `textDocument/foldingRange` for labels, menus, menu choices, screens,
100
+ transforms and other indented blocks
101
+ - `renpy-language-server dap`, a launch-only Debug Adapter Protocol server that starts a
102
+ project through an installed Ren'Py SDK, supports `warp`, streams output and
103
+ terminates the game. Breakpoints, stepping, variables and attach are not
104
+ implemented yet
105
+ - `renpy-language-server format` command for scripts and CI (`--check`, `--diff`)
106
+ - Safe formatting by default: whitespace and quote delimiters only, never
107
+ string values or program behaviour
108
+ - Inline spacing for Python-ish statements (`$ points=1` becomes
109
+ `$ points = 1`) and inside `python:` blocks
110
+ - Single-quoted strings become double-quoted unless the value contains an
111
+ unescaped `"` (`--no-quotes` / `normalize_quotes: false` disables it)
112
+ - Optional Ruff formatting for `python:` block bodies
113
+
114
+ ## Demo
115
+
116
+ ![Ren'Py completion and hover in Zed](https://raw.githubusercontent.com/rockettap/renpy-zed/main/docs/media/renpy-zed.png)
117
+
118
+ ## Install
119
+
120
+ ```bash
121
+ uv tool install renpy-language-server
122
+ uv tool install "renpy-language-server[python-format]" # with optional Ruff formatting
123
+ # for development, from a clone:
124
+ uv tool install --editable .
125
+ uv tool install --editable ".[python-format]"
126
+ ```
127
+
128
+ ## Use
129
+
130
+ ```bash
131
+ renpy-language-server format game/ # format every .rpy under game/ in place
132
+ renpy-language-server format --check game/ # exit 1 if anything would change (CI)
133
+ renpy-language-server format --diff script.rpy
134
+ renpy-language-server format --python game/ # also format python: blocks with Ruff
135
+ cat script.rpy | renpy-language-server format -
136
+ renpy-language-server # start the language server on stdio
137
+ renpy-language-server dap # start the launch-only debug adapter on stdio
138
+ ```
139
+
140
+ ### Debug adapter
141
+
142
+ `renpy-language-server dap` speaks DAP over stdin/stdout. It is normally launched by the
143
+ `renpy-zed` Zed extension, but any DAP client can start it. A launch
144
+ configuration accepts:
145
+
146
+ | Field | Default | Meaning |
147
+ | --------- | ------- | ------------------------------------------------------------- |
148
+ | `request` | launch | Only `launch` is accepted |
149
+ | `sdk` | auto | SDK directory or a launcher inside it |
150
+ | `project` | auto | Directory containing `game/`; inferred from `warp` or the cwd |
151
+ | `warp` | none | `file:line`, made project-relative when possible |
152
+ | `command` | `run` | Ren'Py CLI command (`run`, `lint`, `compile`, ...) |
153
+ | `args` | `[]` | Extra arguments appended to the Ren'Py command line |
154
+ | `env` | `{}` | Extra environment variables for the game process |
155
+
156
+ The SDK is resolved from `sdk`, then `RENPY_SDK`, then `renpy-*-sdk`
157
+ directories in the home directory, `Documents`, `Downloads`, `Desktop`,
158
+ `Applications` and `/Applications`, newest version first. `RENPY_SDK` may name
159
+ the SDK directory or a launcher inside it.
160
+
161
+ `terminate` and `disconnect` stop the game, escalating to a forced kill after
162
+ three seconds. `setBreakpoints` reports the requested lines as unverified and
163
+ stepping, pause and evaluate return a clear "launch-only" error. `attach` is
164
+ rejected.
165
+
166
+ ### In Zed
167
+
168
+ Until the renpy-zed extension is published, point Zed at the binary
169
+ (`which renpy-language-server`) in `settings.json`:
170
+
171
+ ```json
172
+ {
173
+ "lsp": {
174
+ "renpy-language-server": {
175
+ "binary": { "path": "/home/you/.local/bin/renpy-language-server" }
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ The language itself (`.rpy` files) must be registered by the extension first.
182
+
183
+ Zed uses LSP folding ranges only when its `document_folding_ranges` setting is
184
+ enabled. Without it, Zed falls back to indentation-based folding, which can
185
+ attach a menu choice's fold to the next choice:
186
+
187
+ ```json
188
+ {
189
+ "languages": {
190
+ "Ren'Py": { "document_folding_ranges": "on" }
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Settings
196
+
197
+ The server accepts settings from the language client's `initializationOptions`
198
+ (under `renpyLanguageServer`, or flat) and from
199
+ `workspace/didChangeConfiguration`:
200
+
201
+ ```json
202
+ {
203
+ "renpyLanguageServer": {
204
+ "indent": 4,
205
+ "max_blank_lines": 2,
206
+ "align_equals": true,
207
+ "normalize_spacing": true,
208
+ "normalize_quotes": true,
209
+ "format_python": false,
210
+ "diagnostics": true,
211
+ "code_actions": true,
212
+ "unknown_label_diagnostics": true,
213
+ "diagnostics_mode": "open",
214
+ "diagnostic_rules": {
215
+ "indentation": "warning",
216
+ "tabs": "warning",
217
+ "unknown_label": "warning"
218
+ },
219
+ "completion": true,
220
+ "automatic_assets": true,
221
+ "semantic_tokens": false,
222
+ "workspace_symbols": true,
223
+ "hover": true,
224
+ "rename": true,
225
+ "references": true,
226
+ "highlight": true,
227
+ "signature": true,
228
+ "extended_index": true,
229
+ "member_references": true
230
+ }
231
+ }
232
+ ```
233
+
234
+ | Setting | Default | Meaning |
235
+ | --------------------------- | --------- | ------------------------------------------------------------- |
236
+ | `indent` | `4` | Fallback spaces per indentation level |
237
+ | `max_blank_lines` | `2` | Maximum consecutive blank lines |
238
+ | `align_equals` | `true` | Align consecutive assignment `=` |
239
+ | `normalize_spacing` | `true` | Inline operator spacing |
240
+ | `normalize_quotes` | `true` | Rewrite single-quoted strings to double quotes |
241
+ | `format_python` | `false` | Format `python:` bodies with Ruff |
242
+ | `diagnostics` | `true` | Enable all diagnostics |
243
+ | `code_actions` | `true` | Enable diagnostic quick fixes |
244
+ | `unknown_label_diagnostics` | `true` | Legacy switch for the unknown-label rule |
245
+ | `diagnostics_mode` | `"open"` | Check open files (`open`) or the workspace |
246
+ | `diagnostic_rules` | see below | Per-rule `error`, `warning` or `off` |
247
+ | `completion` | `true` | Enable completion |
248
+ | `automatic_assets` | `true` | Offer images/audio found on disk in completion |
249
+ | `semantic_tokens` | `false` | Enable semantic tokens (opt-in) |
250
+ | `workspace_symbols` | `true` | Enable `workspace/symbol` search |
251
+ | `hover` | `true` | Enable hover information |
252
+ | `rename` | `true` | Enable prepare-rename and rename |
253
+ | `references` | `true` | Enable find-references |
254
+ | `highlight` | `true` | Enable occurrence highlights |
255
+ | `signature` | `true` | Enable signature help |
256
+ | `extended_index` | `true` | Index Python classes, stores and related declaration families |
257
+ | `member_references` | `true` | Index `self.x` / `Class.x` / `persistent.x` / `store.x` |
258
+
259
+ `diagnostic_rules` accepts these keys: `indentation`, `tabs`, `filename`,
260
+ `invalid_variable`, `reserved_names`, `comparison`, `stray_dollar`,
261
+ `obsolete_api`, `undefined_persistent`, `undefined_store`, `duplicate_label`,
262
+ `unknown_label`, `unknown_image` and `undefined_screen`. The two pre-existing
263
+ rules (`indentation`, `unknown_label`) and `tabs` default to `warning`; all
264
+ newer rules default to `off` until they have been validated on real projects.
265
+ Set a rule to `error` for stricter projects, or to `off` to silence it. The
266
+ legacy `unknown_label_diagnostics` flag still maps to the `unknown_label` rule.
267
+
268
+ Semantic tokens are disabled by default because Zed already highlights Ren'Py
269
+ through tree-sitter and the two layers should be validated together. Set
270
+ `semantic_tokens` to `true` to let the server add declaration, reference,
271
+ built-in and obsolete-API tokens; the capability and legend are always
272
+ advertised, and the provider returns an empty stream while disabled.
273
+
274
+ Diagnostics have the source `renpy-language-server` and a stable code such as
275
+ `renpy-language-server.unknown-image`. In `workspace` mode the server publishes diagnostics
276
+ for every indexed file on startup and after configuration, save and watched-file
277
+ events; normal typing only rechecks the current document.
278
+
279
+ Rename follows the indexed references: `jump` / `call`, screen statements,
280
+ `show` / `scene` / `hide`, `at`, dialogue speakers and style parents. Variables
281
+ are renamed in code, dialogue interpolation (`"[var]"`), and across files,
282
+ including declarations with an init priority (`define -1 foo = ...`) and the
283
+ `+=` / `|=` operators.
284
+
285
+ Rename protects Ren'Py syntax: statement and screen-language keywords, audio
286
+ channel names, `with` / `as` / `onlayer` / `behind` clauses, dialogue
287
+ attributes, screen-local `default` variables and ordinary strings. Dynamic
288
+ targets such as `jump expression`, automatic image files and arbitrary
289
+ attribute access are not renamed. Store-qualified declarations such as
290
+ `define config.name`, variable names passed as strings (for example
291
+ `SetVariable("points", ...)`), and local names that shadow a store variable
292
+ inside Python functions are not handled. If several declarations share a name,
293
+ all of them are renamed together.
294
+
295
+ Classes, functions, methods, fields, properties, stores, persistent variables,
296
+ test cases and translations are indexed for navigation and completion, but
297
+ class/function/member rename is intentionally deferred to a later release.
298
+
299
+ Occurrence highlights use Zed's `editor.document_highlight.write_background`
300
+ for declarations and `editor.document_highlight.read_background` for
301
+ references. Adjust those keys in the theme overrides if the shade is too
302
+ subtle.
303
+
304
+ Hover documentation for Ren'Py built-ins comes from the MIT-licensed
305
+ `vscode-language-renpy` data; see [`NOTICE.md`](NOTICE.md).
306
+
307
+ ### Logs
308
+
309
+ The server writes logs to stderr, never stdout. Normal runs print only a startup
310
+ line; pygls protocol logs, which include full document contents, are disabled by
311
+ default. To inspect the JSON-RPC traffic, launch Zed with:
312
+
313
+ ```bash
314
+ RENPY_LANGUAGE_SERVER_DEBUG=1 zed .
315
+ ```
316
+
317
+ ## Formatting rules
318
+
319
+ | Rule | Detail |
320
+ | ------------------- | ------------------------------------------------------------------------------------------------------------------------ |
321
+ | Indentation | `tabSize` from the editor (default 4) spaces per level; tabs are expanded |
322
+ | Trailing whitespace | removed |
323
+ | Assignments | the `=` of consecutive assignment lines at the same indentation level is aligned into one column |
324
+ | Inline spacing | assignment and comparison operators in `$`, `define`, `default`, `if`, `elif`, `while`, `return` and `assert` lines |
325
+ | Keyword arguments | spaces around `=` are removed (`color = "#fff"` becomes `color="#fff"`); real assignments keep them |
326
+ | Quotes | single-quoted strings become double-quoted unless the value contains an unescaped `"`; comments and values are untouched |
327
+ | Python blocks | assignment, comparison, arithmetic and bitwise operators inside `python:` blocks |
328
+ | Ruff (opt-in) | `--python` formats `python:` bodies with Ruff, changing more than the default rules |
329
+ | Blank lines | runs capped at 2; none at the start of the file |
330
+ | Block spacing | a blank line before top-level `label`, `screen`, `init`, `transform`; a comment block directly above stays attached |
331
+ | End of file | exactly one newline |
332
+ | Line endings | LF or CRLF is kept as found |
333
+ | Comments | re-indented to the block they sit in; a comment right after `label x:` joins the body |
334
+
335
+ String contents are never edited: a single-quoted literal is only rewritten to
336
+ double quotes when it contains no unescaped `"`, and quoted text in comments is
337
+ skipped. Indentation and alignment rules never touch dialogue, screen
338
+ properties or ATL lines; continuation lines of open brackets or backslashes are
339
+ shifted together with their first line, keeping their relative alignment.
340
+ Keyword and default-parameter `=` inside brackets is the only operator the
341
+ spacing pass tightens; top-level assignment `=` still gets spaces.
342
+
343
+ ### Known assumptions and limits
344
+
345
+ - A string opened with a single `"` or `'` that is still open at the end of a
346
+ line is assumed to continue on the next line. A stray, unbalanced quote
347
+ outside a string or comment can therefore make the rest of the file look like
348
+ string content; the formatter leaves that part alone instead of guessing.
349
+ - Inline spacing outside `python:` blocks is limited to assignment and
350
+ comparison operators. Arithmetic spacing is applied inside `python:` blocks,
351
+ but not to every Ren'Py expression. Use `--no-spacing` to disable the pass.
352
+ - Quote normalization is on by default. Use `--no-quotes` or
353
+ `normalize_quotes: false` to keep single quotes. Literals containing an
354
+ unescaped `"` and unterminated literals are left unchanged. Range formatting
355
+ converts a multi-line literal only when both delimiters are in the selected
356
+ lines, so it is never left half-converted.
357
+ - Diagnostics are intentionally conservative. Newer rules are opt-in, and each
358
+ rule can be reduced to a warning or disabled independently.
359
+ - Ruff formatting changes more than whitespace, so it is opt-in and failure-safe.
360
+
361
+ ## Development
362
+
363
+ ```bash
364
+ uv sync
365
+ uv run pytest
366
+ uv run ruff check . && uv run ruff format --check .
367
+ uv run pyright
368
+ ```
369
+
370
+ - `scanner.py`: tracks strings, comments and brackets across physical lines
371
+ - `analysis.py`: logical lines and the block tree
372
+ - `index.py`: workspace symbol index for Ren'Py declarations, references and
373
+ related declaration families
374
+ - `python_index.py`: conservative indentation parser for `python:` blocks
375
+ - `resolve.py`: shared target and occurrence resolution
376
+ - `references.py`: find-references provider
377
+ - `signature.py`: signature-help provider
378
+ - `completion.py`: context-aware completion for statements, screen bodies,
379
+ API namespaces, audio, dialogue attributes and Python expressions
380
+ - `completion_data.py` and `data/completion.json`: versioned screen, ATL and
381
+ keyword/value vocabulary
382
+ - `strings.py`: physical-line string spans shared by semantic tokens
383
+ - `semantic.py`: opt-in semantic tokens and UTF-16 delta encoding
384
+ - `definition.py`: literal `jump` / `call` target resolution
385
+ - `diagnostics.py`: configurable per-rule diagnostics and workspace publishing
386
+ - `code_actions.py`: safe quick fixes for diagnostics
387
+ - `symbols.py`: LSP document and workspace symbols
388
+ - `positions.py`: client-position conversion helpers
389
+ - `folding.py`: explicit block fold ranges
390
+ - `spacing.py`: inline operator spacing
391
+ - `python_format.py`: optional Ruff bridge
392
+ - `formatter.py`: indentation, blank lines, comments
393
+ - `edits.py`: old text to minimal LSP edits
394
+ - `server.py`: pygls glue (stdout carries the protocol, so log to stderr only)
395
+ - `dap.py`: launch-only Debug Adapter Protocol server, SDK/project discovery,
396
+ output streaming and process termination
397
+ - `tests/test_formatter_renpy.py`: formatter cases, idempotency and
398
+ "only whitespace changed" checks
399
+ - `tests/test_pipeline.py`: scanner, edits and CLI tests
400
+ - `tests/test_server_e2e.py`: starts the real server and formats over stdio
401
+ - `tests/test_dap.py`: launch configuration, SDK discovery and a fixture-SDK
402
+ DAP session over stdio
403
+
404
+ CI runs the test suite on Python 3.10 through 3.14.
405
+
406
+ ### Checked against real scripts
407
+
408
+ Run on six files from the `renpy/renpy` repository (`the_question`, `tutorial`,
409
+ `launcher`; about 4,000 lines): in every file only whitespace changed and a
410
+ second pass changed nothing. Four were already formatted and came out unchanged.
411
+
412
+ ## Roadmap
413
+
414
+ - [x] Diagnostics: indentation, tabs, filenames, variable names, comparisons,
415
+ obsolete APIs, labels, images, screens and persistent/store variables,
416
+ with per-rule severity and open/workspace modes
417
+ - [x] Document symbols and go to definition for labels, including local labels
418
+ - [x] Workspace-wide completion from an index
419
+ - [x] Completion contexts: labels, screens, images, transforms, transitions,
420
+ audio, API namespaces, screen bodies, dialogue attributes and Python
421
+ expressions
422
+ - [x] Opt-in semantic tokens
423
+ - [x] Launch-only DAP server (`renpy-language-server dap`) with SDK discovery and warp
424
+ - [x] Index Python classes, methods, fields, stores, persistent variables,
425
+ test cases and translations for outline and navigation
426
+ - [x] Inline spacing for Python-ish statements (`$ points=1` to `$ points = 1`)
427
+ - [x] Inline spacing inside `python:` blocks and around arithmetic operators
428
+ - [x] Format `python:` blocks with `ruff` (opt-in)
429
+ - [x] Range formatting
430
+ - [ ] Optional tree-sitter backend (decision pending)
431
+
432
+ ## License
433
+
434
+ MIT. See [LICENSE](LICENSE).