Skip to content

feat: add LSP support for superTOML#917

Open
Datron wants to merge 1 commit intomainfrom
claude/supertoml-lsp-server-6Mrbi
Open

feat: add LSP support for superTOML#917
Datron wants to merge 1 commit intomainfrom
claude/supertoml-lsp-server-6Mrbi

Conversation

@Datron
Copy link
Copy Markdown
Collaborator

@Datron Datron commented Mar 16, 2026

Problem

No LSP support for superTOML

Solution

Add LSP server and vscode/nvim plugins for superTOML

Summary by CodeRabbit

  • New Features

    • Added Language Server Protocol (LSP) support for Neovim, VS Code, and Zed editors, providing diagnostics, code completions, hover documentation, and syntax highlighting.
    • Updated TOML configuration syntax: replaced [[context]] blocks with [[overrides]] blocks and _condition_ with _context_ for improved clarity.
    • Enhanced error handling with improved TOML parsing diagnostics.
  • Documentation

    • Added comprehensive guides for editor integrations and configuration options.
    • Updated TOML examples to reflect syntax changes.

@Datron Datron requested a review from a team as a code owner March 16, 2026 10:20
@semanticdiff-com
Copy link
Copy Markdown

semanticdiff-com bot commented Mar 16, 2026

Review changes with  SemanticDiff

Changed Files
File Status
  crates/superposition_core/src/format/error.rs  65% smaller
  crates/superposition_core/src/format.rs  28% smaller
  Cargo.lock Unsupported file format
  Cargo.toml Unsupported file format
  clients/java/bindings/README_TOML_TESTS.md Unsupported file format
  clients/javascript/bindings/README_TOML_TESTS.md Unsupported file format
  crates/cac_toml/Cargo.lock Unsupported file format
  crates/superposition_core/src/format/json.rs  0% smaller
  crates/superposition_core/src/format/toml.rs  0% smaller
  crates/superposition_core/src/lib.rs  0% smaller
  examples/superposition_toml_example/README.md Unsupported file format
  examples/superposition_toml_example/src/main.rs  0% smaller
  tooling/lsp/neovim-extension/.gitignore Unsupported file format
  tooling/lsp/neovim-extension/README.md Unsupported file format
  tooling/lsp/neovim-extension/ftdetect/supertoml.lua  0% smaller
  tooling/lsp/neovim-extension/ftplugin/superTOML.lua  0% smaller
  tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua Unsupported file format
  tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua  0% smaller
  tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua  0% smaller
  tooling/lsp/neovim-extension/plugin/init.lua  0% smaller
  tooling/lsp/neovim-extension/queries/supertoml/highlights.scm Unsupported file format
  tooling/lsp/neovim-extension/queries/supertoml/injections.scm Unsupported file format
  tooling/lsp/neovim-extension/queries/supertoml/textobjects.scm Unsupported file format
  tooling/lsp/neovim-extension/syntax/superTOML.vim Unsupported file format
  tooling/lsp/supertoml-zed-extension/.gitignore Unsupported file format
  tooling/lsp/supertoml-zed-extension/Cargo.toml Unsupported file format
  tooling/lsp/supertoml-zed-extension/README.md Unsupported file format
  tooling/lsp/supertoml-zed-extension/extension.toml Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/brackets.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/config.toml Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/highlights.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/indents.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/injections.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/outline.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/overrides.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/redactions.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/languages/superTOML/textobjects.scm Unsupported file format
  tooling/lsp/supertoml-zed-extension/src/lib.rs  0% smaller
  tooling/lsp/supertoml_lsp/Cargo.toml Unsupported file format
  tooling/lsp/supertoml_lsp/src/backend.rs  0% smaller
  tooling/lsp/supertoml_lsp/src/completions.rs  0% smaller
  tooling/lsp/supertoml_lsp/src/diagnostics.rs  0% smaller
  tooling/lsp/supertoml_lsp/src/hover.rs  0% smaller
  tooling/lsp/supertoml_lsp/src/main.rs  0% smaller
  tooling/lsp/supertoml_lsp/src/utils.rs  0% smaller
  tooling/lsp/vscode-extension/.gitignore Unsupported file format
  tooling/lsp/vscode-extension/.vscodeignore Unsupported file format
  tooling/lsp/vscode-extension/README.md Unsupported file format
  tooling/lsp/vscode-extension/icons/toml-icon.svg  0% smaller
  tooling/lsp/vscode-extension/language-configuration.json  0% smaller
  tooling/lsp/vscode-extension/package-lock.json  0% smaller
  tooling/lsp/vscode-extension/package.json  0% smaller
  tooling/lsp/vscode-extension/src/extension.ts  0% smaller
  tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json  0% smaller
  tooling/lsp/vscode-extension/test.super.toml Unsupported file format
  tooling/lsp/vscode-extension/tsconfig.json  0% smaller

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 173bce44-c14e-4635-927f-5623e57c5d1c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request adds comprehensive Language Server Protocol (LSP) support for SuperTOML across three editors: Neovim, VS Code, and Zed. It includes a new Rust-based LSP server, complete editor-specific plugins, updated TOML configuration examples, and core Rust type refactoring to better handle error handling.

Changes

Cohort / File(s) Summary
Workspace Configuration
Cargo.toml
Added two new workspace members (tooling/lsp/server, tooling/lsp/zed-extension) and a workspace dependency (cac_toml).
Documentation Updates
clients/java/bindings/README_TOML_TESTS.md, clients/javascript/bindings/README_TOML_TESTS.md, examples/superposition_toml_example/README.md
Updated TOML examples from [[context]] blocks with _condition_ to [[overrides]] blocks with _context_ structure.
Core Type System
crates/superposition_core/src/toml.rs
Modified TomlError enum to hold toml::de::Error instead of String; removed Serialize/Deserialize derives; updated validation context keys from <dim>.schema to dimensions.<dim>.schema.
LSP Server Implementation
tooling/lsp/server/Cargo.toml, tooling/lsp/server/src/main.rs, tooling/lsp/server/src/backend.rs, tooling/lsp/server/src/completions.rs, tooling/lsp/server/src/diagnostics.rs, tooling/lsp/server/src/hover.rs, tooling/lsp/server/src/utils.rs
New Rust-based LSP server providing language features (completions with context-aware hints, diagnostics with precise error ranges, hover information) for SuperTOML with utility functions for comment parsing.
Neovim Extension Core
tooling/lsp/neovim-extension/.gitignore, tooling/lsp/neovim-extension/README.md, tooling/lsp/neovim-extension/ftdetect/supertoml.lua, tooling/lsp/neovim-extension/ftplugin/superTOML.lua, tooling/lsp/neovim-extension/plugin/init.lua
New Neovim plugin with filetype detection, buffer configuration, plugin initialization with auto-setup mechanism, and comprehensive documentation.
Neovim LSP Client & Config
tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua, tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua, tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua
Three-module Lua architecture: config module handles server path resolution and capability setup; init module provides lifecycle management (start/stop/restart/attach); lsp module manages client state, buffer attachment, and autocmd integration.
Neovim Syntax & Tree-sitter
tooling/lsp/neovim-extension/syntax/superTOML.vim, tooling/lsp/neovim-extension/queries/supertoml/highlights.scm, tooling/lsp/neovim-extension/queries/supertoml/injections.scm, tooling/lsp/neovim-extension/queries/supertoml/textobjects.scm
Added Vim syntax file with SuperTOML-specific extensions (variables, environment variables, functions, interpolations) and Tree-sitter query files for syntax highlighting, language injection detection, and text object selection.
VS Code Extension Setup
tooling/lsp/vscode-extension/.gitignore, tooling/lsp/vscode-extension/.vscodeignore, tooling/lsp/vscode-extension/tsconfig.json, tooling/lsp/vscode-extension/README.md, tooling/lsp/vscode-extension/package.json, tooling/lsp/vscode-extension/language-configuration.json
New VS Code extension manifest with metadata, activation events, language configuration (comments, brackets, auto-closing), build configuration, and documentation.
VS Code Extension Implementation
tooling/lsp/vscode-extension/src/extension.ts, tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json, tooling/lsp/vscode-extension/test.super.toml
TypeScript extension entry point managing LSP client lifecycle, server discovery, configuration reading, and logging; comprehensive TextMate grammar for syntax highlighting; test configuration example.
Zed Extension Core
tooling/lsp/zed-extension/.gitignore, tooling/lsp/zed-extension/Cargo.toml, tooling/lsp/zed-extension/README.md, tooling/lsp/zed-extension/extension.toml, tooling/lsp/zed-extension/src/lib.rs
New Rust-based Zed extension with manifest configuration, binary path resolution logic, and LSP initialization option setup.
Zed Language Configuration
tooling/lsp/zed-extension/languages/superTOML/config.toml, tooling/lsp/zed-extension/languages/superTOML/brackets.scm, tooling/lsp/zed-extension/languages/superTOML/highlights.scm, tooling/lsp/zed-extension/languages/superTOML/indents.scm, tooling/lsp/zed-extension/languages/superTOML/injections.scm, tooling/lsp/zed-extension/languages/superTOML/outline.scm, tooling/lsp/zed-extension/languages/superTOML/redactions.scm, tooling/lsp/zed-extension/languages/superTOML/textobjects.scm
Zed-specific language configuration including bracket pairing, indent rules, Tree-sitter query files for highlights, injections, outline extraction, redactions, and text object selection.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Editor as Editor<br/>(Neovim/VSCode/Zed)
    participant Plugin as Plugin<br/>Extension
    participant LSP as LSP Server
    participant Validator as Validator<br/>(parse_toml_config)

    User->>Editor: Opens *.super.toml file
    Editor->>Plugin: FileType/Activation Event
    Plugin->>Plugin: resolve server path
    Plugin->>LSP: Start Server Process
    
    User->>Editor: Types/modifies TOML
    Editor->>Plugin: didOpen/didChange
    Plugin->>LSP: Send document changes
    
    LSP->>Validator: parse_toml_config(text)
    Validator->>Validator: Syntax validation
    Validator->>Validator: Semantic validation<br/>(dimensions, overrides)
    Validator-->>LSP: Vec<TomlError>
    
    LSP->>LSP: Map errors to Diagnostics<br/>(compute_range, format messages)
    LSP-->>Plugin: Publish Diagnostics
    Plugin-->>Editor: Display error markers
    Editor-->>User: Show inline diagnostics

    User->>Editor: Trigger completion<br/>at cursor
    Editor->>Plugin: completion request
    Plugin->>LSP: Send completion request<br/>(text, position)
    
    LSP->>LSP: determine_cursor_context()
    LSP->>LSP: Parse TOML defensively
    LSP->>LSP: Generate context-specific<br/>completion items
    LSP-->>Plugin: CompletionResponse
    Plugin-->>Editor: Display completions
    Editor-->>User: Show completion menu

    User->>Editor: Hover over symbol
    Editor->>Plugin: hover request
    Plugin->>LSP: Send hover request<br/>(text, position)
    LSP->>LSP: Extract word under cursor
    LSP->>LSP: Lookup in [dimensions]<br/>or [default-configs]
    LSP-->>Plugin: Hover content<br/>(Markdown formatted)
    Plugin-->>Editor: Display hover tooltip
    Editor-->>User: Show info
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A new server hops in place,

Three editors join the race!

From Neovim to VS Code's embrace,

SuperTOML finds its rightful space.

Completions, diagnostics bright—

Language servers shining light! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add LSP support for superTOML' clearly and concisely summarizes the main change: implementing Language Server Protocol support for the superTOML format across multiple editor integrations.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/supertoml-lsp-server-6Mrbi
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

Migrating from UI to YAML configuration.

Use the @coderabbitai configuration command in a PR comment to get a dump of all your UI settings in YAML format. You can then edit this YAML file and upload it to the root of your repository to configure CodeRabbit programmatically.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (12)
tooling/lsp/neovim-extension/README.md-370-392 (1)

370-392: ⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The project structure code block should have a language specifier for proper rendering.

📝 Proposed fix
-```
+```text
 neovim-extension/
 ├── lua/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/README.md` around lines 370 - 392, The fenced
code block that shows the neovim-extension project tree is missing a language
specifier; update the README.md's block that starts with ``` and the tree under
"neovim-extension/" to use a plain text specifier (e.g., change ``` to ```text)
so the directory tree renders correctly; locate the README block containing the
ASCII tree under the neovim-extension header and add the specifier.
examples/superposition_toml_example/README.md-66-66 (1)

66-66: ⚠️ Potential issue | 🟡 Minor

Inconsistent reference in documentation.

The text mentions "adding them to the _overrides_ table" but the examples above use _context_ as the key within [[overrides]] blocks. This should likely read _context_ for consistency with the shown examples.

📝 Suggested fix
-Contexts define overrides that apply when specific dimension values are present. Multiple dimensions can be combined by adding them to the `_overrides_` table.
+Contexts define overrides that apply when specific dimension values are present. Multiple dimensions can be combined by adding them to the `_context_` table.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/superposition_toml_example/README.md` at line 66, The sentence
incorrectly refers to the `_overrides_` table; update the documentation to
reference the `_context_` key used in the examples instead. Edit the line that
currently reads "adding them to the `_overrides_` table" so it instead says
"adding them to the `_context_` key" (matching the `[[overrides]]` blocks and
the `_context_` symbol shown in the examples).
tooling/lsp/vscode-extension/test.super.toml-282-286 (1)

282-286: ⚠️ Potential issue | 🟡 Minor

Incorrect priority calculation in comment.

The comment states priority includes 2^7 (day_of_week at position 7), but day_of_week is not present in the _context_. The actual priority based on the dimensions in the context should be recalculated.

Present dimensions and their positions:

  • country (1): 2^1 = 2
  • city (2): 2^2 = 4
  • zone (3): 2^3 = 8
  • vehicle_type (4): 2^4 = 16
  • service_tier (5): 2^5 = 32
  • hour_of_day (6): 2^6 = 64
  • user_tier (8): 2^8 = 256
  • city_tier (10): 2^10 = 1024
  • experiment_group (13): 2^13 = 8192

Total: 2 + 4 + 8 + 16 + 32 + 64 + 256 + 1024 + 8192 = 9598

📝 Suggested fix
 # --- Maximum priority override: all dimensions combined ---
 # This demonstrates the maximum possible priority combination
-# Priority = 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^8 + 2^10 + 2^13 = 8278
+# Priority = 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 + 2^8 + 2^10 + 2^13 = 9598
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/test.super.toml` around lines 282 - 286, The
comment above the [[overrides]] block incorrectly lists the priority combination
(mentions 2^7/day_of_week and shows 8278); recalc and update it to reflect the
actual dimensions present in the _context_ (country, city, zone, vehicle_type,
service_tier, hour_of_day, user_tier, city_tier, experiment_group) and replace
the incorrect priority value with the correct total 9598 and remove the stray
reference to 2^7/day_of_week so the comment matches the _context_ keys.
crates/superposition_core/src/toml.rs-342-344 (1)

342-344: ⚠️ Potential issue | 🟡 Minor

Inconsistent error key path format across dimension types.

LocalCohort uses "dimensions.<dim>.schema" (line 342) but RemoteCohort (line 373) and Regular (line 399) still use "<dim>.schema". This inconsistency may confuse users and LSP diagnostics.

🔧 Proposed fix for consistency
 DimensionType::RemoteCohort(ref cohort_dim) => {
     // ...
     validations::validate_schema(&Value::from(&dim_info.schema))
         .map_err(|errors| TomlError::ValidationError {
-            key: format!("{}.schema", dim),
+            key: format!("dimensions.{}.schema", dim),
             errors: validations::format_validation_errors(&errors),
         })?;
     // ...
 }
 DimensionType::Regular {} => {
     validations::validate_schema(&Value::from(&dim_info.schema))
         .map_err(|errors| TomlError::ValidationError {
-            key: format!("{}.schema", dim),
+            key: format!("dimensions.{}.schema", dim),
             errors: validations::format_validation_errors(&errors),
         })?;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/superposition_core/src/toml.rs` around lines 342 - 344, The error key
path is inconsistent: LocalCohort sets key to format!("dimensions.{}.schema",
dim) but RemoteCohort and Regular still use format!("{}.schema", dim); update
the RemoteCohort and Regular error key constructions in
crates::superposition_core::toml.rs (the match arms/handlers that build the
error struct using errors: validations::format_validation_errors(&errors)) to
use the same "dimensions.<dim>.schema" format as LocalCohort so LSP diagnostics
and user-facing keys are consistent.
tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Remove leading dots from fileTypes extensions.

TextMate grammar fileTypes must list extensions without the leading dot. The current format may prevent proper file association.

🔧 Suggested fix
-  "fileTypes": [".super.toml", ".stoml"],
+  "fileTypes": ["super.toml", "stoml"],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json` at line 5,
The TextMate grammar's "fileTypes" array currently contains extensions with
leading dots (e.g., ".super.toml", ".stoml"); update the "fileTypes" array to
list extensions without leading dots (e.g., "super.toml", "stoml") so the editor
correctly associates files; locate the "fileTypes" entry in
superTOML.tmLanguage.json and remove the leading '.' characters from each
extension string.
tooling/lsp/vscode-extension/README.md-224-235 (1)

224-235: ⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced file-structure block.

Line 224 opens a fenced code block without a language, which triggers markdownlint MD040.

💡 Proposed fix
-```
+```text
 vscode-extension/
 ├── package.json              # Extension manifest
 ├── tsconfig.json             # TypeScript configuration
 ├── language-configuration.json # Language settings (brackets, comments)
 ├── syntaxes/
 │   └── superTOML.tmLanguage.json # TextMate grammar for syntax highlighting
 ├── src/
 │   └── extension.ts          # Main extension code
 ├── out/                      # Compiled JavaScript (generated)
 └── README.md                 # This file
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @tooling/lsp/vscode-extension/README.md around lines 224 - 235, The fenced
code block that lists the vscode-extension directory currently has no language
tag (triggering markdownlint MD040); update the README.md fenced block that
starts with "vscode-extension/" to include a language identifier (e.g., add
"text" after the opening ) so the block becomes text and resolves the lint
warning while preserving the directory listing content.


</details>

</blockquote></details>
<details>
<summary>tooling/lsp/zed-extension/README.md-102-109 (1)</summary><blockquote>

`102-109`: _⚠️ Potential issue_ | _🟡 Minor_

**Add a language tag to the fenced file-structure block.**

Line 102 starts a fenced block without a language identifier (markdownlint MD040).


<details>
<summary>💡 Proposed fix</summary>

```diff
-```
+```text
 zed-extension/
 ├── extension.toml    # Extension metadata and language configuration
 ├── Cargo.toml        # Rust crate configuration
 ├── src/
 │   └── lib.rs        # Extension implementation
 └── README.md         # This file
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @tooling/lsp/zed-extension/README.md around lines 102 - 109, The fenced
directory-tree block in README.md (the block showing "zed-extension/ ├──
extension.toml ...") lacks a language tag which triggers markdownlint MD040;
update that fenced code block in README.md to include a language identifier
(e.g., add "text" after the opening ) so the block becomes text and
retains the same content for the zed-extension/ tree.


</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua-131-143 (1)</summary><blockquote>

`131-143`: _⚠️ Potential issue_ | _🟡 Minor_

**Return value is unreliable due to async deferral.**

The function returns `M.client_id` immediately, but the actual restart happens asynchronously in `vim.defer_fn`. The returned value will be `nil` (since `stop()` clears it) or stale, not the new client ID.


<details>
<summary>🐛 Proposed fix - return nil and document async behavior, or use callback</summary>

```diff
 --- Restart the LSP server
----@return integer|nil client_id
+---@return nil
 function M.restart()
     M.stop()

     -- Small delay to ensure clean shutdown
     vim.defer_fn(function()
         M.start()
         M.attach_to_buffers()
     end, 100)

-    return M.client_id
+    -- Restart is async; client_id will be set after defer_fn completes
+    return nil
 end
```

Alternatively, accept a callback parameter to notify when restart completes.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua` around lines 131
- 143, The restart function returns M.client_id immediately even though the new
client is created asynchronously in vim.defer_fn, so update M.restart to either
(a) document and return nil to reflect asynchronous behavior, or (b) accept a
callback or completion handler and invoke it from inside the vim.defer_fn after
M.start()/M.attach_to_buffers() with the new M.client_id (or resolve a returned
promise-like object); refer to M.restart, M.stop, M.start, M.attach_to_buffers
and M.client_id when making the change so the caller receives the correct
post-restart client identifier or a clear async contract.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/plugin/init.lua-10-21 (1)</summary><blockquote>

`10-21`: _⚠️ Potential issue_ | _🟡 Minor_

**`cpoptions` not restored on early returns.**

If the script exits early at lines 6 or 20, the saved `cpoptions` value won't be restored. This could leave Neovim in an unexpected compatibility mode.


<details>
<summary>🐛 Proposed fix - restore before early returns</summary>

```diff
 -- Save compatibility options
 local save_cpo = vim.o.cpoptions
 vim.o.cpoptions = ""

 -- Check for Neovim version compatibility
 if vim.fn.has("nvim-0.9") == 0 then
     vim.notify(
         "[supertoml-analyzer] This plugin requires Neovim 0.9 or later.",
         vim.log.levels.ERROR
     )
+    vim.o.cpoptions = save_cpo
     return
 end
```

Or move the version check before modifying `cpoptions`.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/plugin/init.lua` around lines 10 - 21, The code
sets save_cpo = vim.o.cpoptions then clears vim.o.cpoptions but doesn't restore
it on early exit; ensure vim.o.cpoptions is restored from save_cpo before any
early return (e.g., right before the notify+return when vim.fn.has("nvim-0.9")
== 0) or alternatively move the version check (vim.fn.has("nvim-0.9")) to run
before saving/modifying cpoptions; update all return paths to restore
vim.o.cpoptions using save_cpo so the original cpoptions value is always
reinstated.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua-148-156 (1)</summary><blockquote>

`148-156`: _⚠️ Potential issue_ | _🟡 Minor_

**Incorrect check for existing sign definition.**

`vim.fn.sign_getdefined(name)` returns a list of sign definitions, not a single table. The current check `vim.fn.sign_getdefined(name).text == nil` will always be truthy because a list doesn't have a `.text` property.


<details>
<summary>🐛 Proposed fix</summary>

```diff
     for _, sign in ipairs(signs) do
         local name = "DiagnosticSign" .. sign
-        if vim.fn.sign_getdefined(name).text == nil then
+        local existing = vim.fn.sign_getdefined(name)
+        if `#existing` == 0 then
             vim.fn.sign_define(name, {
                 text = M.get_diagnostic_sign_text(sign),
                 texthl = name,
                 numhl = "",
             })
         end
     end
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua` around lines
148 - 156, The check for existing sign definitions is wrong because
vim.fn.sign_getdefined(name) returns a list; replace the current condition
(vim.fn.sign_getdefined(name).text == nil) with a proper emptiness check such as
vim.tbl_isempty(vim.fn.sign_getdefined(name)) or testing the first element
(vim.fn.sign_getdefined(name)[1] == nil) before calling vim.fn.sign_define;
update the block around the local name = "DiagnosticSign" .. sign and calls to
vim.fn.sign_getdefined and vim.fn.sign_define (and still use
M.get_diagnostic_sign_text(sign) for the text) so signs are only defined when no
existing definition is returned.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua-306-312 (1)</summary><blockquote>

`306-312`: _⚠️ Potential issue_ | _🟡 Minor_

**Format-on-save condition uses wrong config flag.**

The format-on-save logic checks `cfg.diagnostics.enable`, but formatting is conceptually separate from diagnostics. Consider adding a dedicated `format_on_save` configuration option, and making this opt-in since automatic formatting is opinionated.


<details>
<summary>🔧 Suggested approach</summary>

Add to `SuperTOMLConfig`:
```lua
format_on_save = {
    enable = false,  -- Opt-in by default
}
```

Then check:
```diff
-            if cfg.diagnostics.enable then
+            if cfg.format_on_save and cfg.format_on_save.enable then
                 vim.lsp.buf.format({ bufnr = bufnr, timeout_ms = 1000 })
             end
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua` around lines 306
- 312, The format-on-save callback currently checks cfg.diagnostics.enable but
formatting should be gated by a dedicated option; add a new format_on_save table
to the SuperTOMLConfig (e.g., format_on_save = { enable = false }) and update
the callback in lsp.lua to read cfg.format_on_save.enable instead of
cfg.diagnostics.enable so vim.lsp.buf.format({ bufnr = bufnr, timeout_ms = 1000
}) only runs when the new opt-in flag is true.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/plugin/init.lua-29-34 (1)</summary><blockquote>

`29-34`: _⚠️ Potential issue_ | _🟡 Minor_

**Security concern: `vim.fn.eval` can execute arbitrary code.**

Using `vim.fn.eval` to parse command arguments could allow code execution if a user passes malicious input. Consider using `vim.json.decode` or a safer parsing method, or simply document that the command accepts Lua table syntax.


<details>
<summary>🛡️ Safer alternative using loadstring with sandboxing</summary>

```diff
     if opts.args and opts.args ~= "" then
-        local ok, parsed = pcall(vim.fn.eval, opts.args)
-        if ok and type(parsed) == "table" then
-            config = parsed
+        -- Only accept simple table literals
+        local chunk, err = loadstring("return " .. opts.args)
+        if chunk then
+            -- Sandbox the execution
+            setfenv(chunk, {})
+            local ok, parsed = pcall(chunk)
+            if ok and type(parsed) == "table" then
+                config = parsed
+            end
         end
     end
```

Alternatively, document that this command is intended for trusted interactive use only.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/plugin/init.lua` around lines 29 - 34, The
current parsing of command arguments uses vim.fn.eval on opts.args (in the code
handling opts.args) which can execute arbitrary code; replace this with a safe
parser such as vim.json.decode (or a restricted sandboxed parser) and validate
the result is a table before assigning to config, or explicitly document that
opts.args must be trusted Lua table syntax; specifically stop calling
vim.fn.eval with untrusted input, parse opts.args with vim.json.decode (or
another safe decode) and keep the type check for table before setting config.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (12)</summary><blockquote>

<details>
<summary>tooling/lsp/vscode-extension/.vscodeignore (1)</summary><blockquote>

`10-13`: **Minor redundancy in ignore patterns.**

Lines 10-11 have overlapping patterns (`**/node_modules/**` already covers `node_modules/**`). Similarly, lines 4-5 (`**/*.ts`, `**/*.map`) already cover the more specific patterns on lines 12-13 (`out/**/*.ts`, `out/**/*.map`).


<details>
<summary>✨ Suggested cleanup</summary>

```diff
 **/node_modules/**
-node_modules/**
 out/**/*.ts
-out/**/*.map
```

Or alternatively, keep only the broader patterns and remove lines 12-13.
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/.vscodeignore` around lines 10 - 13, Remove
redundant ignore patterns: keep the broad patterns (**/node_modules/** and
**/*.ts, **/*.map) and delete the overlapping specific entries (node_modules/**,
out/**/*.ts, out/**/*.map) so the .vscodeignore only contains the broader rules;
adjust so only **/node_modules/** and the generic **/*.ts and **/*.map remain.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/vscode-extension/src/extension.ts (1)</summary><blockquote>

`171-187`: **Consider making `checkOnSave` configurable.**

The `checkOnSave.enable` is hardcoded to `true` while other options like `diagnostics`, `completions`, and `hover` are configurable via user settings. Consider adding a corresponding configuration option in `package.json` for consistency.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/src/extension.ts` around lines 171 - 187,
getInitializationOptions currently hardcodes checkOnSave.enable to true while
other features read user prefs; make checkOnSave configurable by adding a new
setting (e.g., "checkOnSave.enable") in package.json and update
getInitializationOptions to read
workspace.getConfiguration('superTOML').get<boolean>('checkOnSave.enable', true)
(or similar) so checkOnSave.enable mirrors diagnostics/completions/hover and
uses a sensible default.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/vscode-extension/package.json (1)</summary><blockquote>

`25-27`: **Consider removing redundant `activationEvents`.**

Since VS Code 1.74+, `activationEvents` can be automatically inferred from `contributes.languages`. The explicit `onLanguage:superTOML` event is redundant when the language is already declared in `contributes.languages`.



<details>
<summary>♻️ Proposed simplification</summary>

```diff
-  "activationEvents": [
-    "onLanguage:superTOML"
-  ],
   "main": "./out/extension.js",
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/package.json` around lines 25 - 27, Remove the
redundant explicit activationEvents entry "onLanguage:superTOML" since VS Code
>=1.74 can infer activation from contributes.languages; locate the
"activationEvents" array in package.json and delete the "onLanguage:superTOML"
item (or remove the entire activationEvents key if it only contains that entry),
keeping contributes.languages unchanged.
```

</details>

</blockquote></details>
<details>
<summary>crates/superposition_core/src/toml.rs (1)</summary><blockquote>

`545-546`: **Simplify closure to method reference.**

The closure `|e| TomlError::TomlSyntaxError(e)` can be simplified.


<details>
<summary>✨ Suggested simplification</summary>

```diff
     let detailed_toml_config = toml::from_str::<DetailedConfigToml>(toml_str)
-        .map_err(|e| TomlError::TomlSyntaxError(e))?;
+        .map_err(TomlError::TomlSyntaxError)?;
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@crates/superposition_core/src/toml.rs` around lines 545 - 546, The closure
passed to map_err can be replaced with a direct function pointer; change the
call that constructs detailed_toml_config (the
toml::from_str::<DetailedConfigToml>(toml_str).map_err(|e|
TomlError::TomlSyntaxError(e))?) to use map_err(TomlError::TomlSyntaxError) so
the code is simpler and equivalent while still producing the same TomlError
variant.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/server/src/main.rs (1)</summary><blockquote>

`9-14`: **Consider adding logging initialization for debugging.**

LSP servers benefit from logging for debugging client-server communication issues. While optional, adding `tracing` or `env_logger` initialization would help with troubleshooting.


<details>
<summary>💡 Optional: Add tracing support</summary>

```diff
+use tracing_subscriber::EnvFilter;
+
 #[tokio::main]
 async fn main() {
+    // Initialize logging (writes to stderr, not interfering with LSP on stdin/stdout)
+    tracing_subscriber::fmt()
+        .with_env_filter(EnvFilter::from_default_env())
+        .with_writer(std::io::stderr)
+        .init();
+
     let stdin = tokio::io::stdin();
     let stdout = tokio::io::stdout();
     let (service, socket) = LspService::new(backend::Backend::new);
     Server::new(stdin, stdout, socket).serve(service).await;
 }
```

This requires adding `tracing` and `tracing-subscriber` to `Cargo.toml`:
```toml
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/server/src/main.rs` around lines 9 - 14, Initialize
logging/tracing at the start of main before creating the LSP service: add
tracing and tracing-subscriber (or env_logger) to Cargo.toml, then in async fn
main call a tracing subscriber initializer (e.g., configure env-filter from
RUST_LOG and call tracing_subscriber::fmt::init() or build a subscriber with
tracing_subscriber::fmt().with_env_filter(...).init()) prior to invoking
LspService::new(backend::Backend::new) and Server::new(...).serve(...).await so
all backend::Backend and Server logs are emitted for debugging.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/zed-extension/README.md (1)</summary><blockquote>

`3-3`: **Clarify scope in the intro sentence.**

Line 3 says “support for TOML files,” which can imply full generic TOML coverage. Consider explicitly saying SuperTOML files to set correct expectations.


<details>
<summary>💡 Proposed wording</summary>

```diff
-A Zed editor extension that provides language server support for TOML files using the `supertoml-analyzer` LSP server.
+A Zed editor extension that provides language server support for SuperTOML files using the `supertoml-analyzer` LSP server.
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/zed-extension/README.md` at line 3, Update the README intro
sentence to clarify scope by specifying that the extension provides language
server support for SuperTOML files rather than generic TOML; locate the sentence
referencing "TOML files" in the Zed editor extension README (the line mentioning
`supertoml-analyzer`) and change it to explicitly say "SuperTOML files" or
similar wording that ties it to the SuperTOML format/project to avoid implying
full generic TOML coverage.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/ftplugin/superTOML.lua (1)</summary><blockquote>

`82-83`: **Use `vim.uv or vim.loop` for Neovim version compatibility.**

Line 82 hardcodes `vim.loop`, which is deprecated as of Neovim 0.10. Use `vim.uv or vim.loop` to support both Neovim 0.9.x and 0.10+ without version-specific breakage.

<details>
<summary>Suggested fix</summary>

```diff
-local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(bufnr))
+local uv = vim.uv or vim.loop
+local ok, stats = pcall(uv.fs_stat, vim.api.nvim_buf_get_name(bufnr))
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/ftplugin/superTOML.lua` around lines 82 - 83,
Replace the hardcoded use of vim.loop with a compatibility alias (e.g., local uv
= vim.uv or vim.loop) and call uv.fs_stat(...) instead of vim.loop.fs_stat so
the fs_stat call in the block that uses vim.api.nvim_buf_get_name(bufnr), ok,
stats and max_filesize works on both Neovim 0.9.x and 0.10+; update the line
using vim.loop.fs_stat to use the uv alias (uv.fs_stat) while leaving the
surrounding logic (pcall, vim.api.nvim_buf_get_name, bufnr, stats.size >
max_filesize) unchanged.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua (2)</summary><blockquote>

`173-190`: **Global diagnostic config may override user settings.**

`vim.diagnostic.config()` applies globally and will override any existing diagnostic configuration the user may have set. Consider making this opt-in via a configuration option, or document this behavior clearly.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua` around lines
173 - 190, The current M.setup_diagnostics function unconditionally calls
vim.diagnostic.config which overwrites global user diagnostics; change it to
respect a module configuration option (e.g., a flag like
config.enable_diagnostics_setup or similar) or provide an opt-in parameter to
M.setup_diagnostics, and only call vim.diagnostic.config when that
flag/parameter is true; update the function signature and any initialization
paths that call M.setup_diagnostics so consumers can opt in (or document the
behavior) and ensure you reference M.setup_diagnostics and the
vim.diagnostic.config call when making the conditional guard.
```

</details>

---

`77-80`: **Use `vim.bo` instead of deprecated `nvim_buf_get_option`/`nvim_buf_set_option`.**

These functions are deprecated since Neovim 0.9. Use the `vim.bo[bufnr]` accessor instead.


<details>
<summary>♻️ Proposed fix</summary>

```diff
     callback = function(args)
-        local current_ft = vim.api.nvim_buf_get_option(args.buf, "filetype")
+        local current_ft = vim.bo[args.buf].filetype
         if current_ft == "" or current_ft == "toml" then
-            vim.api.nvim_buf_set_option(args.buf, "filetype", "superTOML")
+            vim.bo[args.buf].filetype = "superTOML"
         end
     end,
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua` around lines 77
- 80, Replace deprecated buffer option APIs by reading and writing the filetype
through the buffer-local table accessor: read the current filetype from
vim.bo[args.buf].filetype (instead of vim.api.nvim_buf_get_option) into
current_ft and set it via vim.bo[args.buf].filetype = "superTOML" (instead of
vim.api.nvim_buf_set_option). Ensure you still check for empty string or "toml"
before assigning, using the existing variables (current_ft and args.buf) to
locate the change.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua (1)</summary><blockquote>

`180-182`: **Consider making `checkOnSave` configurable.**

The `checkOnSave` option is hardcoded to `enable = true`. If this is intentional, it's fine, but consider exposing it through the configuration for consistency with other options.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua` around lines
180 - 182, The checkOnSave setting is hardcoded (checkOnSave = { enable = true
})—make it configurable by reading a user-provided option (e.g.,
options.checkOnSave or config.checkOnSave) with a sensible default of true;
update the configuration table where checkOnSave is defined to use that option,
and ensure any code that references checkOnSave (the checkOnSave table or its
enable flag) uses the new configurable value so behavior remains
backward-compatible.
```

</details>

</blockquote></details>
<details>
<summary>tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua (2)</summary><blockquote>

`341-352`: **Use `vim.bo` instead of deprecated `nvim_buf_set_option`.**

Same as noted earlier, `nvim_buf_set_option` is deprecated since Neovim 0.9.


<details>
<summary>♻️ Proposed fix</summary>

```diff
     callback = function(args)
         if M.is_super_toml_buffer(args.buf) then
-            vim.api.nvim_buf_set_option(args.buf, "filetype", M.get_filetype())
+            vim.bo[args.buf].filetype = M.get_filetype()
         end
     end,
 })

 -- Auto-attach to newly created SuperTOML files
 vim.api.nvim_create_autocmd("BufNewFile", {
     group = M.augroup,
     pattern = M.file_patterns,
     desc = "Set filetype for new SuperTOML files",
     callback = function(args)
-        vim.api.nvim_buf_set_option(args.buf, "filetype", M.get_filetype())
+        vim.bo[args.buf].filetype = M.get_filetype()
     end,
 })
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua` around lines 341
- 352, Replace deprecated vim.api.nvim_buf_set_option usage with the
buffer-local API (vim.bo) when setting the filetype: in the BufNewFile autocmd
callback (and the earlier autocmd callback above) stop calling
vim.api.nvim_buf_set_option(args.buf, "filetype", M.get_filetype()) and instead
assign the option via the buffer-local table using args.buf (e.g., set
vim.bo[<buffer>].filetype = M.get_filetype()); update both places where args.buf
and M.get_filetype() are used so the filetype is set using vim.bo rather than
the deprecated nvim_buf_set_option.
```

</details>

---

`265-294`: **Consider making keymaps configurable or opt-in.**

The plugin sets buffer-local keymaps unconditionally, which may override user customizations. Consider adding a configuration option like `keymaps = { enable = true }` or allowing users to provide their own keymap table.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua` around lines 265
- 294, The current M.setup_buffer_keymaps(bufnr) unconditionally sets
buffer-local keymaps and can override user mappings; change it to respect a
configuration table (e.g., a module-level config.keymaps.enable boolean and
optional config.keymaps.bindings table) so keymaps are only applied when enabled
and allow users to supply/merge their own mapping table; update
M.setup_buffer_keymaps to accept or read the config, check config.keymaps.enable
before calling vim.keymap.set, and if config.keymaps.bindings is present iterate
that table (falling back to the default mappings) to set each mapping so users
can opt-in or override defaults.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua:

  • Line 1: Remove the stray file path string on the first line of the module (the
    literal
    "superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua")
    so the Lua file parses correctly; open the module (config.lua) and delete that
    top-line path entry, ensuring the file begins with the intended Lua code (e.g.,
    local config = ... or return {...}) and that any module-level symbols like the
    config table or return statement remain intact.

In @tooling/lsp/neovim-extension/queries/supertoml/highlights.scm:

  • Around line 8-27: The Tree-sitter queries for table headers incorrectly
    require both (bare_key) and (dotted_key) as children, so patterns like the
    (table) and (table_array_element) captures never match; update each
    table/table_array_element pattern (the ones capturing @namespace and
    @punctuation.bracket) to use alternation for the key child — e.g., replace
    sequences requiring both (bare_key) and (dotted_key) with a single bracketed
    alternative that includes (bare_key) (dotted_key) and (quoted_key) as
    appropriate — so the @namespace capture matches any single key child and the
    bracket captures remain on the surrounding "["/"]" or "[["/"]]".

In @tooling/lsp/neovim-extension/queries/supertoml/injections.scm:

  • Around line 5-27: The string capture @injection.content includes surrounding
    quotes so anchoring patterns like ^ match the delimiter instead of the payload;
    for each injection rule that uses ((string) @injection.content ...) (the ones
    setting injection.language to "bash", "regex", "json", and the two "lua" rules),
    add an offset directive to unwrap the quotes by inserting #offset!
    @injection.content 0 1 0 -1 inside the rule so the regex matches the inner
    content rather than the delimiters.

In @tooling/lsp/server/src/backend.rs:

  • Around line 31-56: The initialize handler currently does not negotiate
    position encoding, causing UTF-16/byte-offset mismatches; update async fn
    initialize(&self, params: InitializeParams) to inspect
    params.capabilities.general.position_encodings (or the equivalent capability
    path) and choose an encoding: prefer PositionEncodingKind::UTF8 if the client
    advertises it, otherwise fall back to PositionEncodingKind::UTF16; store the
    chosen encoding in the server state and set ServerCapabilities.position_encoding
    to that choice so clients know what the server uses; ensure callers that rely on
    byte offsets (e.g., byte_offset_to_position, hover.rs, utils.rs, diagnostics.rs)
    either read the stored encoding and convert between byte offsets and LSP
    positions accordingly or are updated later to expect the negotiated encoding.

In @tooling/lsp/server/src/diagnostics.rs:

  • Around line 143-145: The code slices intermediate segments into sub_nesting
    but only uses sub_nesting[0], so nested TOML paths like
    dimensions.peak_hours.schema.type stop one level early; update the resolver that
    references sub_nesting to iteratively traverse each segment in sub_nesting
    (e.g., starting from the root table/value, loop over each segment name, descend
    into its table/inline table or array element as appropriate, and update the
    current context) rather than indexing [0], and fall back gracefully when a key
    is missing; apply the same change to the other resolver occurrences noted (the
    blocks around the other sub_nesting uses).

In @tooling/lsp/server/src/hover.rs:

  • Around line 20-23: The code currently does a hard early return using
    toml::from_str(text).ok()? which disables hover on any TOML syntax error; change
    this to a best-effort approach by capturing the parse result as an Option (e.g.
    let raw: Optiontoml::Table = toml::from_str(text).ok();) instead of using the
    ? operator, and update subsequent uses of raw in hover.rs to handle None
    gracefully (mirror the pattern used in completions.rs) so hover can still
    operate using the raw buffer or partial data when parsing fails.

In @tooling/lsp/vscode-extension/src/extension.ts:

  • Around line 132-163: The workspace/extension path discovery builds
    possiblePaths without Windows executables, so on Windows the searches miss the
    real binary; update the code that populates possiblePaths (the blocks
    referencing workspace.workspaceFolders and extensionRoot) to also include the
    '.exe' variant when process.platform === 'win32' (or push both with and without
    '.exe'), ensuring the same Windows-handling used in the PATH fallback is applied
    to the checks that use possiblePaths and resolvedPath.

In @tooling/lsp/zed-extension/languages/superTOML/redactions.scm:

  • Line 1: The current S-expression rule (pair (bare_key) "=" (_) @redact)
    redacts all TOML values; restrict it by adding a #match? conditional on the
    bare_key so only known sensitive keys are redacted (e.g.,
    "password","secret","api_key","token" or the project's canonical sensitive-key
    list). Locate the pattern that uses bare_key and @redact and wrap the value
    capture with a conditional check (using #match? against the sensitive-key regex
    or list) so non-sensitive pairs are not assigned @redact while preserving
    existing token names (bare_key, @redact).

In @tooling/lsp/zed-extension/src/lib.rs:

  • Around line 20-34: Replace the hard-coded machine-specific path by resolving
    the analyzer binary at runtime: remove the literal "/Users/..." string in the
    Command returned and instead look up the bundled/packaged binary (via
    environment/config, PATH lookup, or extension resource loading) and populate
    self.cached_binary_path when found; if resolution fails return an Err (or a
    clear failure Command) rather than trying to spawn a non-existent path. Update
    the code that constructs Command (the block that currently returns Command {
    command: "...".to_string(), ... }) to use the resolved path and ensure
    cached_binary_path is set so the cache branch is exercised.

Minor comments:
In @crates/superposition_core/src/toml.rs:

  • Around line 342-344: The error key path is inconsistent: LocalCohort sets key
    to format!("dimensions.{}.schema", dim) but RemoteCohort and Regular still use
    format!("{}.schema", dim); update the RemoteCohort and Regular error key
    constructions in crates::superposition_core::toml.rs (the match arms/handlers
    that build the error struct using errors:
    validations::format_validation_errors(&errors)) to use the same
    "dimensions..schema" format as LocalCohort so LSP diagnostics and
    user-facing keys are consistent.

In @examples/superposition_toml_example/README.md:

  • Line 66: The sentence incorrectly refers to the _overrides_ table; update
    the documentation to reference the _context_ key used in the examples instead.
    Edit the line that currently reads "adding them to the _overrides_ table" so
    it instead says "adding them to the _context_ key" (matching the
    [[overrides]] blocks and the _context_ symbol shown in the examples).

In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua:

  • Around line 148-156: The check for existing sign definitions is wrong because
    vim.fn.sign_getdefined(name) returns a list; replace the current condition
    (vim.fn.sign_getdefined(name).text == nil) with a proper emptiness check such as
    vim.tbl_isempty(vim.fn.sign_getdefined(name)) or testing the first element
    (vim.fn.sign_getdefined(name)[1] == nil) before calling vim.fn.sign_define;
    update the block around the local name = "DiagnosticSign" .. sign and calls to
    vim.fn.sign_getdefined and vim.fn.sign_define (and still use
    M.get_diagnostic_sign_text(sign) for the text) so signs are only defined when no
    existing definition is returned.

In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua:

  • Around line 131-143: The restart function returns M.client_id immediately even
    though the new client is created asynchronously in vim.defer_fn, so update
    M.restart to either (a) document and return nil to reflect asynchronous
    behavior, or (b) accept a callback or completion handler and invoke it from
    inside the vim.defer_fn after M.start()/M.attach_to_buffers() with the new
    M.client_id (or resolve a returned promise-like object); refer to M.restart,
    M.stop, M.start, M.attach_to_buffers and M.client_id when making the change so
    the caller receives the correct post-restart client identifier or a clear async
    contract.
  • Around line 306-312: The format-on-save callback currently checks
    cfg.diagnostics.enable but formatting should be gated by a dedicated option; add
    a new format_on_save table to the SuperTOMLConfig (e.g., format_on_save = {
    enable = false }) and update the callback in lsp.lua to read
    cfg.format_on_save.enable instead of cfg.diagnostics.enable so
    vim.lsp.buf.format({ bufnr = bufnr, timeout_ms = 1000 }) only runs when the new
    opt-in flag is true.

In @tooling/lsp/neovim-extension/plugin/init.lua:

  • Around line 10-21: The code sets save_cpo = vim.o.cpoptions then clears
    vim.o.cpoptions but doesn't restore it on early exit; ensure vim.o.cpoptions is
    restored from save_cpo before any early return (e.g., right before the
    notify+return when vim.fn.has("nvim-0.9") == 0) or alternatively move the
    version check (vim.fn.has("nvim-0.9")) to run before saving/modifying cpoptions;
    update all return paths to restore vim.o.cpoptions using save_cpo so the
    original cpoptions value is always reinstated.
  • Around line 29-34: The current parsing of command arguments uses vim.fn.eval
    on opts.args (in the code handling opts.args) which can execute arbitrary code;
    replace this with a safe parser such as vim.json.decode (or a restricted
    sandboxed parser) and validate the result is a table before assigning to config,
    or explicitly document that opts.args must be trusted Lua table syntax;
    specifically stop calling vim.fn.eval with untrusted input, parse opts.args with
    vim.json.decode (or another safe decode) and keep the type check for table
    before setting config.

In @tooling/lsp/neovim-extension/README.md:

  • Around line 370-392: The fenced code block that shows the neovim-extension
    project tree is missing a language specifier; update the README.md's block that
    starts with and the tree under "neovim-extension/" to use a plain text specifier (e.g., change to ```text) so the directory tree renders correctly;
    locate the README block containing the ASCII tree under the neovim-extension
    header and add the specifier.

In @tooling/lsp/vscode-extension/README.md:

  • Around line 224-235: The fenced code block that lists the vscode-extension
    directory currently has no language tag (triggering markdownlint MD040); update
    the README.md fenced block that starts with "vscode-extension/" to include a
    language identifier (e.g., add "text" after the opening ) so the block becomes text and resolves the lint warning while preserving the directory
    listing content.

In @tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json:

  • Line 5: The TextMate grammar's "fileTypes" array currently contains extensions
    with leading dots (e.g., ".super.toml", ".stoml"); update the "fileTypes" array
    to list extensions without leading dots (e.g., "super.toml", "stoml") so the
    editor correctly associates files; locate the "fileTypes" entry in
    superTOML.tmLanguage.json and remove the leading '.' characters from each
    extension string.

In @tooling/lsp/vscode-extension/test.super.toml:

  • Around line 282-286: The comment above the [[overrides]] block incorrectly
    lists the priority combination (mentions 2^7/day_of_week and shows 8278); recalc
    and update it to reflect the actual dimensions present in the context
    (country, city, zone, vehicle_type, service_tier, hour_of_day, user_tier,
    city_tier, experiment_group) and replace the incorrect priority value with the
    correct total 9598 and remove the stray reference to 2^7/day_of_week so the
    comment matches the context keys.

In @tooling/lsp/zed-extension/README.md:

  • Around line 102-109: The fenced directory-tree block in README.md (the block
    showing "zed-extension/ ├── extension.toml ...") lacks a language tag which
    triggers markdownlint MD040; update that fenced code block in README.md to
    include a language identifier (e.g., add "text" after the opening ) so the block becomes text and retains the same content for the zed-extension/ tree.

Nitpick comments:
In @crates/superposition_core/src/toml.rs:

  • Around line 545-546: The closure passed to map_err can be replaced with a
    direct function pointer; change the call that constructs detailed_toml_config
    (the toml::from_str::(toml_str).map_err(|e|
    TomlError::TomlSyntaxError(e))?) to use map_err(TomlError::TomlSyntaxError) so
    the code is simpler and equivalent while still producing the same TomlError
    variant.

In @tooling/lsp/neovim-extension/ftplugin/superTOML.lua:

  • Around line 82-83: Replace the hardcoded use of vim.loop with a compatibility
    alias (e.g., local uv = vim.uv or vim.loop) and call uv.fs_stat(...) instead of
    vim.loop.fs_stat so the fs_stat call in the block that uses
    vim.api.nvim_buf_get_name(bufnr), ok, stats and max_filesize works on both
    Neovim 0.9.x and 0.10+; update the line using vim.loop.fs_stat to use the uv
    alias (uv.fs_stat) while leaving the surrounding logic (pcall,
    vim.api.nvim_buf_get_name, bufnr, stats.size > max_filesize) unchanged.

In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua:

  • Around line 180-182: The checkOnSave setting is hardcoded (checkOnSave = {
    enable = true })—make it configurable by reading a user-provided option (e.g.,
    options.checkOnSave or config.checkOnSave) with a sensible default of true;
    update the configuration table where checkOnSave is defined to use that option,
    and ensure any code that references checkOnSave (the checkOnSave table or its
    enable flag) uses the new configurable value so behavior remains
    backward-compatible.

In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua:

  • Around line 173-190: The current M.setup_diagnostics function unconditionally
    calls vim.diagnostic.config which overwrites global user diagnostics; change it
    to respect a module configuration option (e.g., a flag like
    config.enable_diagnostics_setup or similar) or provide an opt-in parameter to
    M.setup_diagnostics, and only call vim.diagnostic.config when that
    flag/parameter is true; update the function signature and any initialization
    paths that call M.setup_diagnostics so consumers can opt in (or document the
    behavior) and ensure you reference M.setup_diagnostics and the
    vim.diagnostic.config call when making the conditional guard.
  • Around line 77-80: Replace deprecated buffer option APIs by reading and
    writing the filetype through the buffer-local table accessor: read the current
    filetype from vim.bo[args.buf].filetype (instead of vim.api.nvim_buf_get_option)
    into current_ft and set it via vim.bo[args.buf].filetype = "superTOML" (instead
    of vim.api.nvim_buf_set_option). Ensure you still check for empty string or
    "toml" before assigning, using the existing variables (current_ft and args.buf)
    to locate the change.

In @tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua:

  • Around line 341-352: Replace deprecated vim.api.nvim_buf_set_option usage with
    the buffer-local API (vim.bo) when setting the filetype: in the BufNewFile
    autocmd callback (and the earlier autocmd callback above) stop calling
    vim.api.nvim_buf_set_option(args.buf, "filetype", M.get_filetype()) and instead
    assign the option via the buffer-local table using args.buf (e.g., set
    vim.bo[].filetype = M.get_filetype()); update both places where args.buf
    and M.get_filetype() are used so the filetype is set using vim.bo rather than
    the deprecated nvim_buf_set_option.
  • Around line 265-294: The current M.setup_buffer_keymaps(bufnr) unconditionally
    sets buffer-local keymaps and can override user mappings; change it to respect a
    configuration table (e.g., a module-level config.keymaps.enable boolean and
    optional config.keymaps.bindings table) so keymaps are only applied when enabled
    and allow users to supply/merge their own mapping table; update
    M.setup_buffer_keymaps to accept or read the config, check config.keymaps.enable
    before calling vim.keymap.set, and if config.keymaps.bindings is present iterate
    that table (falling back to the default mappings) to set each mapping so users
    can opt-in or override defaults.

In @tooling/lsp/server/src/main.rs:

  • Around line 9-14: Initialize logging/tracing at the start of main before
    creating the LSP service: add tracing and tracing-subscriber (or env_logger) to
    Cargo.toml, then in async fn main call a tracing subscriber initializer (e.g.,
    configure env-filter from RUST_LOG and call tracing_subscriber::fmt::init() or
    build a subscriber with tracing_subscriber::fmt().with_env_filter(...).init())
    prior to invoking LspService::new(backend::Backend::new) and
    Server::new(...).serve(...).await so all backend::Backend and Server logs are
    emitted for debugging.

In @tooling/lsp/vscode-extension/.vscodeignore:

  • Around line 10-13: Remove redundant ignore patterns: keep the broad patterns
    (/node_modules/ and /.ts, **/.map) and delete the overlapping specific
    entries (node_modules/
    , out//*.ts, out//.map) so the .vscodeignore only
    contains the broader rules; adjust so only /node_modules/ and the generic
    **/
    .ts and **/*.map remain.

In @tooling/lsp/vscode-extension/package.json:

  • Around line 25-27: Remove the redundant explicit activationEvents entry
    "onLanguage:superTOML" since VS Code >=1.74 can infer activation from
    contributes.languages; locate the "activationEvents" array in package.json and
    delete the "onLanguage:superTOML" item (or remove the entire activationEvents
    key if it only contains that entry), keeping contributes.languages unchanged.

In @tooling/lsp/vscode-extension/src/extension.ts:

  • Around line 171-187: getInitializationOptions currently hardcodes
    checkOnSave.enable to true while other features read user prefs; make
    checkOnSave configurable by adding a new setting (e.g., "checkOnSave.enable") in
    package.json and update getInitializationOptions to read
    workspace.getConfiguration('superTOML').get('checkOnSave.enable', true)
    (or similar) so checkOnSave.enable mirrors diagnostics/completions/hover and
    uses a sensible default.

In @tooling/lsp/zed-extension/README.md:

  • Line 3: Update the README intro sentence to clarify scope by specifying that
    the extension provides language server support for SuperTOML files rather than
    generic TOML; locate the sentence referencing "TOML files" in the Zed editor
    extension README (the line mentioning supertoml-analyzer) and change it to
    explicitly say "SuperTOML files" or similar wording that ties it to the
    SuperTOML format/project to avoid implying full generic TOML coverage.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `e692a85c-d203-4f31-86c2-1a55a3de036f`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 95468ee596eca5161e921a9b54422c416a6696c2 and af4545048ba402f91a11b3ead34f3ee399c6604a.

</details>

<details>
<summary>⛔ Files ignored due to path filters (4)</summary>

* `Cargo.lock` is excluded by `!**/*.lock`
* `crates/cac_toml/Cargo.lock` is excluded by `!**/*.lock`
* `tooling/lsp/vscode-extension/icons/toml-icon.svg` is excluded by `!**/*.svg`
* `tooling/lsp/vscode-extension/package-lock.json` is excluded by `!**/package-lock.json`

</details>

<details>
<summary>📒 Files selected for processing (47)</summary>

* `Cargo.toml`
* `clients/java/bindings/README_TOML_TESTS.md`
* `clients/javascript/bindings/README_TOML_TESTS.md`
* `crates/superposition_core/src/toml.rs`
* `examples/superposition_toml_example/README.md`
* `tooling/lsp/neovim-extension/.gitignore`
* `tooling/lsp/neovim-extension/README.md`
* `tooling/lsp/neovim-extension/ftdetect/supertoml.lua`
* `tooling/lsp/neovim-extension/ftplugin/superTOML.lua`
* `tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua`
* `tooling/lsp/neovim-extension/lua/supertoml-analyzer/init.lua`
* `tooling/lsp/neovim-extension/lua/supertoml-analyzer/lsp.lua`
* `tooling/lsp/neovim-extension/plugin/init.lua`
* `tooling/lsp/neovim-extension/queries/supertoml/highlights.scm`
* `tooling/lsp/neovim-extension/queries/supertoml/injections.scm`
* `tooling/lsp/neovim-extension/queries/supertoml/textobjects.scm`
* `tooling/lsp/neovim-extension/syntax/superTOML.vim`
* `tooling/lsp/server/Cargo.toml`
* `tooling/lsp/server/src/backend.rs`
* `tooling/lsp/server/src/completions.rs`
* `tooling/lsp/server/src/diagnostics.rs`
* `tooling/lsp/server/src/hover.rs`
* `tooling/lsp/server/src/main.rs`
* `tooling/lsp/server/src/utils.rs`
* `tooling/lsp/vscode-extension/.gitignore`
* `tooling/lsp/vscode-extension/.vscodeignore`
* `tooling/lsp/vscode-extension/README.md`
* `tooling/lsp/vscode-extension/language-configuration.json`
* `tooling/lsp/vscode-extension/package.json`
* `tooling/lsp/vscode-extension/src/extension.ts`
* `tooling/lsp/vscode-extension/syntaxes/superTOML.tmLanguage.json`
* `tooling/lsp/vscode-extension/test.super.toml`
* `tooling/lsp/vscode-extension/tsconfig.json`
* `tooling/lsp/zed-extension/.gitignore`
* `tooling/lsp/zed-extension/Cargo.toml`
* `tooling/lsp/zed-extension/README.md`
* `tooling/lsp/zed-extension/extension.toml`
* `tooling/lsp/zed-extension/languages/superTOML/brackets.scm`
* `tooling/lsp/zed-extension/languages/superTOML/config.toml`
* `tooling/lsp/zed-extension/languages/superTOML/highlights.scm`
* `tooling/lsp/zed-extension/languages/superTOML/indents.scm`
* `tooling/lsp/zed-extension/languages/superTOML/injections.scm`
* `tooling/lsp/zed-extension/languages/superTOML/outline.scm`
* `tooling/lsp/zed-extension/languages/superTOML/overrides.scm`
* `tooling/lsp/zed-extension/languages/superTOML/redactions.scm`
* `tooling/lsp/zed-extension/languages/superTOML/textobjects.scm`
* `tooling/lsp/zed-extension/src/lib.rs`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

@@ -0,0 +1,212 @@
superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: File path accidentally included as code.

Line 1 contains the file path superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua which is not valid Lua syntax. This will cause a parse error when the module is loaded, breaking the entire plugin.

🐛 Proposed fix - remove the erroneous line
-superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua
 ---@class SuperTOMLConfig
 ---@field server SuperTOMLServerConfig
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua
---@class SuperTOMLConfig
---@field server SuperTOMLServerConfig
🧰 Tools
🪛 Luacheck (1.2.0)

[error] 1-1: expected '=' near 'tooling'

(E011)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua` at line 1,
Remove the stray file path string on the first line of the module (the literal
"superposition/tooling/lsp/neovim-extension/lua/supertoml-analyzer/config.lua")
so the Lua file parses correctly; open the module (config.lua) and delete that
top-line path entry, ensuring the file begins with the intended Lua code (e.g.,
local config = ... or return {...}) and that any module-level symbols like the
config table or return statement remain intact.

Comment on lines +8 to +27
(table
(bare_key) @namespace
(dotted_key) @namespace)

(table
"["
(bare_key) @namespace
(dotted_key) @namespace
"]") @punctuation.bracket

; Array of tables
(table_array_element
"[[" @punctuation.bracket
(bare_key) @namespace
(dotted_key) @namespace
"]]" @punctuation.bracket)

(table_array_element
(bare_key) @namespace
(dotted_key) @namespace)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and examine its content
find . -path "*/tooling/lsp/neovim-extension/queries/supertoml/highlights.scm" -type f

Repository: juspay/superposition

Length of output: 127


🏁 Script executed:

# List files in the supertoml queries directory to understand the structure
find . -path "*/queries/supertoml/*" -type f

Repository: juspay/superposition

Length of output: 256


🏁 Script executed:

# Look for any TOML grammar files or tree-sitter TOML references
find . -name "*toml*" -type f | head -20

Repository: juspay/superposition

Length of output: 789


🏁 Script executed:

# Read the highlights.scm file to examine the patterns
cat -n ./tooling/lsp/neovim-extension/queries/supertoml/highlights.scm

Repository: juspay/superposition

Length of output: 2204


🏁 Script executed:

# Check for tree-sitter TOML grammar documentation or references in the repo
find . -name "*.rs" -type f | xargs grep -l "tree-sitter\|toml.*grammar" | head -10

Repository: juspay/superposition

Length of output: 46


🌐 Web query:

tree-sitter TOML grammar table node structure

💡 Result:

In the tree-sitter TOML grammar, a “table” header is represented by one of two named node types:

  • table for [table]
  • table_array_element for [[table]] (array-of-tables element)

Concrete structure (what children you’ll see)

From the grammar rules:

  • table parses as: '[' + key + ']' + line-ending + 0+ pairs/newlines. [1]
  • table_array_element parses as: '[[' + key + ']]' + line-ending + 0+ pairs/newlines. [1]

The key in both cases is either:

  • dotted_key, or
  • a single _key (which becomes bare_key or quoted_key). [1]

What node-types.json says about the AST shape

node-types.json (the authoritative “node structure” file tree-sitter generates) shows:

  • table has required children drawn from: bare_key | quoted_key | dotted_key | pair (no field names; it’s positional/sequence-based). [2]
  • table_array_element has the same child-type set: bare_key | quoted_key | dotted_key | pair. [2]

Because there are no named fields on table / table_array_element, you typically interpret them as:

  1. first named child = the table key (bare_key / quoted_key / dotted_key)
  2. remaining named children of type pair = key/value pairs inside that table (until the next table header). [1][2]

(Background: tree-sitter exposes this via node-types.json’s children/fields metadata.) [3]

Sources: [1] [2] [3]


🌐 Web query:

tree-sitter query syntax matching multiple children

💡 Result:

In Tree-sitter queries, “multiple children” are matched by writing multiple child patterns inside the parent pattern, and they match the parent’s named children in source order (optionally constrained with . anchors). (tree-sitter.github.io)

1) Match a parent that contains two specific children (in order)

(parent_node
  (child_a) `@a`
  (child_b) `@b`)

2) Allow other children in between

Use a wildcard child (often quantified) between them:

(parent_node
  (child_a) `@a`
  (_)*            ; any number of named children in between
  (child_b) `@b`)

Quantifiers * + ? work on nodes and on grouped sequences. (tree-sitter.github.io)

3) Require the children to be immediate siblings

Put the anchor . between them:

(parent_node
  (child_a) `@a`
  .
  (child_b) `@b`)

An anchor between child patterns enforces “immediately adjacent named siblings”. (tree-sitter.github.io)

4) If the two children can appear in either order

Queries are order-sensitive, so you typically write two patterns (or an alternation) to cover both:

[
  (parent_node (child_a) `@a` (_)* (child_b) `@b`)
  (parent_node (child_b) `@b` (_)* (child_a) `@a`)
]

Alternations are written with [...]. (tree-sitter.github.io)

Citations:


Table header highlight queries never match as written.

In tree-sitter TOML, a table or table_array_element node contains exactly one key child (either bare_key, quoted_key, or dotted_key). The patterns at lines 8–10, 12–16, 19–23, and 25–27 require both bare_key and dotted_key as direct children, making them structurally impossible to match. As a result, namespace and bracket highlighting for all table and array-of-tables headers is completely ineffective.

Use alternations (e.g., [(bare_key) (dotted_key)]) to match either key type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/queries/supertoml/highlights.scm` around lines 8
- 27, The Tree-sitter queries for table headers incorrectly require both
(bare_key) and (dotted_key) as children, so patterns like the (table) and
(table_array_element) captures never match; update each
table/table_array_element pattern (the ones capturing `@namespace` and
`@punctuation.bracket`) to use alternation for the key child — e.g., replace
sequences requiring both (bare_key) and (dotted_key) with a single bracketed
alternative that includes (bare_key) (dotted_key) and (quoted_key) as
appropriate — so the `@namespace` capture matches any single key child and the
bracket captures remain on the surrounding "["/"]" or "[["/"]]".

Comment on lines +5 to +27
((string) @injection.content
(#match? @injection.content "^[\\s]*#!")
(#set! injection.language "bash"))

;; Inject regex for pattern values
((string) @injection.content
(#match? @injection.content "^/.*/$")
(#set! injection.language "regex"))

;; Inject JSON for values that look like JSON
((string) @injection.content
(#match? @injection.content "^\\s*[\\[{].*[\\]}]\\s*$")
(#set! injection.language "json"))

;; Inject Lua for Neovim-style config values
((string) @injection.content
(#match? @injection.content "^\\s*function\\s*\\(")
(#set! injection.language "lua"))

;; Inject Lua for vimscript-style values
((string) @injection.content
(#match? @injection.content "^\\s*vim\\.")
(#set! injection.language "lua"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For Neovim Tree-sitter injections with TOML grammar, does (string) @injection.content`` include quote delimiters, and is #offset! the recommended way to match unquoted content?

💡 Result:

Yes. In the current tree-sitter-toml grammar, a TOML string node is defined as a sequence that includes the opening and closing quote tokens (e.g. "" or '', and likewise """ / '''), so capturing (string) @injection.content`` will include the delimiters in the captured text/range. [1]

And yes: in Neovim, #offset! is the standard/recommended way to make an injection operate on a sub-range of a captured node (it rewrites metadata[capture].range and is explicitly called out as useful for injections). [2] For TOML strings, that typically means offsetting by 1/-1 for single-quote/single-double-quote strings, or 3/-3 for triple-quoted multiline strings. [1][2]


Apply #offset! to unwrap string delimiters in injection rules.

In tree-sitter-toml, (string) @injection.content`` captures the node including quote delimiters. This causes patterns anchored with ^ to match against the opening quote rather than the payload, preventing these injection rules from firing. Add `#offset! `@injection.content` 0 1 0 -1` to each rule to trim delimiters:

Proposed fix
 ((string) `@injection.content`
+  (`#offset`! `@injection.content` 0 1 0 -1)
   (`#match`? `@injection.content` "^[\\s]*#!")
   (`#set`! injection.language "bash"))
 
 ;; Inject regex for pattern values
 ((string) `@injection.content`
+  (`#offset`! `@injection.content` 0 1 0 -1)
   (`#match`? `@injection.content` "^/.*/$")
   (`#set`! injection.language "regex"))
 
 ;; Inject JSON for values that look like JSON
 ((string) `@injection.content`
+  (`#offset`! `@injection.content` 0 1 0 -1)
   (`#match`? `@injection.content` "^\\s*[\\[{].*[\\]}]\\s*$")
   (`#set`! injection.language "json"))
 
 ;; Inject Lua for Neovim-style config values
 ((string) `@injection.content`
+  (`#offset`! `@injection.content` 0 1 0 -1)
   (`#match`? `@injection.content` "^\\s*function\\s*\\(")
   (`#set`! injection.language "lua"))
 
 ;; Inject Lua for vimscript-style values
 ((string) `@injection.content`
+  (`#offset`! `@injection.content` 0 1 0 -1)
   (`#match`? `@injection.content` "^\\s*vim\\.")
   (`#set`! injection.language "lua"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/neovim-extension/queries/supertoml/injections.scm` around lines 5
- 27, The string capture `@injection.content` includes surrounding quotes so
anchoring patterns like ^ match the delimiter instead of the payload; for each
injection rule that uses ((string) `@injection.content` ...) (the ones setting
injection.language to "bash", "regex", "json", and the two "lua" rules), add an
offset directive to unwrap the quotes by inserting `#offset`! `@injection.content` 0
1 0 -1 inside the rule so the regex matches the inner content rather than the
delimiters.

Comment on lines +31 to +56
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
self.client
.log_message(MessageType::INFO, "Initializing superTOML Analyzer...")
.await;
Ok(InitializeResult {
capabilities: ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(
TextDocumentSyncKind::FULL,
)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![
"=".to_string(),
" ".to_string(),
"{".to_string(),
",".to_string(),
]),
..Default::default()
}),
hover_provider: Some(HoverProviderCapability::Simple(true)),
..Default::default()
},
server_info: Some(ServerInfo {
name: "supertoml-analyzer".to_string(),
version: Some(env!("CARGO_PKG_VERSION").to_string()),
}),
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: locate byte-based position math and any negotiated position encoding.
# Expectation: direct byte-based handling is present, and no `position_encoding`
# capability is configured in the server.
rg -n 'character as usize|byte_offset_to_position|position_encoding|PositionEncoding' tooling/lsp/server/src

Repository: juspay/superposition

Length of output: 2458


🏁 Script executed:

sed -n '490,510p' tooling/lsp/server/src/diagnostics.rs

Repository: juspay/superposition

Length of output: 859


🏁 Script executed:

rg -n 'ServerCapabilities|position_encoding' tooling/lsp/server/src --context=5

Repository: juspay/superposition

Length of output: 1007


🏁 Script executed:

rg -n 'struct ServerCapabilities|position_encoding' tooling/lsp/server/src/backend.rs -A 30

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

head -n 30 tooling/lsp/server/src/backend.rs

Repository: juspay/superposition

Length of output: 733


🏁 Script executed:

cat -n tooling/lsp/server/src/backend.rs | sed -n '31,56p'

Repository: juspay/superposition

Length of output: 1356


🏁 Script executed:

rg -n 'position_encoding' . --type rust | head -20

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

cat Cargo.toml | grep -A 3 'tower_lsp'

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

find . -name 'Cargo.toml' -exec grep -l 'tower_lsp' {} \; | head -5

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

cat tooling/lsp/server/Cargo.toml | grep -A 2 'tower_lsp'

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

python3 << 'EOF'
import subprocess
import json

# Search for ServerCapabilities definition or documentation
result = subprocess.run(['rg', '-n', 'pub struct ServerCapabilities|pub.*position_encoding', 'tooling/lsp/server/src', '-A', '2'], 
                       capture_output=True, text=True)
print("ServerCapabilities search:")
print(result.stdout)
print(result.stderr)

# Also search in Cargo.lock if it exists
result2 = subprocess.run(['rg', 'tower_lsp', 'Cargo.lock', '-A', '1'], 
                        capture_output=True, text=True)
print("\ntower_lsp in Cargo.lock:")
print(result2.stdout if result2.stdout else "Not found or no Cargo.lock")
EOF

Repository: juspay/superposition

Length of output: 145


🏁 Script executed:

find tooling -name 'Cargo.toml' -type f

Repository: juspay/superposition

Length of output: 130


🏁 Script executed:

cat tooling/lsp/Cargo.toml 2>/dev/null || cat tooling/lsp/server/Cargo.toml 2>/dev/null || echo "File not found"

Repository: juspay/superposition

Length of output: 560


🏁 Script executed:

rg 'use tower_lsp::lsp_types' tooling/lsp/server/src/ -A 2

Repository: juspay/superposition

Length of output: 906


🌐 Web query:

tower-lsp 0.20 position_encoding ServerCapabilities

💡 Result:

In tower-lsp 0.20, ServerCapabilities is the lsp-types struct, and it has:

  • position_encoding: Option<PositionEncodingKind> — “the position encoding the server picked from the encodings offered by the client via general.positionEncodings”. If the client didn’t offer anything, the only valid value is UTF‑16; if you omit the field it also defaults to UTF‑16. [1]

You read what the client supports from the initialize request (InitializeParams { capabilities: ClientCapabilities, ... }). [2] Concretely, the encodings (if any) come from params.capabilities.general.position_encodings.

Example (pick UTF‑8 if the client offers it, else UTF‑16), then advertise it via ServerCapabilities.position_encoding:

use tower_lsp::lsp_types::{
    InitializeParams, InitializeResult, ServerCapabilities, PositionEncodingKind,
};

async fn initialize(params: InitializeParams) -> tower_lsp::jsonrpc::Result<InitializeResult> {
    let client_offered = params
        .capabilities
        .general
        .as_ref()
        .and_then(|g| g.position_encodings.as_deref())
        .unwrap_or_default();

    let chosen = if client_offered.contains(&PositionEncodingKind::UTF8) {
        PositionEncodingKind::UTF8
    } else {
        PositionEncodingKind::UTF16
    };

    Ok(InitializeResult {
        capabilities: ServerCapabilities {
            position_encoding: Some(chosen),
            ..Default::default()
        },
        ..Default::default()
    })
}

This is the same general negotiation approach used by real servers (e.g., rust-analyzer iterates the client’s general.position_encodings and falls back to UTF‑16). [3]

Sources: [1] [2] [3]


🏁 Script executed:

cat -n tooling/lsp/server/src/diagnostics.rs | sed -n '490,498p'

Repository: juspay/superposition

Length of output: 466


🏁 Script executed:

cat -n tooling/lsp/server/src/hover.rs | sed -n '15,25p'

Repository: juspay/superposition

Length of output: 546


🏁 Script executed:

cat -n tooling/lsp/server/src/utils.rs | sed -n '15,25p'

Repository: juspay/superposition

Length of output: 366


Negotiate position encoding in initialize or convert byte-offset helpers to UTF-16.

The codebase treats positions as UTF-8 byte offsets: byte_offset_to_position computes columns as byte distances from line start, hover.rs and utils.rs cast pos.character directly to usize, and diagnostics.rs uses byte offsets throughout. However, initialize does not set ServerCapabilities.position_encoding, leaving the server on the LSP default (UTF-16). On UTF-16 clients with non-ASCII content, hover/completion/diagnostics will misalign.

Add position_encoding negotiation in initialize: read params.capabilities.general.position_encodings, prefer UTF-8 if the client offers it (falling back to UTF-16), and advertise the choice in ServerCapabilities.position_encoding.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/server/src/backend.rs` around lines 31 - 56, The initialize
handler currently does not negotiate position encoding, causing
UTF-16/byte-offset mismatches; update async fn initialize(&self, params:
InitializeParams) to inspect params.capabilities.general.position_encodings (or
the equivalent capability path) and choose an encoding: prefer
PositionEncodingKind::UTF8 if the client advertises it, otherwise fall back to
PositionEncodingKind::UTF16; store the chosen encoding in the server state and
set ServerCapabilities.position_encoding to that choice so clients know what the
server uses; ensure callers that rely on byte offsets (e.g.,
byte_offset_to_position, hover.rs, utils.rs, diagnostics.rs) either read the
stored encoding and convert between byte offsets and LSP positions accordingly
or are updated later to expect the negotiated encoding.

Comment on lines +33 to +69
// Parse raw TOML for dimension / config names; failures are tolerated
// (the file may be mid-edit and syntactically incomplete).
let raw = toml::from_str::<toml::Table>(text).ok();

let items: Vec<CompletionItem> = match ctx {
CursorContext::ContextInlineTable => raw
.as_ref()
.and_then(|t| t.get("dimensions"))
.and_then(|d| d.as_table())
.map(|dims| {
dims.keys()
.map(|k| CompletionItem {
label: k.clone(),
kind: Some(CompletionItemKind::FIELD),
detail: Some("dimension".to_string()),
..Default::default()
})
.collect()
})
.unwrap_or_default(),

CursorContext::OverrideKey => raw
.as_ref()
.and_then(|t| t.get("default-configs"))
.and_then(|d| d.as_table())
.map(|configs| {
configs
.keys()
.map(|k| CompletionItem {
label: k.clone(),
kind: Some(CompletionItemKind::FIELD),
detail: Some("config key".to_string()),
..Default::default()
})
.collect()
})
.unwrap_or_default(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

git ls-files | grep -E "tooling/lsp/server/src/completions.rs"

Repository: juspay/superposition


Repository: juspay/superposition
Exit code: 0

stdout:

tooling/lsp/server/src/completions.rs

Script executed:

wc -l tooling/lsp/server/src/completions.rs

Repository: juspay/superposition


Repository: juspay/superposition
Exit code: 0

stdout:

162 tooling/lsp/server/src/completions.rs

Script executed:

cat -n tooling/lsp/server/src/completions.rs | head -100

Repository: juspay/superposition


Repository: juspay/superposition
Exit code: 0

stdout:

     1	use tower_lsp::lsp_types::*;
     2	
     3	use crate::utils;
     4	
     5	/// Context of the cursor within a SuperTOML document.
     6	#[derive(Debug, PartialEq)]
     7	enum CursorContext {
     8	    /// Inside the `_context_ = { … }` inline table of an `[[overrides]]` block.
     9	    /// → suggest declared dimension names.
    10	    ContextInlineTable,
    11	    /// On a key line after `_context_` in an `[[overrides]]` block.
    12	    /// → suggest declared default-config keys.
    13	    OverrideKey,
    14	    /// On a `type = …` value in a `[dimensions]` entry.
    15	    /// → suggest REGULAR / LOCAL_COHORT:… / REMOTE_COHORT:…
    16	    DimensionType,
    17	    /// Cursor position not in a recognised completion context.
    18	    Unknown,
    19	}
    20	
    21	/// Produce completion items for the given cursor position.
    22	pub fn compute(text: &str, pos: Position) -> Option<CompletionResponse> {
    23	    // Don't provide completions if cursor is inside a comment
    24	    if utils::is_inside_comment(text, pos) {
    25	        return None;
    26	    }
    27	
    28	    let lines: Vec<&str> = text.lines().collect();
    29	    let line_num = pos.line as usize;
    30	
    31	    let ctx = determine_cursor_context(&lines, line_num);
    32	
    33	    // Parse raw TOML for dimension / config names; failures are tolerated
    34	    // (the file may be mid-edit and syntactically incomplete).
    35	    let raw = toml::from_str::<toml::Table>(text).ok();
    36	
    37	    let items: Vec<CompletionItem> = match ctx {
    38	        CursorContext::ContextInlineTable => raw
    39	            .as_ref()
    40	            .and_then(|t| t.get("dimensions"))
    41	            .and_then(|d| d.as_table())
    42	            .map(|dims| {
    43	                dims.keys()
    44	                    .map(|k| CompletionItem {
    45	                        label: k.clone(),
    46	                        kind: Some(CompletionItemKind::FIELD),
    47	                        detail: Some("dimension".to_string()),
    48	                        ..Default::default()
    49	                    })
    50	                    .collect()
    51	            })
    52	            .unwrap_or_default(),
    53	
    54	        CursorContext::OverrideKey => raw
    55	            .as_ref()
    56	            .and_then(|t| t.get("default-configs"))
    57	            .and_then(|d| d.as_table())
    58	            .map(|configs| {
    59	                configs
    60	                    .keys()
    61	                    .map(|k| CompletionItem {
    62	                        label: k.clone(),
    63	                        kind: Some(CompletionItemKind::FIELD),
    64	                        detail: Some("config key".to_string()),
    65	                        ..Default::default()
    66	                    })
    67	                    .collect()
    68	            })
    69	            .unwrap_or_default(),
    70	
    71	        CursorContext::DimensionType => {
    72	            let dim_names: Vec<String> = raw
    73	                .as_ref()
    74	                .and_then(|t| t.get("dimensions"))
    75	                .and_then(|d| d.as_table())
    76	                .map(|dims| dims.keys().cloned().collect())
    77	                .unwrap_or_default();
    78	
    79	            let mut items = vec![CompletionItem {
    80	                label: "REGULAR".to_string(),
    81	                kind: Some(CompletionItemKind::ENUM_MEMBER),
    82	                detail: Some("Standard dimension (default)".to_string()),
    83	                ..Default::default()
    84	            }];
    85	
    86	            for dim in &dim_names {
    87	                items.push(CompletionItem {
    88	                    label: format!("LOCAL_COHORT:{}", dim),
    89	                    kind: Some(CompletionItemKind::ENUM_MEMBER),
    90	                    detail: Some(format!("Local cohort of dimension '{}'", dim)),
    91	                    ..Default::default()
    92	                });
    93	                items.push(CompletionItem {
    94	                    label: format!("REMOTE_COHORT:{}", dim),
    95	                    kind: Some(CompletionItemKind::ENUM_MEMBER),
    96	                    detail: Some(format!("Remote cohort of dimension '{}'", dim)),
    97	                    ..Default::default()
    98	                });
    99	            }
   100	            items

Script executed:

cat -n tooling/lsp/server/src/completions.rs | tail -70

Repository: juspay/superposition


Repository: juspay/superposition
Exit code: 0

stdout:

    93	                items.push(CompletionItem {
    94	                    label: format!("REMOTE_COHORT:{}", dim),
    95	                    kind: Some(CompletionItemKind::ENUM_MEMBER),
    96	                    detail: Some(format!("Remote cohort of dimension '{}'", dim)),
    97	                    ..Default::default()
    98	                });
    99	            }
   100	            items
   101	        }
   102	
   103	        CursorContext::Unknown => return None,
   104	    };
   105	
   106	    if items.is_empty() {
   107	        None
   108	    } else {
   109	        Some(CompletionResponse::Array(items))
   110	    }
   111	}
   112	
   113	/// Determine what kind of completion is appropriate at `line_num`.
   114	///
   115	/// Strategy: scan from the top of the file to `line_num`, tracking which
   116	/// section header (`[default-configs]`, `[dimensions]`, `[[overrides]]`) was
   117	/// most recently seen and whether `_context_` has appeared in the current
   118	/// overrides block.
   119	fn determine_cursor_context(lines: &[&str], line_num: usize) -> CursorContext {
   120	    let up_to = (line_num + 1).min(lines.len());
   121	
   122	    let mut in_overrides = false;
   123	    let mut context_seen = false;
   124	    let mut in_dimensions = false;
   125	
   126	    for line in &lines[..up_to] {
   127	        let trimmed = line.trim();
   128	
   129	        if trimmed == "[[overrides]]" {
   130	            in_overrides = true;
   131	            in_dimensions = false;
   132	            context_seen = false;
   133	        } else if trimmed == "[dimensions]" {
   134	            in_dimensions = true;
   135	            in_overrides = false;
   136	            context_seen = false;
   137	        } else if trimmed.starts_with('[') {
   138	            // Any other section header ([default-configs], etc.)
   139	            in_overrides = false;
   140	            in_dimensions = false;
   141	            context_seen = false;
   142	        } else if in_overrides && trimmed.starts_with("_context_") {
   143	            context_seen = true;
   144	        }
   145	    }
   146	
   147	    let current_line = lines.get(line_num).map(|l| l.trim()).unwrap_or("");
   148	
   149	    if in_overrides {
   150	        if current_line.contains("_context_") {
   151	            CursorContext::ContextInlineTable
   152	        } else if context_seen {
   153	            CursorContext::OverrideKey
   154	        } else {
   155	            CursorContext::Unknown
   156	        }
   157	    } else if in_dimensions && current_line.contains("type") {
   158	        CursorContext::DimensionType
   159	    } else {
   160	        CursorContext::Unknown
   161	    }
   162	}

Script executed:

grep -n "determine_cursor_context" tooling/lsp/server/src/completions.rs

Repository: juspay/superposition


Repository: juspay/superposition
Exit code: 0

stdout:

31:    let ctx = determine_cursor_context(&lines, line_num);
119:fn determine_cursor_context(lines: &[&str], line_num: usize) -> CursorContext {

All completion contexts disappear on the first parse error.

The _context_, override-key, and dimension-type branches all depend on raw, which is assigned from toml::from_str(text).ok() (line 35). When the parser encounters a syntax error anywhere in the file, raw becomes None, causing all three match arms to return empty Vec via unwrap_or_default(). The function then returns None for empty items (line 106–110). This is problematic during active editing when the file may be temporarily incomplete; completions should remain available by extracting dimension and config names without requiring a fully-valid parse. The inline comment claims "failures are tolerated," but no fallback mechanism exists to extract partial data from an incomplete TOML structure.

Comment on lines +143 to +145
// Middle segments are sub-nesting (keys that lead to inline tables)
let sub_nesting = &segments[1..segments.len().saturating_sub(1)];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Nested TOML paths are only resolved one level deep.

sub_nesting collects every intermediate segment, but these resolvers only use sub_nesting[0]. A key like dimensions.peak_hours.schema.type will therefore stop at the peak_hours line and return the first type = on that line, not the nested schema field that actually failed validation.

Also applies to: 223-253, 410-438

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/server/src/diagnostics.rs` around lines 143 - 145, The code
slices intermediate segments into sub_nesting but only uses sub_nesting[0], so
nested TOML paths like dimensions.peak_hours.schema.type stop one level early;
update the resolver that references sub_nesting to iteratively traverse each
segment in sub_nesting (e.g., starting from the root table/value, loop over each
segment name, descend into its table/inline table or array element as
appropriate, and update the current context) rather than indexing [0], and fall
back gracefully when a key is missing; apply the same change to the other
resolver occurrences noted (the blocks around the other sub_nesting uses).

Comment on lines +20 to +23
// A best-effort parse; we still try to provide hover even when the file
// has minor errors by working with the raw table.
let raw: toml::Table = toml::from_str(text).ok()?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n tooling/lsp/server/src/hover.rs | head -60

Repository: juspay/superposition

Length of output: 2473


🏁 Script executed:

# Find the toml dependency specification
rg "toml\s*=" Cargo.toml | head -20

Repository: juspay/superposition

Length of output: 183


🏁 Script executed:

# Check if there are any partial parsing or error-tolerant parsing attempts elsewhere
rg "toml::" tooling/lsp/server/src/ --type rust

Repository: juspay/superposition

Length of output: 244


🏁 Script executed:

# Check how completions.rs handles the parse result
cat -n tooling/lsp/server/src/completions.rs | head -80

Repository: juspay/superposition

Length of output: 3519


🏁 Script executed:

# Double-check the toml crate version and its behavior
rg "0.8.8" --type toml

Repository: juspay/superposition

Length of output: 2776


Hover stops working as soon as the buffer has any TOML syntax error.

toml::from_str(text).ok()? is not best-effort parsing; one syntax error anywhere makes this return None and disables hover entirely. The comment claiming "best-effort parse" contradicts the implementation. Compare with completions.rs, which handles the same parser by storing the Option without early exit, allowing graceful degradation during editing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/server/src/hover.rs` around lines 20 - 23, The code currently
does a hard early return using toml::from_str(text).ok()? which disables hover
on any TOML syntax error; change this to a best-effort approach by capturing the
parse result as an Option (e.g. let raw: Option<toml::Table> =
toml::from_str(text).ok();) instead of using the ? operator, and update
subsequent uses of raw in hover.rs to handle None gracefully (mirror the pattern
used in completions.rs) so hover can still operate using the raw buffer or
partial data when parsing fails.

Comment on lines +132 to +163
// Try to find the binary in common locations
const possiblePaths: string[] = [];

// Check workspace folders
if (workspace.workspaceFolders) {
for (const folder of workspace.workspaceFolders) {
const folderPath = folder.uri.fsPath;
possiblePaths.push(
path.join(folderPath, 'target', 'debug', 'supertoml-analyzer'),
path.join(folderPath, 'target', 'release', 'supertoml-analyzer'),
// Also check parent directory in case workspace is a subdirectory
path.join(folderPath, '..', 'target', 'debug', 'supertoml-analyzer'),
path.join(folderPath, '..', 'target', 'release', 'supertoml-analyzer')
);
}
}

// Check relative to extension path (development mode)
const extensionRoot = path.dirname(path.dirname(context.extensionPath));
possiblePaths.push(
path.join(extensionRoot, 'target', 'debug', 'supertoml-analyzer'),
path.join(extensionRoot, 'target', 'release', 'supertoml-analyzer')
);

// Try each path
for (const p of possiblePaths) {
const resolvedPath = path.resolve(p);
if (fs.existsSync(resolvedPath)) {
outputChannel.appendLine(`Found server at: ${resolvedPath}`);
return resolvedPath;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Windows compatibility issue in binary path discovery.

The possible paths array doesn't include the .exe extension for Windows. While line 166 correctly handles this for the PATH fallback, the workspace and extension path searches will fail on Windows.

🐛 Proposed fix
 function getServerPath(context: ExtensionContext): string {
     const config = workspace.getConfiguration('superTOML');
     const configuredPath = config.get<string>('server.path', '');
+    const binaryName = process.platform === 'win32' ? 'supertoml-analyzer.exe' : 'supertoml-analyzer';

     // If a path is explicitly configured, use it
     if (configuredPath && configuredPath.trim() !== '') {
         outputChannel.appendLine(`Using configured server path: ${configuredPath}`);
         return configuredPath;
     }

     // Try to find the binary in common locations
     const possiblePaths: string[] = [];

     // Check workspace folders
     if (workspace.workspaceFolders) {
         for (const folder of workspace.workspaceFolders) {
             const folderPath = folder.uri.fsPath;
             possiblePaths.push(
-                path.join(folderPath, 'target', 'debug', 'supertoml-analyzer'),
-                path.join(folderPath, 'target', 'release', 'supertoml-analyzer'),
+                path.join(folderPath, 'target', 'debug', binaryName),
+                path.join(folderPath, 'target', 'release', binaryName),
                 // Also check parent directory in case workspace is a subdirectory
-                path.join(folderPath, '..', 'target', 'debug', 'supertoml-analyzer'),
-                path.join(folderPath, '..', 'target', 'release', 'supertoml-analyzer')
+                path.join(folderPath, '..', 'target', 'debug', binaryName),
+                path.join(folderPath, '..', 'target', 'release', binaryName)
             );
         }
     }

     // Check relative to extension path (development mode)
     const extensionRoot = path.dirname(path.dirname(context.extensionPath));
     possiblePaths.push(
-        path.join(extensionRoot, 'target', 'debug', 'supertoml-analyzer'),
-        path.join(extensionRoot, 'target', 'release', 'supertoml-analyzer')
+        path.join(extensionRoot, 'target', 'debug', binaryName),
+        path.join(extensionRoot, 'target', 'release', binaryName)
     );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Try to find the binary in common locations
const possiblePaths: string[] = [];
// Check workspace folders
if (workspace.workspaceFolders) {
for (const folder of workspace.workspaceFolders) {
const folderPath = folder.uri.fsPath;
possiblePaths.push(
path.join(folderPath, 'target', 'debug', 'supertoml-analyzer'),
path.join(folderPath, 'target', 'release', 'supertoml-analyzer'),
// Also check parent directory in case workspace is a subdirectory
path.join(folderPath, '..', 'target', 'debug', 'supertoml-analyzer'),
path.join(folderPath, '..', 'target', 'release', 'supertoml-analyzer')
);
}
}
// Check relative to extension path (development mode)
const extensionRoot = path.dirname(path.dirname(context.extensionPath));
possiblePaths.push(
path.join(extensionRoot, 'target', 'debug', 'supertoml-analyzer'),
path.join(extensionRoot, 'target', 'release', 'supertoml-analyzer')
);
// Try each path
for (const p of possiblePaths) {
const resolvedPath = path.resolve(p);
if (fs.existsSync(resolvedPath)) {
outputChannel.appendLine(`Found server at: ${resolvedPath}`);
return resolvedPath;
}
}
function getServerPath(context: ExtensionContext): string {
const config = workspace.getConfiguration('superTOML');
const configuredPath = config.get<string>('server.path', '');
const binaryName = process.platform === 'win32' ? 'supertoml-analyzer.exe' : 'supertoml-analyzer';
// If a path is explicitly configured, use it
if (configuredPath && configuredPath.trim() !== '') {
outputChannel.appendLine(`Using configured server path: ${configuredPath}`);
return configuredPath;
}
// Try to find the binary in common locations
const possiblePaths: string[] = [];
// Check workspace folders
if (workspace.workspaceFolders) {
for (const folder of workspace.workspaceFolders) {
const folderPath = folder.uri.fsPath;
possiblePaths.push(
path.join(folderPath, 'target', 'debug', binaryName),
path.join(folderPath, 'target', 'release', binaryName),
// Also check parent directory in case workspace is a subdirectory
path.join(folderPath, '..', 'target', 'debug', binaryName),
path.join(folderPath, '..', 'target', 'release', binaryName)
);
}
}
// Check relative to extension path (development mode)
const extensionRoot = path.dirname(path.dirname(context.extensionPath));
possiblePaths.push(
path.join(extensionRoot, 'target', 'debug', binaryName),
path.join(extensionRoot, 'target', 'release', binaryName)
);
// Try each path
for (const p of possiblePaths) {
const resolvedPath = path.resolve(p);
if (fs.existsSync(resolvedPath)) {
outputChannel.appendLine(`Found server at: ${resolvedPath}`);
return resolvedPath;
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/vscode-extension/src/extension.ts` around lines 132 - 163, The
workspace/extension path discovery builds possiblePaths without Windows
executables, so on Windows the searches miss the real binary; update the code
that populates possiblePaths (the blocks referencing workspace.workspaceFolders
and extensionRoot) to also include the '.exe' variant when process.platform ===
'win32' (or push both with and without '.exe'), ensuring the same
Windows-handling used in the PATH fallback is applied to the checks that use
possiblePaths and resolvedPath.

@@ -0,0 +1 @@
(pair (bare_key) "=" (_) @redact)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare redaction patterns in-repo and confirm current convention before merging.
fd redactions.scm -t f | xargs -I{} sh -c 'echo "=== {} ==="; sed -n "1,120p" "{}"'
rg -n --type-add 'scm:*.scm' --type scm -C2 '#match\\?|@redact|\\(pair' tooling/lsp

Repository: juspay/superposition

Length of output: 280


🏁 Script executed:

#!/bin/bash
# Find all .scm files in the repository
fd "\.scm$" -t f

# Search for `@redact` and similar patterns more carefully
rg "@redact" -n

# Look for `#match` patterns (which indicate conditional matching)
rg "#match" -n -C 2

# Check for other language definition files
fd redactions\\.scm\\|language\\. -t f | head -20

Repository: juspay/superposition

Length of output: 3839


🏁 Script executed:

#!/bin/bash
# Get more context about the file and its surrounding directory
cd tooling/lsp/zed-extension/languages/superTOML 2>/dev/null && ls -la
cat highlights.scm 2>/dev/null || echo "No highlights.scm found"

Repository: juspay/superposition

Length of output: 1223


🏁 Script executed:

#!/bin/bash
# Search for TOML-related language definitions in repo
fd -e scm -e json -t f | xargs rg -l "toml|TOML" | head -10

Repository: juspay/superposition

Length of output: 9810


🏁 Script executed:

#!/bin/bash
# Search for `@redact` usage in the repo and any related documentation
rg "@redact" -B2 -A2

# Look for any documentation about redactions
find . -type f \( -name "*.md" -o -name "*.txt" -o -name "README*" \) | xargs grep -l "redact" 2>/dev/null | head -10

# Check if there's a Zed configuration or schema that explains redactions
find . -name "*.toml" -o -name "*.json" | xargs grep -l "redact" 2>/dev/null | head -5

Repository: juspay/superposition

Length of output: 158


🏁 Script executed:

#!/bin/bash
# Look at the entire redactions.scm file and nearby files for context
cat tooling/lsp/zed-extension/languages/superTOML/redactions.scm

# Check the config.toml in the same directory
cat tooling/lsp/zed-extension/languages/superTOML/config.toml

# Look for similar patterns in other Zed language extensions
find tooling/lsp/zed-extension/languages -name "redactions.scm" -o -name "*redact*"

Repository: juspay/superposition

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Check if this is a new file or has history
cd tooling/lsp/zed-extension/languages/superTOML && git log --oneline -n 10 redactions.scm 2>&1 || echo "No git history available"

# Search for any Zed documentation or language definition patterns
find . -type f -name "*.md" | xargs grep -i "redact\|sensitive" 2>/dev/null | head -20

# Look at other language extensions in Zed to see if any have redaction patterns
find tooling/lsp/zed-extension/languages -type f -name "*.scm" | xargs grep -l "@redact\|redaction" 2>/dev/null

Repository: juspay/superposition

Length of output: 191


🏁 Script executed:

#!/bin/bash
# Check Zed language extension documentation/examples
find . -type f \( -name "*.md" -o -name "*.txt" \) | xargs grep -l "zed.*extension\|language.*server" 2>/dev/null | head -5

# Look at the entire language directory structure
find tooling/lsp/zed-extension/languages -type d | head -20

Repository: juspay/superposition

Length of output: 265


🏁 Script executed:

#!/bin/bash
# Read the Zed extension README to understand context
cat tooling/lsp/zed-extension/README.md

# Check git log for the redactions.scm file
git log --oneline -n 20 -- tooling/lsp/zed-extension/languages/superTOML/redactions.scm

Repository: juspay/superposition

Length of output: 2732


🏁 Script executed:

#!/bin/bash
# Look at all .scm files in the superTOML directory
ls -la tooling/lsp/zed-extension/languages/superTOML/

# Read each .scm file to understand the patterns
for file in tooling/lsp/zed-extension/languages/superTOML/*.scm; do
  echo "=== $(basename $file) ==="
  cat "$file"
  echo ""
done

Repository: juspay/superposition

Length of output: 1970


🏁 Script executed:

#!/bin/bash
# Search for any Zed-specific documentation or references to `@redact`
rg "redact" --type-list | head -20

# Check git history to see when redactions.scm was added
git log --all -p -- "**/redactions.scm" | head -100

# Look for Zed extension.toml or other config files
find tooling/lsp/zed-extension -name "*.toml" -exec echo "=== {} ===" \; -exec cat {} \;

Repository: juspay/superposition

Length of output: 2338


🏁 Script executed:

#!/bin/bash
# Search web context or references to understand Zed redaction feature
# Look for any comments or documentation in extension source
find tooling/lsp/zed-extension/src -type f | xargs cat 2>/dev/null

# Check if there's any reference to redactions in Cargo.toml or extension.toml
cat tooling/lsp/zed-extension/extension.toml 2>/dev/null || echo "No extension.toml"
cat tooling/lsp/zed-extension/Cargo.toml 2>/dev/null | head -50

Repository: juspay/superposition

Length of output: 3802


Redaction pattern should be limited to sensitive keys, not all values.

Line 1 currently redacts every TOML pair value. This defeats usability in the editor by masking non-sensitive configuration data. Add a #match? conditional to filter for sensitive keys only, consistent with pattern matching conventions used elsewhere in the project.

Suggested fix
-(pair (bare_key) "=" (_) `@redact`)
+(pair
+  (bare_key) `@key`
+  "="
+  (_) `@redact`
+  (`#match`? `@key` "(?i)^(password|passwd|secret|token|api[_-]?key|private[_-]?key)$"))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(pair (bare_key) "=" (_) @redact)
(pair
(bare_key) `@key`
"="
(_) `@redact`
(`#match`? `@key` "(?i)^(password|passwd|secret|token|api[_-]?key|private[_-]?key)$"))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/zed-extension/languages/superTOML/redactions.scm` at line 1, The
current S-expression rule (pair (bare_key) "=" (_) `@redact`) redacts all TOML
values; restrict it by adding a `#match`? conditional on the bare_key so only
known sensitive keys are redacted (e.g., "password","secret","api_key","token"
or the project's canonical sensitive-key list). Locate the pattern that uses
bare_key and `@redact` and wrap the value capture with a conditional check (using
`#match`? against the sensitive-key regex or list) so non-sensitive pairs are not
assigned `@redact` while preserving existing token names (bare_key, `@redact`).

Comment on lines +20 to +34
// Check if we already have a cached path
if let Some(path) = &self.cached_binary_path {
return Ok(Command {
command: path.clone(),
args: Vec::new(),
env: Default::default(),
});
}
Ok(Command {
command:
"/Users/kartik.gajendra/superposition/target/debug/supertoml-analyzer"
.to_string(),
args: Vec::new(),
env: Default::default(),
})
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don't ship a machine-specific analyzer path.

This only works on /Users/kartik.gajendra/..., so the extension cannot start the language server anywhere else, and the cache branch is never populated. Resolve the binary from the packaged extension/environment at runtime and fail cleanly when it is unavailable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tooling/lsp/zed-extension/src/lib.rs` around lines 20 - 34, Replace the
hard-coded machine-specific path by resolving the analyzer binary at runtime:
remove the literal "/Users/..." string in the Command returned and instead look
up the bundled/packaged binary (via environment/config, PATH lookup, or
extension resource loading) and populate self.cached_binary_path when found; if
resolution fails return an Err (or a clear failure Command) rather than trying
to spawn a non-existent path. Update the code that constructs Command (the block
that currently returns Command { command: "...".to_string(), ... }) to use the
resolved path and ensure cached_binary_path is set so the cache branch is
exercised.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"/Users/kartik.gajendra/superposition/target/debug/supertoml-analyzer"
.to_string(),
Can we change this ? This isn't gonna work on anywhere else

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the full current content of the lib.rs file
fd "lib.rs" tooling/lsp --exec cat {}

# Check what zed_extension_api version is used and what APIs are available
fd "Cargo.toml" tooling/lsp/zed-extension --exec cat {}

Length of output: 3123


🌐 Web query:

zed_extension_api 0.7 language_server_command binary resolution worktree which

💡 Result:

In zed_extension_api 0.7.x, Extension::language_server_command(...) must return a zed_extension_api::Command whose command field is a path string Zed can execute. The typical patterns are:

  1. Use a user/system-installed binary (preferred when you don’t bundle an LSP):
  • Call worktree.which("my-lsp"), which “returns the path to the given binary name, if one is present on the $PATH”.
  • Use the returned absolute path as Command.command, and usually pass env: worktree.shell_env() so the server inherits the worktree’s shell environment. [1][2]
  1. Use a binary bundled with the extension:
  • Return a path relative to the extension (e.g. bin/my-lsp or server/my-lsp).
  • On the host side, Zed resolves what you returned via path_from_extension(...) before launching the process (so the relative path becomes an absolute path inside the installed extension directory). [3]

So: worktree.which(...) is for resolving executables on the worktree $PATH, while relative command strings are resolved as extension-relative paths by Zed. [1][3]

Sources
[1] https://docs.rs/zed_extension_api/latest/zed_extension_api/struct.Worktree.html
[2] https://docs.rs/zed_extension_api/latest/zed_extension_api/trait.Extension.html
[3] https://readmex.com/en-US/zed-industries/zed/page-6.1822b1e7b-14b2-434c-96d4-a4bfc121f722


@sauraww — agreed, this is a blocker. The fix is already partially written in the commented-out code inside the file — it just needs to be activated. The worktree.which("supertoml-analyzer") API is the correct approach per the zed_extension_api docs: worktree.which("my-lsp") "returns the path to the given binary name, if one is present on the $PATH", and you should also pass env: worktree.shell_env() so the server inherits the worktree's shell environment.

⚠️ The workspace-relative std::path::Path::new(&path).exists() checks from the commented block should not be restored — Zed extensions run in a WASM sandbox and don't have direct filesystem access that way.

Here's the corrected language_server_command:

fn language_server_command(
    &mut self,
    _language_server_id: &LanguageServerId,
    worktree: &Worktree,                         // remove leading `_`
) -> Result<Command> {
    // Return cached path if already resolved
    if let Some(path) = &self.cached_binary_path {
        return Ok(Command {
            command: path.clone(),
            args: Vec::new(),
            env: worktree.shell_env(),
        });
    }

    // Resolve from the worktree's $PATH
    let binary = worktree
        .which("supertoml-analyzer")
        .ok_or_else(|| {
            "supertoml-analyzer not found in PATH. \
             Please build it with `cargo build -p supertoml_lsp` \
             and add the binary to your PATH.".to_string()
        })?;

    self.cached_binary_path = Some(binary.clone());

    Ok(Command {
        command: binary,
        args: Vec::new(),
        env: worktree.shell_env(),
    })
}

Key changes:

  1. Removed the hard-coded /Users/kartik.gajendra/... path entirely.
  2. worktree.which("supertoml-analyzer") resolves the binary from the user's $PATH — this is the standard pattern for Zed extensions that don't bundle their own LSP binary.
  3. cached_binary_path is now populated after a successful lookup, so the cache branch is actually exercised on subsequent calls.
  4. Returns an Err with a clear message when the binary is missing, rather than attempting to spawn a non-existent path.
  5. env: worktree.shell_env() passed in both branches so the server inherits the correct shell environment.

🧠 Learnings used
Learnt from: ayushjain17
Repo: juspay/superposition PR: 827
File: crates/superposition_core/src/config.rs:154-154
Timestamp: 2026-01-07T20:38:53.153Z
Learning: Deprecate jsonlogic-based context conditions and migrate to simple key-value pair map conditions across the codebase. Replace jsonlogic::apply usage with superposition_types::apply for condition evaluation. Update all relevant modules (notably Rust files under crates) to use the new approach, remove jsonlogic dependencies where applicable, and run full compilation and tests to catch regressions. Ensure context evaluation logic consistently uses key-value maps and that the architectural change is reflected in unit/integration tests.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sauraww yeah made this point during the standup. Review only the LSP. None of the plugins will work without code changes

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@Datron Datron force-pushed the claude/supertoml-lsp-server-6Mrbi branch 6 times, most recently from 6c4aa0d to 1be3a7c Compare March 18, 2026 10:45
Adds a new `supertoml_lsp` crate that implements a Language Server
Protocol server for the SuperTOML configuration format used by
Superposition.

Features:
- **Diagnostics** – two-pass validation: raw TOML syntax errors are
  reported with accurate byte-span positions (via the `toml` crate's
  span API); semantic errors from `parse_toml_config` are mapped to
  source ranges by searching for the offending token in the document.
- **Completion** – context-aware suggestions:
  - Dimension names inside `_context_ = { … }` inline tables
  - Config keys (from `[default-configs]`) on override key lines
  - `REGULAR` / `LOCAL_COHORT:<dim>` / `REMOTE_COHORT:<dim>` for
    `type = …` fields in `[dimensions]`
- **Hover** – shows position, type, and schema for dimension names;
  shows default value and schema for config keys.

The server communicates over stdin/stdout (standard LSP transport) and
is built with `tower-lsp`.

https://claude.ai/code/session_01BMN7G45dz4TsP4GkQYXCW5
Signed-off-by: datron <Datron@users.noreply.github.com>
@Datron Datron force-pushed the claude/supertoml-lsp-server-6Mrbi branch from 1be3a7c to beef656 Compare March 18, 2026 10:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants