Skip to content

feat(tree-sitter)!: Remove dependency on nvim-treesitter #707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ luac.out

# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib

# Executables
Expand Down
3 changes: 0 additions & 3 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1676,12 +1676,9 @@ More optimized version would be to create a lua file that has only necessary plu
-- ~/.config/nvim/lua/partials/org_cron.lua

-- If you are using lazy.vim do this:
local treesitter = vim.fn.stdpath('data') .. '/lazy/nvim-treesitter'
local orgmode = vim.fn.stdpath('data') .. '/lazy/orgmode'
vim.opt.runtimepath:append(orgmode)
vim.opt.runtimepath:append(treesitter)
-- If you are using Packer or any other package manager that uses built-in package manager, do this:
vim.cmd('packadd nvim-treesitter')
vim.cmd('packadd orgmode')

-- Run the orgmode cron
Expand Down
67 changes: 21 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
### Requirements

* Neovim 0.9.2 or later
* [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter)

### Installation

Expand All @@ -33,39 +32,31 @@ Use your favourite package manager:
```lua
{
'nvim-orgmode/orgmode',
dependencies = {
{ 'nvim-treesitter/nvim-treesitter', lazy = true },
},
event = 'VeryLazy',
config = function()
-- Load treesitter grammar for org
require('orgmode').setup_ts_grammar()

-- Setup treesitter
require('nvim-treesitter.configs').setup({
highlight = {
enable = true,
},
ensure_installed = { 'org' },
})

-- Setup orgmode
require('orgmode').setup({
org_agenda_files = '~/orgfiles/**/*',
org_default_notes_file = '~/orgfiles/refile.org',
})

-- NOTE: If you are using nvim-treesitter with `ensure_installed = "all"` option
-- add `org` to ignore_install
-- require('nvim-treesitter.configs').setup({
-- ensure_installed = 'all',
-- ignore_install = { 'org' },
-- })
end,
}
```

</details>

<details open>
<details>
<summary><b><a href="https://github.com/wbthomason/packer.nvim">packer.nvim</a></b></summary>
</br>

```lua
use {'nvim-treesitter/nvim-treesitter'}
use {'nvim-orgmode/orgmode', config = function()
require('orgmode').setup{}
end
Expand All @@ -79,7 +70,6 @@ end
</br>

```vim
Plug 'nvim-treesitter/nvim-treesitter'
Plug 'nvim-orgmode/orgmode'
```

Expand All @@ -90,7 +80,6 @@ Plug 'nvim-orgmode/orgmode'
</br>

```vim
call dein#add('nvim-treesitter/nvim-treesitter')
call dein#add('nvim-orgmode/orgmode')
```

Expand All @@ -104,29 +93,27 @@ since instructions above covers full setup
```lua
-- init.lua

-- Load custom treesitter grammar for org filetype
require('orgmode').setup_ts_grammar()

-- Treesitter configuration
require('nvim-treesitter.configs').setup {
highlight = {
enable = true,
},
ensure_installed = {'org'}, -- Or run :TSUpdate org
}

require('orgmode').setup({
org_agenda_files = {'~/Dropbox/org/*', '~/my-orgs/**/*'},
org_default_notes_file = '~/Dropbox/org/refile.org',
})
```

-- NOTE: If you are using nvim-treesitter with `ensure_installed = "all"` option
-- add `org` to ignore_install
-- require('nvim-treesitter.configs').setup({
-- ensure_installed = 'all',
-- ignore_install = { 'org' },
-- })

Or if you are using `init.vim`, wrap the above snippet like so:
```vim
" init.vim
lua << EOF

require('orgmode').setup_ts_grammar() ...
require('orgmode').setup({
org_agenda_files = {'~/Dropbox/org/*', '~/my-orgs/**/*'},
org_default_notes_file = '~/Dropbox/org/refile.org',
})

EOF
```
Expand Down Expand Up @@ -191,29 +178,17 @@ or a hands-on [tutorial](https://github.com/nvim-orgmode/orgmode/wiki/Getting-St

## Treesitter Info
The built-in treesitter parser is used for parsing the org files.
Highlights are experimental and partially supported.

### Advantages of treesitter over built in parsing/syntax:
* More reliable, since parsing is done with a proper parsing tool
* Better highlighting (Experimental, still requires improvements)
* Future features will be easier to implement because the grammar already parses some things that were not parsed before (tables, latex, etc.)
* Allows for easier hacking (custom motions that can work with TS nodes, etc.)

### Known highlighting issues and limitations
* LaTex is still highlighted through syntax file

### Improvements over Vim's syntax highlighting
* Better highlighting of certain parts (tags, deadline/schedule/closed dates)
* [Treesitter highlight injections](https://github.com/nvim-treesitter/nvim-treesitter/blob/4f2265632becabcd2c5b1791fa31ef278f1e496c/CONTRIBUTING.md#injections) through `#BEGIN_SRC filetype` blocks
* Headline markup highlighting (https://github.com/nvim-orgmode/orgmode/issues/67)

## Troubleshoot
### Indentation is not working
Make sure you are not overriding indentexpr in Org buffers with [nvim-treesitter indentation](https://github.com/nvim-treesitter/nvim-treesitter#indentation)

### I get `treesitter/query.lua` errors when opening agenda/capture prompt or org files
Make sure you are using latest changes from [tree-sitter-org](https://github.com/milisims/tree-sitter-org) grammar.<br />
by running `:TSUpdate org` and restarting the editor.
Tree-sitter parser might not be installed.
Try running `:lua require('orgmode.config'):reinstall_grammar()` to reinstall it.

### Dates are not in English
Dates are generated with Lua native date support, and it reads your current locale when creating them.<br />
Expand Down
4 changes: 3 additions & 1 deletion ftplugin/org.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ local utils = require('orgmode.utils')

vim.b.org_bufnr = vim.api.nvim_get_current_buf()

vim.treesitter.start()

config:setup_mappings('org', vim.b.org_bufnr)
config:setup_mappings('text_objects', vim.b.org_bufnr)
config:setup_foldlevel()
Expand All @@ -21,7 +23,7 @@ require('orgmode.org.indent').setup_virtual_indent()
vim.bo.modeline = false
vim.opt_local.fillchars:append('fold: ')
vim.opt_local.foldmethod = 'expr'
vim.opt_local.foldexpr = 'nvim_treesitter#foldexpr()'
vim.opt_local.foldexpr = 'v:lua.require("orgmode.org.fold").foldexpr()'
if utils.has_version_10() then
vim.opt_local.foldtext = ''
else
Expand Down
13 changes: 13 additions & 0 deletions lua/orgmode/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ function Config:__index(key)
return rawget(getmetatable(self), key)
end

function Config:install_grammar()
local ok = pcall(vim.treesitter.language.add, 'org')
if ok then
return
end
require('orgmode.utils.treesitter.install').run()
end

---@param url? string
function Config:reinstall_grammar(url)
return require('orgmode.utils.treesitter.install').run(url)
end

---@param opts table
---@return OrgConfig
function Config:extend(opts)
Expand Down
46 changes: 5 additions & 41 deletions lua/orgmode/init.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
_G.orgmode = _G.orgmode or {}
local ts_revision = 'f8c6b1e72f82f17e41004e04e15f62a83ecc27b0'
local setup_ts_grammar_used = false
---@type Org | nil
local instance = nil

Expand Down Expand Up @@ -98,52 +96,18 @@ function Org:setup_autocmds()
})
end

--- @param revision string?
function Org.setup_ts_grammar(revision)
setup_ts_grammar_used = true
local parser_config = require('nvim-treesitter.parsers').get_parser_configs()
---@diagnostic disable-next-line: inject-field
parser_config.org = {
install_info = {
url = 'https://github.com/nvim-orgmode/tree-sitter-org',
revision = revision or ts_revision,
files = { 'src/parser.c', 'src/scanner.c' },
},
filetype = 'org',
}
end

---@private
function Org._check_ts_grammar()
vim.defer_fn(function()
if setup_ts_grammar_used then
return
end
local parser_config = require('nvim-treesitter.parsers').get_parser_configs()
if parser_config and parser_config.org and parser_config.org.install_info.revision then
if parser_config.org.install_info.revision ~= ts_revision then
require('orgmode.utils').echo_error({
'You are using outdated version of tree-sitter grammar for Orgmode.',
'To use latest version, replace current grammar installation with "require(\'orgmode\').setup_ts_grammar()" and run :TSUpdate org.',
'More info in setup section of readme: https://github.com/nvim-orgmode/orgmode#setup',
})
end
else
require('orgmode.utils').echo_error({
'Cannot detect parser revision.',
"Please check your org grammar's install info.",
'Maybe you forgot to call "require(\'orgmode\').setup_ts_grammar()" before setup.',
})
end
end, 200)
function Org.setup_ts_grammar()
require('orgmode.utils').echo_info(
'calling require("orgmode").setup_ts_grammar() is no longer necessary. Dependency on nvim-treesitter was removed'
)
end

---@param opts? OrgDefaultConfig
---@return Org
function Org.setup(opts)
opts = opts or {}
Org._check_ts_grammar()
local config = require('orgmode.config'):extend(opts)
config:install_grammar()
instance = Org:new()
vim.defer_fn(function()
if config.notifications.enabled and #vim.api.nvim_list_uis() > 0 then
Expand Down
108 changes: 108 additions & 0 deletions lua/orgmode/org/fold.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
-- Taken from https://github.com/nvim-treesitter/nvim-treesitter

local api = vim.api
local ts_utils = require('orgmode.utils.treesitter')

---@type vim.treesitter.Query
local query = nil

local M = {}

-- This is cached on buf tick to avoid computing that multiple times
-- Especially not for every line in the file when `zx` is hit
local folds_levels = ts_utils.memoize_by_buf_tick(function(bufnr)
local max_fold_level = api.nvim_get_option_value('foldnestmax', { win = 0 })
local trim_level = function(level)
if level > max_fold_level then
return max_fold_level
end
return level
end

query = query or vim.treesitter.query.get('org', 'folds')
local trees = vim.treesitter.get_parser(bufnr):parse()
local root = trees[1]:root()

local matches = {}
for _, node in query:iter_captures(root, bufnr) do
table.insert(matches, node)
end

---@type table<number, number>
local start_counts = {}
---@type table<number, number>
local stop_counts = {}

local prev_start = -1
local prev_stop = -1

local min_fold_lines = api.nvim_get_option_value('foldminlines', { win = 0 })

for _, match in ipairs(matches) do
local start, _, stop, stop_col = match:range() ---@type integer, integer, integer, integer

if stop_col == 0 then
stop = stop - 1
end

local fold_length = stop - start + 1
local should_fold = fold_length > min_fold_lines

-- Fold only multiline nodes that are not exactly the same as previously met folds
-- Checking against just the previously found fold is sufficient if nodes
-- are returned in preorder or postorder when traversing tree
if should_fold and not (start == prev_start and stop == prev_stop) then
start_counts[start] = (start_counts[start] or 0) + 1
stop_counts[stop] = (stop_counts[stop] or 0) + 1
prev_start = start
prev_stop = stop
end
end

---@type string[]
local levels = {}
local current_level = 0

-- We now have the list of fold opening and closing, fill the gaps and mark where fold start
for lnum = 0, api.nvim_buf_line_count(bufnr) do
local prefix = ''

local last_trimmed_level = trim_level(current_level)
current_level = current_level + (start_counts[lnum] or 0)
local trimmed_level = trim_level(current_level)
current_level = current_level - (stop_counts[lnum] or 0)
local next_trimmed_level = trim_level(current_level)

-- Determine if it's the start/end of a fold
-- NB: vim's fold-expr interface does not have a mechanism to indicate that
-- two (or more) folds start at this line, so it cannot distinguish between
-- ( \n ( \n )) \n (( \n ) \n )
-- versus
-- ( \n ( \n ) \n ( \n ) \n )
-- If it did have such a mechanism, (trimmed_level - last_trimmed_level)
-- would be the correct number of starts to pass on.
if trimmed_level - last_trimmed_level > 0 then
prefix = '>'
elseif trimmed_level - next_trimmed_level > 0 then
-- Ending marks tend to confuse vim more than it helps, particularly when
-- the fold level changes by at least 2; we can uncomment this if
-- vim's behavior gets fixed.
-- prefix = "<"
prefix = ''
end

levels[lnum + 1] = prefix .. tostring(trimmed_level)
end

return levels
end)

---@return string
function M.foldexpr()
local buf = api.nvim_get_current_buf()
local levels = folds_levels(buf) or {}

return levels[vim.v.lnum] or '0'
end

return M
Loading