Skip to content

Add advanced language features for Csound via custom Tree-sitter–based LSP#42

Open
PasqualeMainolfi wants to merge 26 commits intocsound:masterfrom
PasqualeMainolfi:lsp-dev-fresh
Open

Add advanced language features for Csound via custom Tree-sitter–based LSP#42
PasqualeMainolfi wants to merge 26 commits intocsound:masterfrom
PasqualeMainolfi:lsp-dev-fresh

Conversation

@PasqualeMainolfi
Copy link

Csound vscode

This PR introduces a new Csound extension for Visual Studio Code built on top of a custom Csound Language Server (LSP) and a Csound Tree-sitter grammar specifically designed for Csound.

The extension aims to provide a modern, semantically aware editing experience for Csound users, fully compatible with Csound7.

Key Features

LSP

  • Advanced diagnostics
  • Detection of unused variables
  • Detection of undefined variables
  • Opcode error reporting
  • Advanced scoping logic, aware of instruments, UDOs, score blocks, and global contexts
  • Precise error localization based on the Tree-sitter syntax tree

Editor

  • Format on type
  • CodeLens for:
    • Running a script
    • Saving output to an audio file
    • Opening the Csound manual
  • Code Actions for quick fixes and navigation

Completion & Hover

  • Opcode completion
  • Option/flag completion
  • Hover support for:
    • Built-in opcodes
    • User-defined opcodes (UDOs)
    • User-defined types

Commands exposed by the Language Server

The server exposes the following commands:

  • csound-lsp.run_file — run the current Csound script
  • csound-lsp.to_audio_file — render and save output to an audio file
  • csound-lsp.open_manual — open the Csound reference manual

Offline Manual (Web Preview)

  • The Csound HTML reference manual is bundled with the language server
  • A lightweight local HTTP server is started by the server
  • The manual is displayed inside a VS Code Webview
  • Fully functional offline browsing, including links, searching and assets

Language Injections

  • Python and HTML language injections are supported inside Csound files
  • Enables proper highlighting and tooling for embedded code

Technical Notes

  • Syntax parsing is handled via Tree-sitter
  • Semantic analysis is built directly on the syntax tree
  • The LSP architecture is modular and designed for future extensions

Future Work

Planned next steps include:

  • Support for Csound plugins (not yet tested)
  • Deeper support for Cabbage blocks
  • Additional semantic checks, completions, and editor actions

This PR lays the groundwork for a complete and modern Csound development environment in VS Code, aligned with current language-server–based tooling.
Feedback and testing are highly appreciated.

@kunstmusik
Copy link
Member

Just wanted to note that I saw this PR but it came in while I was traveling for holidays. I'm on another trip now so will look at this when I return home in a couple days. Apologies @PasqualeMainolfi for the delay in review!

@kunstmusik
Copy link
Member

@PasqualeMainolfi One thing I did want to ask is if you tested this as a Web Extension (i.e., when installed and running in github.dev). I didn't see code to support that but may have just missed it.

@PasqualeMainolfi
Copy link
Author

No worries.
In the meantime, I’m working on fixing a few bugs and improving the parsing of .udo files when they are used as imports. I also need to add support for the additional syntax introduced for raw strings (R{}R).
I haven’t tested the extension as a Web Extension on github.dev.
This extension relies on a language server process and a Tree-sitter grammar, which currently require a Node / native (or WASM) environment and are not compatible with VS Code for the Web out of the box.
For this reason, the extension is intended to run in the desktop VS Code environment only.
If web support becomes a goal in the future, it would likely require a web-based LSP and additional work for Tree-sitter compatibility.

Copy link
Contributor

@rorywalsh rorywalsh left a comment

Choose a reason for hiding this comment

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

Thank you @PasqualeMainolfi, it's going to be very nice to have this feature. I am going to leave the technical review to @kunstmusik as he is more familiar with these things, but I am concerned that this PR overwrites the project's readme file. This removes all the historical context for the existing project as well as removing valuable information about live-coding and using the UDP server. Would you mind reverting the README to the original master branch version. We can then update accordingly after any merge.

@PasqualeMainolfi
Copy link
Author

I’ve restored the original README to preserve the historical context and existing information. Additionally, I’ve added release notes to the PR, without referencing any version numbers.

@rorywalsh
Copy link
Contributor

Thanks @PasqualeMainolfi - I know Steven is extremely busy right now, but hopefully we can get this merged and into the extension in time for the first release of Csound 7 👍

@PasqualeMainolfi
Copy link
Author

I was wondering if there is any approximate timeline available for the release of Csound7. Having a rough idea of the release date would help me plan the remaining work and properly schedule the effort needed to deliver a stable version of the LSP.

I am already quite close, and only a few final adjustments are still missing.

Thanks in advance for any information you can share.

@rorywalsh
Copy link
Contributor

Good question. I think most of the tasks have been completed at this stage. So it could be in the next few weeks but I don't think any hard deadline has been set.

src/utils.ts Outdated
try {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `Installazione Csound LSP (${binaryName})...`,
Copy link
Member

Choose a reason for hiding this comment

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

This should be in English to match the rest of the plugin. (I haven't done I18N for vscode plugins before; we could investigate that in a separate PR)

@kunstmusik
Copy link
Member

@PasqualeMainolfi I ran this in the extension development host. It requested installing the LSP and then reported success in installing the LSP. I checked before and after and CSD and ORC code looks no difference with little syntax highlighting. I did not get any manual entries, autocomplete, etc. I am on MacOS. Unsure if something's gone wrong in installation or what but it's not operating as (I assume) expected.

image

@PasqualeMainolfi
Copy link
Author

It seems very strange. Check the lsp-bin folder and verify the downloaded executable, it’s probably a permissions issue. Also, check /var/folders/hp/.../T/ (temporary files) to see if the csound-lsp-temp-folder exists. This is where it downloads the external resources it uses. I’ve run several tests and everything seems fine. Additionally, I made a modification to the source code to avoid issues with the local manual server, especially on Windows (though I don’t think this is the cause of the problem).

One more thing: there is a check in the extension that disables the LSP in web environments:

  if (vscode.env.uiKind === vscode.UIKind.Web) {
    console.warn("LSP disabled in web");
    return;
  }
Screenshot 2026-01-12 alle 00 15 01 Screenshot 2026-01-12 alle 00 16 44 Screenshot 2026-01-12 alle 00 17 00 Screenshot 2026-01-12 alle 00 17 25 Screenshot 2026-01-12 alle 00 20 24

@kunstmusik
Copy link
Member

I updated to latest and am getting further. Parser seems to have problems with floating point numbers that start with decimal:

image

I think because of the decimal issue it reports unclosed blocks:

image

Also multiline comments seem to throw things off:

image

@PasqualeMainolfi
Copy link
Author

I think I have fixed the bugs you showed me.
To give priority to the LSP for handling multi-line comments, I had to disable the comments section in language.
Note that the LSP checks installed plugins on each startup. Could you please verify if everything is working as expected by checking the Developer Tools / Show Dev Logs for the Csound Language Server?
If anything was missed or there are additional things to implement, we can go over the details next.

@kunstmusik
Copy link
Member

@PasqualeMainolfi I tried the latest and when I start up the extension host it asks to download a newer version of LSP. I says yet and then it errors:

image

@PasqualeMainolfi
Copy link
Author

Thanks for the report!
The issue was caused by using a for...in loop instead of for...of when iterating over the files returned by fs.readdirSync().
for...in iterates over array indices (e.g. 0, 1, …), which caused the extension to try to unlink non-existent paths like lsp-bin/0.
This has now been fixed by switching to for...of, which correctly iterates over the actual file names.
The installer should work correctly now.

@kunstmusik
Copy link
Member

One other note, I asked Opus about using tree-sitter in Javascript so that we can use it in the web extension version of this plugin. Looks like compiling to WASM would be an option, which is promising.

Looking at this PR, your concern is valid. The PR introduces a Csound Language Server built on a custom Tree-sitter grammar, which typically compiles to native binaries. This would indeed be incompatible with running as a VS Code web extension.

The Good News

Yes, Tree-sitter parsers can run in JavaScript/WebAssembly. Tree-sitter supports compiling grammars to WASM, which can then be loaded and run in JavaScript environments (including browsers and VS Code web extensions).

How to Make It Work for Web Extensions

The tree-sitter-csound grammar would need to be compiled to WASM instead of (or in addition to) native binaries. Here's what's typically required:

  1. Compile the grammar to WASM:

    tree-sitter build --wasm
  2. Use web-tree-sitter - the official JavaScript/WASM binding:

    npm install web-tree-sitter
  3. Load the parser in JS:

    const Parser = require('web-tree-sitter');
    await Parser.init();
    const parser = new Parser();
    const Csound = await Parser.Language.load('tree-sitter-csound.wasm');
    parser.setLanguage(Csound);

Recommendations for the PR

  1. Ask the PR author if the LSP server (csound-lsp) supports running in a browser/Node.js environment with the WASM-compiled parser, or if it's strictly a native binary.

  2. Consider a hybrid approach:

    • Native LSP for desktop (full features, better performance)
    • WASM-based parsing for web extension (potentially reduced features)
  3. Check the LSP implementation language - if it's written in a language that can compile to WASM (Rust, TypeScript, etc.), the entire LSP could potentially run in the browser.

@PasqualeMainolfi
Copy link
Author

Yes, I can.
I think, the best approach would be to consider a hybrid version: a full-featured implementation for desktop, and a slightly limited version for the web. Certain features, like file watchers and other system-level integrations, simply cannot be ported to a web environment. Rust (and tree-sitter) is not an issue... the main challenge is deciding which parts of the functionality to bring to the web and which to leave out.

@kunstmusik
Copy link
Member

kunstmusik commented Jan 21, 2026

I think we're on the same page, offer what we can in each environment. I think this will work out nicely and users will be happy. :)

@rorywalsh
Copy link
Contributor

Thanks @PasqualeMainolfi it wasn't the vertical line but the issue with the chnget opcode not being recognized. Looks like it's fixed now. 👍

@PasqualeMainolfi
Copy link
Author

@kunstmusik you can try the web version now. It’s still a work in progress: at the moment I’ve implemented the basic features such as syntax highlighting, basic completion, and indentation. Diagnostics and injections are still missing, but I’m currently working on them.
For now, I’d mainly like you to check whether you encounter any issues during the initialization phase.

@kunstmusik
Copy link
Member

I tried the web version and highlighting worked. Looks like command names were updated for desktop but not for web, as trying to render a document ends up with an error. Opus suggests:

Looking at the error and the code, the issue is that the PR's LSP server exposes a command csound-lsp.run_file (as mentioned in the README), but in the web extension, the play command is registered as extension.csoundPlayActiveDocument and uses the CsoundWebViewProvider approach.

The error command 'csound.runFile' not found suggests there's a mismatch - something is trying to call csound.runFile which doesn't exist in the web extension context.

The Problem

The PR likely registers CodeLens actions that try to execute csound.runFile (or csound-lsp.run_file), but:

  1. The web extension's extension.ts doesn't register that command
  2. The LSP server commands may not be properly exposed to the web extension context

Quick Fix

You need to register the missing command in the web extension. Add this to your activate function:

// ...existing code...

const evalScoCommand = vscode.commands.registerTextEditorCommand(
    "extension.csoundEvalSco",
    (textEditor: vscode.TextEditor) => {
      const content = getEvalText(textEditor);
      if (content.trim()) {
        csoundWebViewProvider.evalSco(content);
      } else {
        vscode.window.showWarningMessage("No score code selected or found.");
      }
    }
  );
  context.subscriptions.push(evalScoCommand);

  // Register the csound.runFile command that CodeLens expects
  const runFileCommand = vscode.commands.registerCommand(
    "csound.runFile",
    async (uri?: vscode.Uri) => {
      const editor = vscode.window.activeTextEditor;
      if (editor) {
        const content = getActiveDocumentContent(editor);
        if (content) {
          const relativePath = vscode.workspace.asRelativePath(editor.document.uri);
          await csoundWebViewProvider.playCsd(content, relativePath);
          vscode.commands.executeCommand('csound.webview.focus');
        }
      } else {
        vscode.window.showErrorMessage("No active Csound file to run.");
      }
    }
  );
  context.subscriptions.push(runFileCommand);
}

// ...existing code...

Additional Commands to Consider

If the LSP also uses csound-lsp.run_file, csound-lsp.to_audio_file, or csound-lsp.open_manual, you may need to register those as well (or alias them):

  // Alias for LSP command name
  const runFileLspCommand = vscode.commands.registerCommand(
    "csound-lsp.run_file",
    async () => {
      vscode.commands.executeCommand("csound.runFile");
    }
  );
  context.subscriptions.push(runFileLspCommand);

  const toAudioFileCommand = vscode.commands.registerCommand(
    "csound-lsp.to_audio_file",
    async () => {
      vscode.window.showInformationMessage(
        "Render to audio file is not supported in the web extension."
      );
    }
  );
  context.subscriptions.push(toAudioFileCommand);

This should resolve the command not found error. The root issue is that the LSP's CodeLens features are triggering commands that weren't registered in the web extension context.

@PasqualeMainolfi
Copy link
Author

@kunstmusik the web version does not depend on the desktop LSP. The extension has two different entry points.
For the web version, I’m working on a separate LSP written in TS, which you can find in the web folder.
The commands exposed by the desktop LSP use the csound. prefix, while the commands currently used in the web version are the ones you implemented. These should now be working properly.
For convenience, I’ve also added CodeLens actions for Run and Kill process.
At this point, the following features should be working: completion, injections, indents, syntax highlighting, run and stop commands.
Diagnostics are still missing, but I’m already working on them, along with the possibility to access the manual.
If you have any specific requests or ideas, feel free to ask, I’m happy to implement or adjust things as needed.

@rorywalsh
Copy link
Contributor

I just updated here and I can no longer build due to the tree-sitter-csound dep which itself has a load of other dependencies, Emsctipten, Docker? Is it possible to continue building the desktop version without having to install these dependencies. I'm seriously low on disk space.

@PasqualeMainolfi
Copy link
Author

@rorywalsh Ok, it is possible! I've added the compiled .wasm so you don't have to build it yourself for this testing phase. Later on, I'll release an npm package so you can install it directly without needing any extra build tools for the wasm.
Try now and let me know...

@rorywalsh
Copy link
Contributor

Thanks @PasqualeMainolfi I can now build locally again, but nothing seems to work. I see no syntax highlighting at all. I wonder if it might be best to try to complete the desktop stuff first, and then in another PR look at the web stuff? The desktop stuff was almost ready for merging as far as I could make out.

@PasqualeMainolfi
Copy link
Author

@rorywalsh That seems strange. I’ve just tested the latest desktop version and everything works fine on my end. You might want to try downloading the latest desktop version.
Screenshot 2026-02-12 alle 17 35 45

@rorywalsh
Copy link
Contributor

rorywalsh commented Feb 12, 2026 via email

@PasqualeMainolfi
Copy link
Author

@rorywalsh update your local repository to the latest commit, rebuild the project, and it should automatically notify you about the new LSP version.

@rorywalsh
Copy link
Contributor

Ok, back up and running now. I'm starting to see these kinds of lines in teh Cabbage JSON section now?

image

@rorywalsh
Copy link
Contributor

Loos good now with the latest change 👍

@PasqualeMainolfi
Copy link
Author

@rorywalsh perfect!

@rorywalsh
Copy link
Contributor

rorywalsh commented Feb 13, 2026

Oh, I'm seeing this now?

image

I get there might be an issue with cabbageGetValue but the init opcodes should be fine?

@PasqualeMainolfi
Copy link
Author

@rorywalsh check the LSP version in the lsp-bin folder, it should be 1.4.6. The web version is separate from the desktop one, so they don't touch each other. I did a fresh test from scratch just to be sure, and it all works fine. Additionally, verify that the csound-lsp_temp_folder directory exists within your system's temporary folders. On macOS, you can find it under /var/folders/.../T/, while on Windows it should be in the standard temp directory (usually %TEMP%). This folder is where all the extension's resources are stored. Checking your local repository and perhaps performing a rebase to ensure you are up to date. It really seems like this might be a local environment issue.

Screenshot 2026-02-13 alle 17 04 04 Screenshot 2026-02-13 alle 17 03 55

@PasqualeMainolfi
Copy link
Author

@rorywalsh If you'd like, feel free to send me the entire script. There might be something breaking the syntax that I'm missing just by looking at the photo.

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.

3 participants