From 914de065451ac7d0e14be14f29e7b73186494220 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 14 Dec 2021 20:19:02 +0100 Subject: [PATCH 1/3] Migration to ts-org parser 0.2.0. --- README.md | 4 +- lua/orgmode/colors/todo_highlighter.lua | 6 +- lua/orgmode/objects/date.lua | 30 ++++++++ lua/orgmode/objects/edit_special/init.lua | 7 +- lua/orgmode/org/mappings.lua | 63 +++++++++++++---- lua/orgmode/parser/file.lua | 32 ++++----- lua/orgmode/parser/section.lua | 85 +++++++++++------------ lua/orgmode/utils/init.lua | 31 ++------- queries/org/highlights.scm | 28 ++++---- queries/org/injections.scm | 2 +- 10 files changed, 158 insertions(+), 130 deletions(-) diff --git a/README.md b/README.md index cea86b4a6..ef8c5585e 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ local parser_config = require "nvim-treesitter.parsers".get_parser_configs() parser_config.org = { install_info = { url = 'https://github.com/milisims/tree-sitter-org', - revision = 'f110024d539e676f25b72b7c80b0fd43c34264ef', + revision = 'main', files = {'src/parser.c', 'src/scanner.cc'}, }, filetype = 'org', @@ -105,7 +105,7 @@ local parser_config = require "nvim-treesitter.parsers".get_parser_configs() parser_config.org = { install_info = { url = 'https://github.com/milisims/tree-sitter-org', - revision = 'f110024d539e676f25b72b7c80b0fd43c34264ef', + revision = 'main', files = {'src/parser.c', 'src/scanner.cc'}, }, filetype = 'org', diff --git a/lua/orgmode/colors/todo_highlighter.lua b/lua/orgmode/colors/todo_highlighter.lua index d5fce70f1..64f64a01e 100644 --- a/lua/orgmode/colors/todo_highlighter.lua +++ b/lua/orgmode/colors/todo_highlighter.lua @@ -31,10 +31,10 @@ local function add_todo_keyword_highlights() end, todo_keywords.DONE), ' ' ) - table.insert(lines, string.format([[(item "keyword?" @OrgTODO (#any-of? @OrgTODO %s))]], todo_type)) - table.insert(lines, string.format([[(item "keyword?" @OrgDONE (#any-of? @OrgDONE %s))]], done_type)) + table.insert(lines, string.format([[(item . (expr) @OrgTODO (#any-of? @OrgTODO %s))]], todo_type)) + table.insert(lines, string.format([[(item . (expr) @OrgDONE (#any-of? @OrgDONE %s))]], done_type)) for face_name, face_hl in pairs(faces) do - table.insert(lines, string.format([[(item "keyword?" @%s (#eq? @%s %s))]], face_hl, face_hl, face_name)) + table.insert(lines, string.format([[(item . (expr) @%s (#eq? @%s %s))]], face_hl, face_hl, face_name)) end vim.treesitter.set_query('org', 'highlights', table.concat(lines, '\n')) if vim.bo.filetype == 'org' then diff --git a/lua/orgmode/objects/date.lua b/lua/orgmode/objects/date.lua index e6d808059..3d2126692 100644 --- a/lua/orgmode/objects/date.lua +++ b/lua/orgmode/objects/date.lua @@ -213,6 +213,35 @@ local function from_string(datestr, opts) return parse_date(date, dayname, adjustments, opts) end +--- @param datestr string +--- @return table[] +local function parse_parts(datestr) + local result = {} + local counter = 1 + local patterns = { + { type = 'date', rgx = '^%d%d%d%d%-%d%d%-%d%d$' }, + { type = 'dayname', rgx = '^%a%a%a$' }, + { type = 'time', rgx = '^%d?%d:%d%d$' }, + { type = 'time_range', rgx = '^%d?%d:%d%d%-%d?%d:%d%d$' }, + { type = 'adjustment', rgx = '^[%.%+%-]+%d+[hdwmy]?$' }, + } + for space, item in string.gmatch(datestr, '(%s*)(%S+)') do + local from = counter + space:len() + for _, dt_pattern in ipairs(patterns) do + if item:match(dt_pattern.rgx) then + table.insert(result, { + type = dt_pattern.type, + value = item, + from = from, + to = from + item:len() - 1, + }) + counter = counter + item:len() + space:len() + end + end + end + return result +end + local function from_org_date(datestr, opts) local from_open, from, from_close, delimiter, to_open, to, to_close = datestr:match(pattern .. '(%-%-)' .. pattern) if not delimiter then @@ -858,6 +887,7 @@ local function parse_all_from_line(line, lnum) end return { + parse_parts = parse_parts, from_org_date = from_org_date, from_string = from_string, now = now, diff --git a/lua/orgmode/objects/edit_special/init.lua b/lua/orgmode/objects/edit_special/init.lua index 4e2337735..7e4bc4ee7 100644 --- a/lua/orgmode/objects/edit_special/init.lua +++ b/lua/orgmode/objects/edit_special/init.lua @@ -21,12 +21,7 @@ function EditSpecial:new() end function EditSpecial:_parse_position() - local nearest_block_node_info = utils.get_nearest_block_node( - self.file, - { 'name', 'contents', 'parameters' }, - self.org_pos, - true - ) + local nearest_block_node_info = utils.get_nearest_block_node(self.file, self.org_pos, true) if not nearest_block_node_info then utils.echo_warning('No block node found near cursor') diff --git a/lua/orgmode/org/mappings.lua b/lua/orgmode/org/mappings.lua index 1f5a0e9b3..904d06670 100644 --- a/lua/orgmode/org/mappings.lua +++ b/lua/orgmode/org/mappings.lua @@ -183,38 +183,70 @@ function OrgMappings:_adjust_date_part(direction, amount, fallback) local do_replacement = function(date) local col = vim.fn.col('.') local char = vim.fn.getline('.'):sub(col, col) + local raw_date_value = vim.fn.getline('.'):sub(date.range.start_col + 1, date.range.end_col - 1) if col == date.range.start_col or col == date.range.end_col then date.active = not date.active return self:_replace_date(date) end - local node = Files.get_node_at_cursor() local col_from_start = col - date.range.start_col - local modify_end_time = false + local parts = Date.parse_parts(raw_date_value) local adj = nil - if node:type() == 'date' then - if col_from_start <= 5 then + local modify_end_time = false + local part = nil + for _, p in ipairs(parts) do + if col_from_start >= p.from and col_from_start <= p.to then + part = p + break + end + end + + if not part then + return + end + + local offset = col_from_start - part.from + + if part.type == 'date' then + if offset <= 4 then adj = get_adj('y') - elseif col_from_start <= 8 then + elseif offset <= 7 then adj = get_adj('m') - elseif col_from_start <= 15 then + else adj = get_adj('d') end - elseif node:type() == 'time' then - local has_end_time = node:parent() and node:parent():type() == 'timerange' - if col_from_start <= 17 then + end + + if part.type == 'dayname' then + adj = get_adj('d') + end + + if part.type == 'time' then + if offset <= 2 then + adj = get_adj('h') + else + adj = minute_adj + end + end + + if part.type == 'time_range' then + if offset <= 2 then adj = get_adj('h') - elseif col_from_start <= 20 then + elseif offset <= 5 then adj = minute_adj - elseif has_end_time and col_from_start <= 23 then + elseif offset <= 8 then adj = get_adj('h') modify_end_time = true - elseif has_end_time and col_from_start <= 26 then + else adj = minute_adj modify_end_time = true end - elseif (node:type() == 'repeater' or node:type() == 'delay') and char:match('[hdwmy]') ~= nil then + end + + if part.type == 'adjustment' then local map = { h = 'd', d = 'w', w = 'm', m = 'y', y = 'h' } - vim.cmd(string.format('norm!r%s', map[char])) + if map[char] then + vim.cmd(string.format('norm!r%s', map[char])) + end return true end @@ -397,6 +429,9 @@ function OrgMappings:handle_return(suffix) suffix = suffix or '' local current_file = Files.get_current_file() local item = current_file:get_current_node() + if item.type == 'expr' then + item = current_file:convert_to_file_node(item.node:parent()) + end if item.node:parent() and item.node:parent():type() == 'headline' then item = current_file:convert_to_file_node(item.node:parent()) diff --git a/lua/orgmode/parser/file.lua b/lua/orgmode/parser/file.lua index e53cbde18..520200258 100644 --- a/lua/orgmode/parser/file.lua +++ b/lua/orgmode/parser/file.lua @@ -43,7 +43,8 @@ end function File:_parse() self:_parse_source_code_filetypes() - self:_parse_sections_and_root_directives() + self:_parse_directives() + self:_parse_sections() end function File:get_errors() @@ -335,11 +336,8 @@ function File:get_section(index) end ---@private -function File:_parse_sections_and_root_directives() +function File:_parse_sections() for child in self.tree:root():iter_children() do - if child:type() == 'directive' then - self:_parse_directive(child) - end if child:type() == 'section' then local section = Section.from_node(child, self) table.insert(self.sections, section) @@ -366,11 +364,10 @@ end ---@private function File:_parse_source_code_filetypes() - local blocks = self:get_ts_matches('(block (name) @name (parameters) @parameters (#eq? @name "SRC"))') + local blocks = self:get_ts_matches('(block name: (expr) @name parameter: (expr) @parameters (#eq? @name "SRC"))') local source_code_filetypes = {} for _, item in ipairs(blocks) do - local params = vim.split(item.parameters.text, '%s+') - local ft = params[1] + local ft = item.parameters and item.parameters.text if ft and ft ~= '' @@ -383,18 +380,15 @@ function File:_parse_source_code_filetypes() self.source_code_filetypes = source_code_filetypes end -function File:_parse_directive(node) - local name = node:named_child(0) - local value = node:named_child(1) - if not name or not value then - return - end - - local name_text = self:get_node_text(name) - if name_text:upper() == 'FILETAGS' then - local value_text = self:get_node_text(value) - self.tags = utils.parse_tags_string(value_text) +function File:_parse_directives() + local directives = self:get_ts_matches( + [[(directive name: (expr) @name value: (value) @value (#match? @name "\\cfiletags"))]] + ) + local tags = {} + for _, directive in ipairs(directives) do + utils.concat(tags, utils.parse_tags_string(directive.value.text), true) end + self.tags = tags end return File diff --git a/lua/orgmode/parser/section.lua b/lua/orgmode/parser/section.lua index 9e9e0bcf1..44acfaca8 100644 --- a/lua/orgmode/parser/section.lua +++ b/lua/orgmode/parser/section.lua @@ -88,49 +88,41 @@ function Section.from_node(section_node, file, parent) for child in section_node:iter_children() do if child:type() == 'plan' then - local plan_children = ts_utils.get_named_children(child) - local len = #plan_children - local i = 1 - while i <= len do - local plan_item = plan_children[i] - if not plan_item then - break - end - local next_item = plan_children[i + 1] - - local type = 'NONE' - local node = plan_item - local item_type = plan_item:type():upper() - - if item_type == 'NAME' and next_item and next_item:type():upper() == 'TIMESTAMP' then - local t = file:get_node_text(plan_item):upper() - if t == 'DEADLINE:' or t == 'SCHEDULED:' or t == 'CLOSED:' then - node = next_item - type = t:sub(1, t:len() - 1) - i = i + 1 + for entry in child:iter_children() do + if entry:type() == 'entry' then + local first_node = entry:named_child(0) + local first_node_text = file:get_node_text(first_node) + if entry:named_child_count() == 1 and first_node:type() == 'timestamp' then + utils.concat( + data.dates, + Date.from_org_date(first_node_text, { + range = Range.from_node(first_node), + }) + ) + end + if entry:named_child_count() == 2 and first_node:type() == 'entry_name' then + local valid_plan_types = { 'SCHEDULED', 'DEADLINE', 'CLOSED' } + local type = 'NONE' + if vim.tbl_contains(valid_plan_types, first_node_text:upper()) then + type = first_node_text + end + local timestamp = file:get_node_text(entry:named_child(1)) + utils.concat( + data.dates, + Date.from_org_date(timestamp, { + range = Range.from_node(entry:named_child(1)), + type = type, + }) + ) end end - - local date = file:get_node_text(node) - utils.concat( - data.dates, - Date.from_org_date(date, { - type = type, - range = Range.from_node(node), - }) - ) - i = i + 1 end end if child:type() == 'body' then - local dates = file:get_ts_matches('(timestamp) @timestamp', child) - for _, date in ipairs(dates) do - utils.concat( - data.dates, - Date.from_org_date(date.timestamp.text, { - range = Range.from_node(date.timestamp.node), - }) - ) + local start_line = child:range() + local lines = file:get_node_text_list(child) + for i, line in ipairs(lines) do + utils.concat(data.dates, Date.parse_all_from_line(line, start_line + i)) end local drawers = file:get_ts_matches('(drawer) @drawer', child) for _, drawer_item in ipairs(drawers) do @@ -166,13 +158,16 @@ function Section.from_node(section_node, file, parent) data.title = file:get_node_text(headline_node) data.todo_keyword_node = headline_node:child(0) end - if headline_node:type() == 'tag' then - local tag = file:get_node_text(headline_node) - if not vim.tbl_contains(data.tags, tag) then - table.insert(data.tags, tag) - end - if not vim.tbl_contains(data.own_tags, tag) then - table.insert(data.own_tags, tag) + if headline_node:type() == 'tag_list' then + local tags = ts_utils.get_named_children(headline_node) + for _, tag_node in ipairs(tags) do + local tag = file:get_node_text(tag_node) + if not vim.tbl_contains(data.tags, tag) then + table.insert(data.tags, tag) + end + if not vim.tbl_contains(data.own_tags, tag) then + table.insert(data.own_tags, tag) + end end end end diff --git a/lua/orgmode/utils/init.lua b/lua/orgmode/utils/init.lua index 0a6b8e3fe..a9d97ca6a 100644 --- a/lua/orgmode/utils/init.lua +++ b/lua/orgmode/utils/init.lua @@ -470,12 +470,10 @@ function utils.get_named_children_nodes(file, parent_node, children_names) end ---@param file File ----@param block_type string ----@param child_names string[] ---@param cursor string[] ---@param accept_at_cursor boolean ---@return nil|table -function utils.get_nearest_block_node(file, child_names, cursor, accept_at_cursor) +function utils.get_nearest_block_node(file, cursor, accept_at_cursor) local current_node = file:get_node_at_cursor(cursor) local block_node = utils.get_closest_parent_of_type(current_node, 'block', accept_at_cursor) if not block_node then @@ -483,8 +481,11 @@ function utils.get_nearest_block_node(file, child_names, cursor, accept_at_curso end -- Block might not have contents yet, which is fine - local children_nodes = utils.get_named_children_nodes(file, block_node, child_names) - if not next(children_nodes) or not children_nodes.name or not children_nodes.parameters then + local children_nodes = file:get_ts_matches( + '(block name: (expr) @name parameter: (expr) @parameters contents: (contents)? @contents)', + block_node + )[1] + if not children_nodes or not children_nodes.name or not children_nodes.parameters then return end @@ -494,24 +495,4 @@ function utils.get_nearest_block_node(file, child_names, cursor, accept_at_curso } end ----@param file File ----@param block_type string ----@param child_names string[] ----@param cursor string[] ----@param accept_at_cursor boolean ----@return nil|table -function utils.get_nearest_block_node_with_name(file, block_type, child_names, cursor, accept_at_cursor) - local block_info = utils.get_nearest_block_node(file, child_names, cursor, accept_at_cursor) - - if not block_info then - return - end - - if block_info.children.name.text ~= block_type then - return - end - - return block_info -end - return utils diff --git a/queries/org/highlights.scm b/queries/org/highlights.scm index bd9b3427d..773480fa4 100644 --- a/queries/org/highlights.scm +++ b/queries/org/highlights.scm @@ -1,5 +1,5 @@ -((timestamp) @timestamp (#match? @timestamp "^\\<.*$")) @OrgTSTimestampActive -((timestamp) @timestamp_inactive (#match? @timestamp_inactive "^\\[.*$")) @OrgTSTimestampInactive +(timestamp "<") @OrgTSTimestampActive +(timestamp "[") @OrgTSTimestampInactive (headline (stars) @stars (#eq? @stars "*")) @OrgTSHeadlineLevel1 (headline (stars) @stars (#eq? @stars "**")) @OrgTSHeadlineLevel2 (headline (stars) @stars (#eq? @stars "***")) @OrgTSHeadlineLevel3 @@ -8,17 +8,15 @@ (headline (stars) @stars (#eq? @stars "******")) @OrgTSHeadlineLevel6 (headline (stars) @stars (#eq? @stars "*******")) @OrgTSHeadlineLevel7 (headline (stars) @stars (#eq? @stars "********")) @OrgTSHeadlineLevel8 -(subscript) @OrgTSSubscript -(superscript) @OrgTSSuperscript -(bullet) @OrgTSBullet -(checkbox) @OrgTSCheckbox -((checkbox) @check (#eq? @check "\[-\]")) @OrgTSCheckboxHalfChecked -((checkbox) @check (#eq? @check "\[ \]")) @OrgTSCheckboxUnchecked -((checkbox) @check (#match? @check "\[[xX]\]")) @OrgTSCheckboxChecked -(property_drawer) @OrgTSPropertyDrawer -(drawer) @OrgTSDrawer -(tag) @OrgTSTag -(plan) @OrgTSPlan -(comment) @OrgTSComment -(directive) @OrgTSDirective + (bullet) @OrgTSBullet + (checkbox) @OrgTSCheckbox + ((checkbox) @check (#eq? @check "\[-\]")) @OrgTSCheckboxHalfChecked + ((checkbox) @check (#eq? @check "\[ \]")) @OrgTSCheckboxUnchecked + ((checkbox) @check (#match? @check "\[[xX]\]")) @OrgTSCheckboxChecked + (property_drawer) @OrgTSPropertyDrawer + (drawer) @OrgTSDrawer + (tag) @OrgTSTag + (plan) @OrgTSPlan + (comment) @OrgTSComment + (directive) @OrgTSDirective (ERROR) @LspDiagnosticsUnderlineError diff --git a/queries/org/injections.scm b/queries/org/injections.scm index b75080d77..b46b70ade 100644 --- a/queries/org/injections.scm +++ b/queries/org/injections.scm @@ -1 +1 @@ -(block (parameters) @language (contents) @content) + (block parameter: (expr) @language (contents) @content) From 2a0dfed29a152928fa68553988f612e0a587a7ee Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sun, 6 Feb 2022 10:46:52 +0100 Subject: [PATCH 2/3] Move custom ts grammer configuration to setup function. --- .github/workflows/tests.yml | 1 + DOCS.md | 3 + README.md | 25 ++--- doc/orgmode.txt | 3 + lua/orgmode/init.lua | 38 +++++++- lua/orgmode/parser/file.lua | 8 +- lua/orgmode/utils/promise.lua | 166 ++++++++++++++++++++++++---------- tests/minimal_init.vim | 12 +-- 8 files changed, 174 insertions(+), 82 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0facc583b..cd9cf8da1 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,6 +17,7 @@ jobs: - v0.5.0 - v0.5.1 - v0.6.0 + - v0.6.1 - nightly runs-on: ubuntu-latest steps: diff --git a/DOCS.md b/DOCS.md index 53e71baf6..c4221c7de 100644 --- a/DOCS.md +++ b/DOCS.md @@ -86,6 +86,8 @@ Available options: * underline - `:underline on` * italic - `:slant italic` +--- + Full configuration example with additional todo keywords and their colors: ```lua require('orgmode').setup({ @@ -1183,6 +1185,7 @@ set statusline=%{v:lua.orgmode.statusline()} ``` ## Changelog +To track breaking changes, subscribe to [Notice of breaking changes](https://github.com/nvim-orgmode/orgmode/issues/217) issue where those are announced. #### 24 October 2021 * Help mapping was changed from `?` to `g?` to avoid conflict with built in backward search. See issue [#106](https://github.com/nvim-orgmode/orgmode/issues/106). diff --git a/README.md b/README.md index ef8c5585e..6f4a41c36 100644 --- a/README.md +++ b/README.md @@ -71,16 +71,10 @@ call dein#add('nvim-orgmode/orgmode') ```lua -- init.lua -local parser_config = require "nvim-treesitter.parsers".get_parser_configs() -parser_config.org = { - install_info = { - url = 'https://github.com/milisims/tree-sitter-org', - revision = 'main', - files = {'src/parser.c', 'src/scanner.cc'}, - }, - filetype = 'org', -} +-- Load custom tree-sitter grammar for org filetype +require('orgmode').setup_ts_grammar() +-- Tree-sitter configuration require'nvim-treesitter.configs'.setup { -- If TS highlights are not enabled at all, or disabled via `disable` prop, highlighting will fallback to default Vim syntax highlighting highlight = { @@ -101,16 +95,11 @@ Or if you are using `init.vim`: ```vim " init.vim lua << EOF -local parser_config = require "nvim-treesitter.parsers".get_parser_configs() -parser_config.org = { - install_info = { - url = 'https://github.com/milisims/tree-sitter-org', - revision = 'main', - files = {'src/parser.c', 'src/scanner.cc'}, - }, - filetype = 'org', -} +-- Load custom tree-sitter grammar for org filetype +require('orgmode').setup_ts_grammar() + +-- Tree-sitter configuration require'nvim-treesitter.configs'.setup { -- If TS highlights are not enabled at all, or disabled via `disable` prop, highlighting will fallback to default Vim syntax highlighting highlight = { diff --git a/doc/orgmode.txt b/doc/orgmode.txt index 161906252..444866caa 100644 --- a/doc/orgmode.txt +++ b/doc/orgmode.txt @@ -261,6 +261,7 @@ Available options: * underline - `:underline on` * italic - `:slant italic` +-------------------------------------------------------------------------------- Full configuration example with additional todo keywords and their colors: > require('orgmode').setup({ @@ -1572,6 +1573,8 @@ Show the currently clocked in headline (if any), with total clocked time / effor -------------------------------------------------------------------------------- CHANGELOG *orgmode-changelog* +To track breaking changes, subscribe to Notice of breaking changes (https://github.com/nvim-orgmode/orgmode/issues/217) issue where those are announced. + 24 OCTOBER 2021 *orgmode-24_october_2021* * Help mapping was changed from `?` to `g?` to avoid conflict with built in backward search. See issue #106 (https://github.com/nvim-orgmode/orgmode/issues/106). diff --git a/lua/orgmode/init.lua b/lua/orgmode/init.lua index dd397e7ca..f72ec3070 100644 --- a/lua/orgmode/init.lua +++ b/lua/orgmode/init.lua @@ -1,4 +1,7 @@ -_G.orgmode = _G.orgmode or {} +_G.orgmode = _G.orgmode or { + ts_revision = '1c3eb533a9cf6800067357b59e03ac3f91fc3a54', +} +local setup_ts_grammar_used = false local instance = nil ---@class Org @@ -58,10 +61,42 @@ function Org:setup_autocmds() vim.cmd([[augroup END]]) end +--- @param revision string? +local function setup_ts_grammar(revision) + setup_ts_grammar_used = true + local parser_config = require('nvim-treesitter.parsers').get_parser_configs() + parser_config.org = { + install_info = { + url = 'https://github.com/milisims/tree-sitter-org', + revision = revision or _G.orgmode.ts_revision, + files = { 'src/parser.c', 'src/scanner.cc' }, + }, + filetype = 'org', + } +end + +local function check_ts_grammar() + if setup_ts_grammar_used then + return + end + vim.defer_fn(function() + local parser_config = require('nvim-treesitter.parsers').get_parser_configs() + if parser_config and parser_config.org and parser_config.org.install_info.revision ~= _G.orgmode.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 + end, 200) +end + ---@param opts? table ---@return Org local function setup(opts) + opts = opts or {} instance = Org:new() + check_ts_grammar() local config = require('orgmode.config'):extend(opts) vim.defer_fn(function() if config.notifications.enabled and #vim.api.nvim_list_uis() > 0 then @@ -140,6 +175,7 @@ function _G.orgmode.statusline() end return { + setup_ts_grammar = setup_ts_grammar, setup = setup, reload = reload, action = action, diff --git a/lua/orgmode/parser/file.lua b/lua/orgmode/parser/file.lua index 520200258..0faca31cf 100644 --- a/lua/orgmode/parser/file.lua +++ b/lua/orgmode/parser/file.lua @@ -381,12 +381,12 @@ function File:_parse_source_code_filetypes() end function File:_parse_directives() - local directives = self:get_ts_matches( - [[(directive name: (expr) @name value: (value) @value (#match? @name "\\cfiletags"))]] - ) + local directives = self:get_ts_matches([[(directive name: (expr) @name value: (value) @value)]]) local tags = {} for _, directive in ipairs(directives) do - utils.concat(tags, utils.parse_tags_string(directive.value.text), true) + if directive.name.text:lower() == 'filetags' then + utils.concat(tags, utils.parse_tags_string(directive.value.text), true) + end end self.tags = tags end diff --git a/lua/orgmode/utils/promise.lua b/lua/orgmode/utils/promise.lua index 9c55ad9c0..6103032d7 100644 --- a/lua/orgmode/utils/promise.lua +++ b/lua/orgmode/utils/promise.lua @@ -1,5 +1,7 @@ -- Taken from https://github.com/notomo/promise.nvim +local vim = vim + local PackedValue = {} PackedValue.__index = PackedValue @@ -35,22 +37,17 @@ function PackedValue.first(self) return first end -local vim = vim - ----@class Promise local Promise = {} Promise.__index = Promise -local PromiseStatus = { Pending = 'Pending', Fulfilled = 'Fulfilled', Rejected = 'Rejected' } +local PromiseStatus = { Pending = 'pending', Fulfilled = 'fulfilled', Rejected = 'rejected' } local is_promise = function(v) return getmetatable(v) == Promise end -local new_any_userdata = function() - local userdata = vim.loop.new_async(function() end) - userdata:close() - return userdata +local new_empty_userdata = function() + return newproxy(true) end local new_pending = function(on_fullfilled, on_rejected) @@ -65,27 +62,30 @@ local new_pending = function(on_fullfilled, on_rejected) _on_fullfilled = on_fullfilled, _on_rejected = on_rejected, _handled = false, - _unhandled_detector = new_any_userdata(), } local self = setmetatable(tbl, Promise) - getmetatable(tbl._unhandled_detector).__gc = function() + local userdata = new_empty_userdata() + self._unhandled_detector = setmetatable({ [self] = userdata }, { __mode = 'k' }) + getmetatable(userdata).__gc = function() if self._status ~= PromiseStatus.Rejected or self._handled then return end self._handled = true vim.schedule(function() - error('unhandled promise rejection: ' .. vim.inspect({ self._value:unpack() })) + local values = vim.inspect({ self._value:unpack() }, { newline = '', indent = '' }) + error('unhandled promise rejection: ' .. values, 0) end) end return self end ---- TODO doc ---- @param f function: -function Promise.new(f) - vim.validate({ f = { f, 'function' } }) +--- Equivalents to JavaScript's Promise.new. +--- @param executor function: `(resolve: function(...), reject: function(...))` +--- @return table: Promise +function Promise.new(executor) + vim.validate({ executor = { executor, 'function' } }) local self = new_pending() @@ -95,17 +95,19 @@ function Promise.new(f) local reject = function(...) self:_reject(...) end - f(resolve, reject) + executor(resolve, reject) return self end ---- TODO doc ---- @vararg any: +--- Returns a fulfilled promise. +--- But if the first argument is promise, returns the promise. +--- @vararg any: one promise or non-promises +--- @return table: Promise function Promise.resolve(...) - local v = ... - if is_promise(v) then - return v + local first = ... + if is_promise(first) then + return first end local value = PackedValue.new(...) return Promise.new(function(resolve, _) @@ -113,12 +115,14 @@ function Promise.resolve(...) end) end ---- TODO doc ---- @vararg any: +--- Returns a rejected promise. +--- But if the first argument is promise, returns the promise. +--- @vararg any: one promise or non-promises +--- @return table: Promise function Promise.reject(...) - local v = ... - if is_promise(v) then - return v + local first = ... + if is_promise(first) then + return first end local value = PackedValue.new(...) return Promise.new(function(_, reject) @@ -166,7 +170,7 @@ function Promise._start_resolve(self, value) end function Promise._reject(self, ...) - if self._status == PromiseStatus.Resolved then + if self._status == PromiseStatus.Fulfilled then return end self._status = PromiseStatus.Rejected @@ -205,15 +209,17 @@ function Promise._start_reject(self, value) end) end ---- TODO doc ---- @param on_fullfilled function|nil: ---- @param on_rejected function|nil: +--- Equivalents to JavaScript's Promise.then. +--- @param on_fullfilled function|nil: A callback on fullfilled. +--- @param on_rejected function|nil: A callback on rejected. +--- @return table: Promise function Promise.next(self, on_fullfilled, on_rejected) vim.validate({ on_fullfilled = { on_fullfilled, 'function', true }, on_rejected = { on_rejected, 'function', true }, }) local promise = new_pending(on_fullfilled, on_rejected) + table.insert(self._queued, promise) vim.schedule(function() if self._status == PromiseStatus.Fulfilled then return self:_resolve(self._value:unpack()) @@ -222,18 +228,19 @@ function Promise.next(self, on_fullfilled, on_rejected) return self:_reject(self._value:unpack()) end end) - table.insert(self._queued, promise) return promise end ---- TODO doc ---- @param on_rejected function|nil: +--- Equivalents to JavaScript's Promise.catch. +--- @param on_rejected function|nil: A callback on rejected. +--- @return table: Promise function Promise.catch(self, on_rejected) return self:next(nil, on_rejected) end ---- TODO doc ---- @param on_finally function: +--- Equivalents to JavaScript's Promise.finally. +--- @param on_finally function +--- @return table: Promise function Promise.finally(self, on_finally) vim.validate({ on_finally = { on_finally, 'function', true } }) return self @@ -243,29 +250,28 @@ function Promise.finally(self, on_finally) end) :catch(function(...) on_finally() - local value = PackedValue.new(...) - return Promise.new(function(_, reject) - reject(value:unpack()) - end) + return Promise.reject(...) end) end ---- TODO doc ---- @param list table: +--- Equivalents to JavaScript's Promise.all. +--- Even if multiple value are resolved, results include only the first value. +--- @param list table: promise or non-promise values +--- @return table: Promise function Promise.all(list) vim.validate({ list = { list, 'table' } }) - local remain = #list - local results = {} return Promise.new(function(resolve, reject) + local remain = #list if remain == 0 then - return resolve(results) + return resolve({}) end + local results = {} for i, e in ipairs(list) do Promise.resolve(e) :next(function(...) - -- use only the first argument - results[i] = ... + local first = ... + results[i] = first if remain == 1 then return resolve(results) end @@ -278,8 +284,9 @@ function Promise.all(list) end) end ---- TODO doc ---- @param list table: +--- Equivalents to JavaScript's Promise.race. +--- @param list table: promise or non-promise values +--- @return table: Promise function Promise.race(list) vim.validate({ list = { list, 'table' } }) return Promise.new(function(resolve, reject) @@ -295,4 +302,67 @@ function Promise.race(list) end) end +--- Equivalents to JavaScript's Promise.any. +--- Even if multiple value are rejected, errors include only the first value. +--- @param list table: promise or non-promise values +--- @return table: Promise +function Promise.any(list) + vim.validate({ list = { list, 'table' } }) + return Promise.new(function(resolve, reject) + local remain = #list + if remain == 0 then + return reject({}) + end + + local errs = {} + for i, e in ipairs(list) do + Promise.resolve(e) + :next(function(...) + resolve(...) + end) + :catch(function(...) + local first = ... + errs[i] = first + if remain == 1 then + return reject(errs) + end + remain = remain - 1 + end) + end + end) +end + +--- Equivalents to JavaScript's Promise.allSettled. +--- Even if multiple value are resolved/rejected, value/reason is only the first value. +--- @param list table: promise or non-promise values +--- @return table: Promise +function Promise.all_settled(list) + vim.validate({ list = { list, 'table' } }) + return Promise.new(function(resolve) + local remain = #list + if remain == 0 then + return resolve({}) + end + + local results = {} + for i, e in ipairs(list) do + Promise.resolve(e) + :next(function(...) + local first = ... + results[i] = { status = PromiseStatus.Fulfilled, value = first } + end) + :catch(function(...) + local first = ... + results[i] = { status = PromiseStatus.Rejected, reason = first } + end) + :finally(function() + if remain == 1 then + return resolve(results) + end + remain = remain - 1 + end) + end + end) +end + return Promise diff --git a/tests/minimal_init.vim b/tests/minimal_init.vim index 05988c8a3..bc22b0b42 100644 --- a/tests/minimal_init.vim +++ b/tests/minimal_init.vim @@ -8,19 +8,9 @@ runtime plugin/nvim-treesitter.vim let mapleader = ',' lua << EOF +require('orgmode').setup_ts_grammar() require('nvim-treesitter.configs').setup({}) -local parser_config = require('nvim-treesitter.parsers').get_parser_configs() - -parser_config.org = { - install_info = { - url = 'https://github.com/milisims/tree-sitter-org', - revision = 'f110024d539e676f25b72b7c80b0fd43c34264ef', - files = {'src/parser.c', 'src/scanner.cc'}, - }, - filetype = 'org', -} - require('orgmode').setup({ org_agenda_files = { vim.fn.getcwd() .. '/tests/plenary/fixtures/*' }, org_default_notes_file = vim.fn.getcwd() .. '/tests/plenary/fixtures/refile.org', From d053f86ca8151737e6c2b1d212b914889cad6261 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sat, 12 Feb 2022 14:51:29 +0100 Subject: [PATCH 3/3] Fix parsing archive location. --- lua/orgmode/parser/file.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lua/orgmode/parser/file.lua b/lua/orgmode/parser/file.lua index 0faca31cf..e41c47534 100644 --- a/lua/orgmode/parser/file.lua +++ b/lua/orgmode/parser/file.lua @@ -16,6 +16,7 @@ local utils = require('orgmode.utils') ---@field sections_by_line table ---@field source_code_filetypes string[] ---@field is_archive_file boolean +---@field archive_location string ---@field clocked_headline Section ---@field tags string[] local File = {} @@ -322,9 +323,8 @@ end ---@return string function File:get_archive_file_location() - local matches = self:get_ts_matches('(document (directive (name) @name (value) @value (#eq? @name "ARCHIVE")))') - if #matches > 0 then - return config:parse_archive_location(self.filename, matches[1].value.text) + if self.archive_location then + return self.archive_location end return config:parse_archive_location(self.filename) end @@ -384,9 +384,13 @@ function File:_parse_directives() local directives = self:get_ts_matches([[(directive name: (expr) @name value: (value) @value)]]) local tags = {} for _, directive in ipairs(directives) do - if directive.name.text:lower() == 'filetags' then + local directive_name = directive.name.text:lower() + if directive_name == 'filetags' then utils.concat(tags, utils.parse_tags_string(directive.value.text), true) end + if directive_name == 'archive' then + self.archive_location = config:parse_archive_location(self.filename, directive.value.text) + end end self.tags = tags end