Tree-sitter support for SuperCollider

did you enable TS highlighting in your init.vim file?

lua <<EOF
require'nvim-treesitter.configs'.setup {
  highlight = {
    enable = true,
    custom_captures = {
      -- Highlight the @foo.bar capture group with the "Identifier" highlight group.
      ["foo.bar"] = "Identifier",
    },
  },
}
EOF
1 Like

FYI: nvim-treesitter has not updated it’s supercollider parser for a while (and thus does not contain all of the work done for supporting class writing etc) because of a strange bug. I’m working on it though

2 Likes

Thank you! I will follow this thread

Should be fixed now and also include the latest stuff. Update your treesitter plugin (if using nvim) and then don’t forget to :TSUpdate supercollider

4 Likes

HELP WANTED

Hello everyone. I really need some help in maintaining the tree-sitter grammar. Most of the language is implemented but there are constantly issues to resolve and it’s honestly too big of a project to do on my own. Please help out if you can.

4 Likes

The tree-sitter grammar has been updated significantly recently. In fact, all of the language has been implemented now (to my knowledge) except for two (tricky) things: List comprehensions and method names as binary operators.

nvim-treesitter should be updated soon to implement all of the new bug fixes and changes. Also, there’s a changelog now!

Playground
NeoVim users who are curious how your supercollider code is parsed by TS may use the playground plugin. The plugin has a bug for SC( see https://github.com/nvim-treesitter/playground/issues/55) , but I’ve made a fork that works for SuperCollider here:

2 Likes

Here is a live demo where you can see the code being parsed live. The tree-sitter tree is seen on the left and the SuperCollider code on the right.

playground2

2 Likes

Super Mads! You are a hero :slight_smile:
Eirik

Amazing work as usual!! I finally have some time to set up a proper vim environment for SC, looking forward to testing out SCNVim and your plugins

Pushed an update which fixes some minor issues in the query files and adds support for constants in classes and, more importantly, methods as infix binary operators ala true or: {0.5.coin}. We’ve now covered 99.5% of the language to my knowledge (constantly discover new features though).

Nv-treesitter will have an update of the parser soon.

Ranjith Hegde has added support for SuperCollider in the textobjects plugin for Neovim, allowing really cool syntax specific manipulation !!!

2 Likes

Hey :slightly_smiling_face:

Thanks for working on this! I am using nvim and I have treesitter and cmp working. It is all working for Rust for example but in SC I don’t get autocompletion options like you do here (I only get a popup with type ‘text’). Do you mind sharing your config?

Thanks!

Sure, here is mine:

-- Setup nvim-cmp.
local cmp = require'cmp'

-- local lspkind = require "lspkind"
-- lspkind.init()

vim.opt.completeopt = { "menu", "menuone", "noselect" }

-- Don't show the dumb matching stuff.
vim.opt.shortmess:append "c"

cmp.setup({
	snippet = {
		expand = function(args)
			require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
			-- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
		end,
	},
	mapping = {
		['<C-d>'] = cmp.mapping.scroll_docs(-4),
		['<C-f>'] = cmp.mapping.scroll_docs(4),
		['<C-Space>'] = cmp.mapping.complete(),
		['<C-e>'] = cmp.mapping.close(),
		['<CR>'] = cmp.mapping.confirm({ select = true }),
	},
	sources = {
		{ name = 'path' },
		{ name = 'luasnip' }, -- For luasnip users.
		{ name = 'nvim_lsp' },
		{ name = 'tags' },
		-- { name = 'nvim_lua' },
		{ name = 'treesitter' },
		-- { name = 'spell' },
		{ name = 'buffer' , keyword_length=5}, -- dont complete until at 5 chars
	},
	formatting = {
		-- set up nice formatting for your sources.
		-- format = lspkind.cmp_format {
		-- 	with_text = true,
		-- 	menu = {
		-- 		nvim_lsp = "[LSP]",
		-- 		nvim_lua = "[api]",
		-- 		path = "[path]",
		-- 		luasnip = "[snip]",
		-- 		-- gh_issues = "[issues]",
		-- 		rg = "[ripgrep]",
		-- 		tags = "[tags]",
		-- 		buffer = "[buf]",
		-- 	},
		-- },
	},
	view = {
		entries = "native",
	},
	experimental = {
		-- native_menu = true,
		ghost_text = true
	}
})

And then the sources I have installed as plugins:

use {'hrsh7th/nvim-cmp',
			disable = false,
			requires = {
			'hrsh7th/cmp-nvim-lsp',
			'hrsh7th/cmp-buffer',
			'quangnguyen30192/cmp-nvim-tags',
			'saadparwaiz1/cmp_luasnip',
			'hrsh7th/cmp-nvim-lua',
			-- 'f3fora/cmp-spell',
			'ray-x/cmp-treesitter',
			'hrsh7th/cmp-path',
			-- 'onsails/lspkind-nvim',
			-- 'lukas-reineke/cmp-rg'
			}, config = function()
	require"plugins/cmp"
end}
2 Likes

I seem to have a similar problem to Dionysis even with your config Mads! I see Snippet completions and Text but no luck with Tags or Treesitter. Just been lamely copypasting so apologies if I’ve missed something - any tips appreciated!

Did you install these plugins ?

		'hrsh7th/cmp-nvim-lsp',
		'hrsh7th/cmp-buffer',
		'quangnguyen30192/cmp-nvim-tags',
		'saadparwaiz1/cmp_luasnip',
		'hrsh7th/cmp-nvim-lua',
		-- 'f3fora/cmp-spell',
		'ray-x/cmp-treesitter',
		'hrsh7th/cmp-path',

You also need to generate snippets and tags in SCNvim to see those. See the SCnVim wiki for more info as well as luasnip setup

Thank you! I finally got it working. That was not easy :sweat_smile: But I am very disapointed that I am not getting the help file in the popup :face_with_monocle::stuck_out_tongue: Just kidding, so happy to have argument completion! I had tried to implement this in the past during the times when vimscript was the only option and had failed… This is luxury!

@semiquaver Don’t give up. There is kind of crazy amount of plugins involved to get the full experience! Here is my configuration in case it helps (Make sure you commit your current setup before experimenting with any cut and paste of my mess though…).

Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'}
Plug 'nvim-treesitter/nvim-treesitter-refactor'
Plug 'nvim-treesitter/playground'
Plug 'haorenW1025/completion-nvim'
Plug 'nvim-treesitter/completion-treesitter'
Plug 'nvim-treesitter/nvim-treesitter-textobjects'

Plug 'neovim/nvim-lspconfig'
Plug 'williamboman/nvim-lsp-installer'

Plug 'hrsh7th/cmp-nvim-lsp'
Plug 'hrsh7th/cmp-buffer'
Plug 'hrsh7th/cmp-path'
Plug 'hrsh7th/cmp-cmdline'
Plug 'hrsh7th/nvim-cmp'
Plug 'ray-x/cmp-treesitter'
Plug 'quangnguyen30192/cmp-nvim-tags'
Plug 'hrsh7th/cmp-nvim-lua'

" For vsnip users.
Plug 'hrsh7th/cmp-vsnip'

" Snippets {{{
Plug 'rafamadriz/friendly-snippets'
Plug 'hrsh7th/vim-vsnip'
Plug 'L3MON4D3/LuaSnip'
Plug 'saadparwaiz1/cmp_luasnip'

let g:scnvim_snippet_format = "luasnip"

" Jump forward or backward in vsnip
imap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
smap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'

" and here for luasnip
imap <silent><expr> <Tab> luasnip#expand_or_jumpable() ? '<Plug>luasnip-expand-or-jump' : '<Tab>' 
" -1 for jumping backwards.
inoremap <silent> <S-Tab> <cmd>lua require'luasnip'.jump(-1)<Cr>

snoremap <silent> <Tab> <cmd>lua require('luasnip').jump(1)<Cr>
snoremap <silent> <S-Tab> <cmd>lua require('luasnip').jump(-1)<Cr>

"}}}

set completeopt=menu,menuone,noselect

and for lua:

-- SuperCollider

require("luasnip").add_snippets("supercollider", require("scnvim/utils").get_snippets())

-- Rust
require('rust-tools').setup({})

require("nvim-treesitter.configs").setup {
  -- ensure_installed = {"supercollider", "rust", "html", "javascript"},
  ensure_installed = "maintained",
  highlight = {
    enable = true, additional_vim_regex_highlighting = true,
    -- disable = { "supercollider"},
  },
  incremental_selection = {
    enable = true,
    keymaps = {
      init_selection = "<CR>",
      scope_incremental = "<CR>",
      node_incremental = "<TAB>",
      node_decremental = "<S-TAB>",
    },
  },
  indent = { enable = true },
  matchup = { enable = true },
  autopairs = { enable = true },
  playground = {
    enable = true,
    disable = {},
    updatetime = 25,
    persist_queries = false,
    keybindings = {
      toggle_query_editor = "o",
      toggle_hl_groups = "i",
      toggle_injected_languages = "t",
      toggle_anonymous_nodes = "a",
      toggle_language_display = "I",
      focus_language = "f",
      unfocus_language = "F",
      update = "R",
      goto_node = "<cr>",
      show_help = "?",
    },
  },
  rainbow = {
    enable = true,
    extended_mode = true, -- Highlight also non-parentheses delimiters
    max_file_lines = 1000,
  },
  refactor = {
    smart_rename = { enable = true, keymaps = { smart_rename = "grr" } },
    highlight_definitions = { enable = true },
    navigation = {
      enable = true,
      keymaps = {
        goto_definition_lsp_fallback = "gnd",
        -- use telescope for these lists
        -- list_definitions = "gnD",
        -- list_definitions_toc = "gO",
        -- @TODOUA: figure out if I need both below
        goto_next_usage = "<a-*>", -- is this redundant?
        goto_previous_usage = "<a-#>", -- also this one?
      },
      disable = { "supercollider"},
    },
    -- highlight_current_scope = {enable = true}
  },
  textobjects = {
    lsp_interop = {
      enable = true,
      border = "none",
      peek_definition_code = {
        ["df"] = "@function.outer",
        ["dF"] = "@class.outer",
      },
    },
    move = {
      enable = true,
      set_jumps = true, -- whether to set jumps in the jumplist
      goto_next_start = {
        ["]m"] = "@function.outer",
        ["]]"] = "@call.outer",
      },
      goto_next_end = {
        ["]M"] = "@function.outer",
        ["]["] = "@call.outer",
      },
      goto_previous_start = {
        ["[m"] = "@function.outer",
        ["[["] = "@call.outer",
      },
      goto_previous_end = {
        ["[M"] = "@function.outer",
        ["[]"] = "@call.outer",
      },
    },
    select = {
      enable = true,
      lookahead = true,
      keymaps = {
        ["af"] = "@function.outer",
        ["if"] = "@function.inner",
        ["ac"] = "@call.outer",
        ["ic"] = "@call.inner",
      },
    },
    swap = {
      enable = true,
      swap_next = {
        [",a"] = "@parameter.inner",
      },
      swap_previous = {
        [",A"] = "@parameter.inner",
      },
    },
  },
}

vim.opt.foldmethod = "expr"
vim.opt.foldexpr = "nvim_treesitter#foldexpr()"

local cmp = require'cmp'

vim.opt.completeopt = { "menu", "menuone", "noselect" }
vim.opt.shortmess:append "c"

cmp.setup {
  snippet = {
      -- REQUIRED - you must specify a snippet engine
      expand = function(args)
        vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
        require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
        -- require('snippy').expand_snippet(args.body) -- For `snippy` users.
        -- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
      end,
    },
	mapping = {
		['<C-d>'] = cmp.mapping.scroll_docs(-4),
		['<C-f>'] = cmp.mapping.scroll_docs(4),
		['<C-Space>'] = cmp.mapping.complete(),
		['<C-e>'] = cmp.mapping.close(),
		['<CR>'] = cmp.mapping.confirm({ select = true }),
	},
	sources = {
		{ name = 'path' },
		{ name = 'vsnip' },
		{ name = 'luasnip' },
		{ name = 'nvim_lsp' },
		{ name = 'tags' },
		-- { name = 'nvim_lua' },
		{ name = 'treesitter' },
		-- { name = 'spell' },
		{ name = 'buffer' , keyword_length=5}, -- dont complete until at 5 chars
	},
	view = {
		entries = "native",
	},
	experimental = {
		-- native_menu = true,
		ghost_text = true
	}
}

-- The nvim-cmp almost supports LSP's capabilities so You should advertise it to LSP servers..
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = require('cmp_nvim_lsp').update_capabilities(capabilities)

-- The following example advertise capabilities to `clangd`.
require'lspconfig'.clangd.setup {
  capabilities = capabilities,
}

Good luck! :slightly_smiling_face:

EDIT: Also check here Additional configuration · davidgranstrom/scnvim Wiki · GitHub

1 Like

whoop whoop! Congratulations!

Ps all of this dependency handling of plugins is a bit easier with Packer as packagemanager. It allows you to set these as dependencies of the main cmp plugin, which in turn allows you to disable the cmp plugin and then automatically all of the dependendents. Just a tip.

1 Like

Ah yes, I can see everyone using Packer. It is just that I have a 1000 lines configuration in vimscript and haven’t decided to transition to lua yet. I literally just added a require for a config.lua a few days ago to manage treesitter and cmp configurations! I do like the idea of moving to lua though so it is just a matter of time :slightly_smiling_face: Again thanks for your work!! Great stuff :upside_down_face:

1 Like