Tree-sitter support for SuperCollider

If you are able to make TS crash reproducibly with any class code, please post it here so we can investigate further. This is very interesting!

Will do - I’m hoping it’s just bc I’m using old code.

Make sure you are running tree-sitter version 0.20.7. I suspect this release fixed the segfaults

Hi @dyalan.
Thanks for you long and detailed notes about setting the beast up on emacs. Thanks to this I’ve running tree-sitter on my archlinux.
Just to add something, as for now this line is not recognized as correct (at least for my box)
(add-to-list 'tree-sitter-major-mode-language-alist '(sclang-mode . supercollider))

Instead of this, I’ve added:

(after! tree-sitter
  (add-to-list 'tree-sitter-major-mode-language-alist
               '(sclang-mode . supercollider)))

(use-package! tree-sitter-langs
  :after tree-sitter
  :config
  ;; Ensure SuperCollider grammar is loaded
  (tree-sitter-require 'supercollider))

Did you make some new functionality for sclang in emacs with tree-siiter? Would you give me some clue, what it is good for?
best

2 Likes

Hi all.

Happy to announce that tree-sitter-supercollider has gotten some serious love recently and is now even more robust. A lot of work by bsssssss, thanks!

tree-sitter-supercollider is now able to parse almost anything from SuperCollider syntax (that said, I keep discovering new esoteric syntax goodies (see issues)).

5 Likes

For those using sclang-mode in Emacs 29.1 and above, here is an experimetal (especially the indentation) sclang-ts-mode implementing tree-sitter grammar based syntax highlighting (fontification) and indentation.

Many thanks to Mads K and the contributors!

Any feedback is very welcome!

1 Like

Hey yo,

I’ve been hacking the ts grammar and opened this:

Some changes:

  • makes method chains bind before any binary operator via _primary_postfix
  • adds List Comprehensions
  • flattens all binary operators into one left-to-right precedence rule (which matches sclang)
  • fixes named arguments (name: value)
  • differentiates { … } code blocks and ( … ) groups

Currently, the branch has 16 small commits, making it easy to review changes incrementally.

Sharing a few more suggestions, this time also for scanner.c, to enhance clarity and speed. The scanner now handles low-level details within Treesitter’s grammar.

For instance, I’ve replaced conditionals and regular expression checks with lookup tables for whitespace and operators, enabling sort speed to be constant. The updated scanner effectively manages nested /* ... */ comments, maintaining comment depth and allowing graceful recovery from unclosed comments.

Additionally, a state machine is implemented to identify operators such as ++ and ->, as well as pattern-matching operators of 1 to 3 characters, rather than relying on string comparisons. It is faster and more convenient, since it can infer even new operators. The scanner utilizes a compact state representation (only two bytes) to track comment depth and array depth, enabling even smoother incremental parsing.

The code ended up being around 200 lines shorter. Glad to receive feedback, this is still experimental.

When cloning the repo I’m asked for a password:

 git clone https://github.com/supercollider/tree-sitter-supercollider.git
Klone nach 'tree-sitter-supercollider' …
Username for 'https://github.com': 

Cloning other repos works fine without any password.

You need to use ssh, I think :slight_smile:

1 Like

"Hi there!

I’m encountering a weird issue with the SC Tree-sitter parser. It seems to only recognize the syntax on the first line of the buffer, and only if there are no spaces between characters.

As soon as I move to a new line, the highlighting stops working completely. If I run :Inspect, I get the message: ‘No items found at position x,y in buffer z’.

Tree-sitter works on my system for other languages like C++ and Python, so it seems specific to the SC parser or configuration.

I’d really appreciate any help!

I’m running Linux Mint 22.1, SC 3.14.0 and nvim 0.11.5.
Here is the output of nvim command :version

NVIM v0.11.5                                                                                           
Build type: Release                                                                                    
LuaJIT 2.1.1741730670                                                                                                                                                                               

Below are my init.lua file and some videos showing the issue:


vim.cmd("syntax on")
vim.opt.relativenumber = true
vim.opt.wrap = false
vim.cmd("filetype plugin indent on")
vim.cmd("filetype plugin on")

local Plug = vim.fn['plug#']
vim.call('plug#begin')
Plug 'sheerun/vim-polyglot'
Plug 'flazz/vim-colorschemes'
Plug 'folke/tokyonight.nvim'
Plug 'catppuccin/nvim'
Plug 'rebelot/kanagawa.nvim'
Plug 'ellisonleao/gruvbox.nvim'
Plug 'nvim-treesitter/nvim-treesitter'
Plug 'davidgranstrom/scnvim'
Plug 'madskjeldgaard/nvim-supercollider-piano'
Plug 'martineausimon/nvim-lilypond-suite'
Plug 'vimwiki/vimwiki'
vim.call('plug#end')


vim.cmd("colorscheme tokyonight-night")

vim.api.nvim_create_autocmd({"BufRead", "BufNewFile"}, {
  pattern = {"*.sc", "*.scd"},
  command = "set filetype=supercollider",
})


require'nvim-treesitter'.setup {
indent = {
	enable = {},
	disable = {"supercollider"}
}}


--scnvim setup
local scnvim = require 'scnvim'
local map = scnvim.map
local map_expr = scnvim.map_expr
scnvim.setup({
  keymaps = {
    ['<M-e>'] = scnvim.map('editor.send_line', {'i', 'n'}),
    ['<C-e>'] = {
      scnvim.map('editor.send_block', {'i', 'n'}),
      scnvim.map('editor.send_selection', 'x'),
    },
    ['<CR>'] = map('postwin.toggle'),
    ['<M-CR>'] = map('postwin.toggle', 'i'),
    ['<M-L>'] = map('postwin.clear', {'n', 'i'}),
    ['<C-k>'] = map('signature.show', {'n', 'i'}),
    ['<F12>'] = map('sclang.hard_stop', {'n', 'x', 'i'}),
    ['<leader>st'] = map('sclang.start'),
    ['<leader>sk'] = map('sclang.recompile'),
    ['<F1>'] = map_expr('s.boot'),
    ['<F2>'] = map_expr('s.meter'),
  },
  editor = {
    highlight = {
      color = 'IncSearch',
      flash = {
      duration = 100,  
      repeats = 1 }  
    },
  },
  postwin = {
    float = {
      enabled = true,
    },
  },
  postwin = { float = { enabled = true }, orientation = 'v', direction = 'right', size = 50 },
})
scnvim.load_extension('piano')

-- scnvim globals
vim.g.scnvim_postwin_syntax_hl = 1
vim.g.scnvim_scdoc = 1



--lilypond setup
require('nvls').setup({
  lilypond = {
    mappings = {
      player = "<F3>",
      compile = "<F5>",
      open_pdf = "<F6>",
      switch_buffers = "<A-Space>",
      insert_version = "<F4>",
      hyphenation = "<F12>",
      hyphenation_change_lang = "<F11>",
      insert_hyphen = "<leader>ih",
      add_hyphen = "<leader>ah",
      del_next_hyphen = "<leader>dh",
      del_prev_hyphen = "<leader>dH",
    },
    options = {
      pitches_language = "default",
      hyphenation_language = "en_DEFAULT",
      output = "pdf",
      backend = nil,
      main_file = "main.ly",
      main_folder = "%:p:h",
      include_dir = nil,
      pdf_viewer = nil,
      errors = {
        diagnostics = true,
        quickfix = "external",
        filtered_lines = {
          "compilation successfully completed",
          "search path"
        }
      },
    },
  },
  latex = {
    mappings = {
      compile = "<F5>",
      open_pdf = "<F6>",
      lilypond_syntax = "<F3>"
    },
    options = {
      lilypond_book_flags = nil,
      clean_logs = false,
      main_file = "main.tex",
      main_folder = "%:p:h",
      include_dir = nil,
      lilypond_syntax_au = "BufEnter",
      pdf_viewer = nil,
      errors = {
        diagnostics = true,
        quickfix = "external",
        filtered_lines = {
          "Missing character",
          "LaTeX manual or LaTeX Companion",
          "for immediate help.",
          "Overfull \\hbox",
          "^%s%.%.%.",
          "%s+%(.*%)"
        }
      },
    },
  },
  texinfo = {
    mappings = {
      compile = "<F5>",
      open_pdf = "<F6>",
      lilypond_syntax = "<F3>"
    },
    options = {
      lilypond_book_flags = "--pdf",
      clean_logs = false,
      main_file = "main.texi",
      main_folder = "%:p:h",
      lilypond_syntax_au = "BufEnter",
      pdf_viewer = nil,
      errors = {
        diagnostics = true,
        quickfix = "external",
        filtered_lines = {
          "Missing character",
          "LaTeX manual or LaTeX Companion",
          "for immediate help.",
          "Overfull \\hbox",
          "^%s%.%.%.",
          "%s+%(.*%)"
        }
      },
    },
  },
  player = {
    mappings = {
      quit = "q",
      play_pause = "p",
      loop = "<A-l>",
      backward = "h",
      small_backward = "<S-h>",
      forward = "l",
      small_forward = "<S-l>",
      decrease_speed = "j",
      increase_speed = "k",
      halve_speed = "<S-j>",
      double_speed = "<S-k>"
    },
    options = {
      row = 1,
      col = "99%",
      width = "37",
      height = "1",
      border_style = "single",
      winhighlight = "Normal:Normal,FloatBorder:Normal,FloatTitle:Normal",
      midi_synth = "fluidsynth",
      fluidsynth_flags = nil,
      timidity_flags = nil,
      ffmpeg_flags = nil,
      audio_format = "mp3",
      mpv_flags = {
        "--msg-level=cplayer=no,ffmpeg=no,alsa=no",
        "--loop",
        "--config-dir=/dev/null",
        "--no-video"
      }
    },
  },
})




I get the same thing here (though with a bigger config, which is basically default LazyVim + scnvim), seems that spaces and newlines throw off the parser. If I do InspectTree, (1+1) seems to get parsed fine, whereas 1+1 shows an error in the tree, so does (1+ 1) or (1+1 ). (1 +1) does not show an error node but just gets parsed wrong.

tree-sitter is working for me as expected on Mac with a complex config…

tried this again now with no plugins other than nvim-treesitter enabled. No dice.
Checkchealth looks fine too.

Ubu Studio 24.04
NVIM v0.11.5 (it’s the snap package)
Build type: RelWithDebInfo
LuaJIT 2.1.1741730670
tree-sitter-cli 0.26.5

my treesitter is 0.25.2 from homebrew - (no cli tool) perhaps the problem comes with 0.26?

Oh I should clarify though, I’ve never seriously tried to get the treesitter to work for supercollider before. So it’s not a situation where I upgraded to something and it broke… It might still be something that I’m doing wrong on my end. (But it works fine for other languages).

The nvim-treesitter plugin actually says it requires version 0.26 of that cli… so I guess I’d have to downgrade that one also.

Same here, I’m trying to set up nvim for SuperCollider for the first time too, I’m actually a beginner in vim and nvim. Tried a clean install with no other plugins, but still no luck.

I’m going to try downgrading nvim-treesitter to see if that solves the problem

Ok, now I tried on macos (15.7.3, Intel) with the same nvim config (fresh lazyvim install + TSInstallFromGrammar supercollider and there it works). So it appears to be a linux bug :frowning:
EDIT: running tree-sitter parse (outside nvim, in linux) works fine, though. So it’s got to do with the nvim-treesitter somehow, and only under linux … beats me.

@madskjeldgaard, anything in particular that would be useful to try for a bug report?

happy to report that nvim-treesitter now started working again for supercollider, without changes to my config… so @brazorf you could just try updating the plugin, they must have changed something within the last few days?

1 Like