Skip to content
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

Add Virtual Indent #627

Merged
merged 1 commit into from
Jan 25, 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
34 changes: 29 additions & 5 deletions DOCS.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,31 @@ Possible values:
* between `\[` and `\]` delimiters - example: `\[ a=-\sqrt{2} \]`
* between `\(` and `\)` delimiters - example: `\( b=2 \)`

#### **org_indent_mode**
*type*: `string`<br />
*default value*: `indent`<br />
#### **org_startup_indented**

*type*: `boolean`<br />
*default value*: `false`<br />
Possible values:
* `indent` - Use default indentation that follows headlines/checkboxes/previous line indent
* `noindent` - Disable indentation. All lines start from 1st column
* `true` - Uses *Virtual* indents to align content visually. The indents are only visual, they are not saved to the file.
* `false` - Do not add any *Virtual* indentation.

This feature has no effect when enabled on Neovim versions < 0.10.0

#### **org_adapt_indentation**

*type*: `boolean`<br />
*default value*: `true`<br />
PriceHiller marked this conversation as resolved.
Show resolved Hide resolved
Possible values:
* `true` - Use *hard* indents for content under headlines. Files will save with indents relative to headlines.
* `false` - Do not add any *hard* indents. Files will save without indentation relative to headlines.

#### **org_indent_mode_turns_off_org_adapt_indentation**

*type*: `boolean`<br />
*default value*: `true`<br />
Possible values:
* `true` - Disable [`org_adapt_indentation`](#org_adapt_indentation) by default when [`org_startup_indented`](#org_startup_indented) is enabled.
* `false` - Do not disable [`org_adapt_indentation`](#org_adapt_indentation) by default when [`org_startup_indented`](#org_startup_indented) is enabled.

#### **org_src_window_setup**
*type*: `string|function`<br />
Expand Down Expand Up @@ -1550,6 +1569,11 @@ 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.

#### 21 January 2024

* Option `org_indent_mode` was deprecated in favor of [org_startup_indented](#org_startup_indented). To remove the
warning use `org_startup_indented`. This was introduced to support Virtual Indent more in line with Emacs.

#### 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).

Expand Down
5 changes: 5 additions & 0 deletions ftplugin/org.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ config:setup_mappings('org')
config:setup_mappings('text_objects')
config:setup_foldlevel()

if config.org_startup_indented then
vim.b.org_indent_mode = true
end
require("orgmode.org.indent").setup()

vim.bo.modeline = false
vim.opt_local.fillchars:append('fold: ')
vim.opt_local.foldmethod = 'expr'
Expand Down
4 changes: 3 additions & 1 deletion lua/orgmode/config/defaults.lua
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ local DefaultConfig = {
org_log_into_drawer = nil,
org_highlight_latex_and_related = nil,
org_custom_exports = {},
org_indent_mode = 'indent',
org_adapt_indentation = true,
org_startup_indented = false,
org_indent_mode_turns_off_org_adapt_indentation = true,
org_time_stamp_rounding_minutes = 5,
org_blank_before_new_entry = {
heading = true,
Expand Down
13 changes: 12 additions & 1 deletion lua/orgmode/config/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ function Config:extend(opts)
opts.org_priority_default = self.opts.org_priority_default
end
self.opts = vim.tbl_deep_extend('force', self.opts, opts)
if self.org_startup_indented then
self.org_adapt_indentation = not self.org_indent_mode_turns_off_org_adapt_indentation
end
return self
end

Expand Down Expand Up @@ -143,6 +146,14 @@ function Config:_deprecation_notify(opts)
end
end

if opts.org_indent_mode and type(opts.org_indent_mode) == 'string' then
table.insert(
messages,
'"org_indent_mode" is deprecated in favor of "org_startup_indented". Check the documentation about the new option.'
)
opts.org_startup_indented = (opts.org_indent_mode == 'indent')
end

if #messages > 0 then
-- Schedule so it gets printed out once whole init.vim is loaded
vim.schedule(function()
Expand Down Expand Up @@ -406,7 +417,7 @@ end
---@param amount number
---@return string
function Config:get_indent(amount)
if self.opts.org_indent_mode == 'indent' then
if self.org_adapt_indentation then
return string.rep(' ', amount)
end
return ''
Expand Down
16 changes: 12 additions & 4 deletions lua/orgmode/org/indent.lua
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
local config = require('orgmode.config')
local headline_lib = require('orgmode.treesitter.headline')
local VirtualIndent = require('orgmode.ui.virtual_indent')
local ts_utils = require('nvim-treesitter.ts_utils')
local query = nil

local function get_indent_pad(linenr)
local indent_mode = config.org_indent_mode == 'indent'
if indent_mode then
local headline = headline_lib.from_cursor({ linenr, 0 })
if config.org_adapt_indentation then
local headline = require('orgmode.treesitter.headline').from_cursor({ linenr, 0 })
if not headline then
return 0
end
Expand Down Expand Up @@ -309,7 +308,16 @@ local function foldtext()
return line .. config.org_ellipsis
end

local function setup()
local v = vim.version()

if config.org_startup_indented and not vim.version.lt({ v.major, v.minor, v.patch }, { 0, 10, 0 }) then
VirtualIndent:new():attach()
end
end

return {
setup = setup,
foldexpr = foldexpr,
indentexpr = indentexpr,
foldtext = foldtext,
Expand Down
4 changes: 2 additions & 2 deletions lua/orgmode/parser/section.lua
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ end
function Section:demote(amount, demote_child_sections, dryRun)
amount = amount or 1
demote_child_sections = demote_child_sections or false
local should_indent = config.org_indent_mode == 'indent'
local should_indent = config.org_adapt_indentation
local lines = {}
local headline_line = string.rep('*', amount) .. self.line
table.insert(lines, headline_line)
Expand Down Expand Up @@ -444,7 +444,7 @@ end
function Section:promote(amount, promote_child_sections, dryRun)
amount = amount or 1
promote_child_sections = promote_child_sections or false
local should_dedent = config.org_indent_mode == 'indent'
local should_dedent = config.org_adapt_indentation
local lines = {}
if self.level == 1 then
utils.echo_warning('Cannot demote top level heading.')
Expand Down
29 changes: 22 additions & 7 deletions lua/orgmode/treesitter/headline.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local Date = require('orgmode.objects.date')
local Range = require('orgmode.parser.range')
local config = require('orgmode.config')
local ts = vim.treesitter
local indent = require('orgmode.org.indent')

---@class Headline
---@field headline userdata
Expand Down Expand Up @@ -62,12 +63,18 @@ function Headline:promote(amount, recursive)
return utils.echo_warning('Cannot demote top level heading.')
end

return self:_handle_promote_demote(recursive, function(lines)
return self:_handle_promote_demote(recursive, function(start_line, lines)
for i, line in ipairs(lines) do
if line:sub(1, 1) == '*' then
lines[i] = line:sub(1 + amount)
elseif vim.trim(line:sub(1, amount)) == '' then
lines[i] = line:sub(1 + amount)
if config.org_adapt_indentation then
lines[i] = line:sub(1 + amount)
else
line, _ = line:gsub('^%s+', '')
local indent_amount = indent.indentexpr(start_line + i)
lines[i] = string.rep(' ', indent_amount) .. line
end
end
end
return lines
Expand All @@ -80,12 +87,18 @@ function Headline:demote(amount, recursive)
amount = amount or 1
recursive = recursive or false

return self:_handle_promote_demote(recursive, function(lines)
return self:_handle_promote_demote(recursive, function(start_line, lines)
for i, line in ipairs(lines) do
if line:sub(1, 1) == '*' then
lines[i] = string.rep('*', amount) .. line
else
lines[i] = config:apply_indent(line, amount)
if config.org_adapt_indentation then
lines[i] = config:apply_indent(line, amount)
else
line, _ = line:gsub('^%s+', '')
local indent_amount = indent.indentexpr(start_line + i)
lines[i] = string.rep(' ', indent_amount) .. line
end
end
end
return lines
Expand All @@ -94,8 +107,10 @@ end

function Headline:_handle_promote_demote(recursive, modifier)
local whole_subtree = function()
local text = ts.get_node_text(self.headline:parent(), 0)
local lines = modifier(vim.split(text, '\n', true))
local parent = self.headline:parent()
local text = ts.get_node_text(parent, 0)
local start, _, _ = parent:start()
local lines = modifier(start, vim.split(text, '\n', true))
tree_utils.set_node_lines(self.headline:parent(), lines)
return self:refresh()
end
Expand All @@ -118,7 +133,7 @@ function Headline:_handle_promote_demote(recursive, modifier)

local start = self.headline:start()
local end_line = first_child_section:start()
local lines = modifier(vim.api.nvim_buf_get_lines(0, start, end_line, false))
local lines = modifier(start, vim.api.nvim_buf_get_lines(0, start, end_line, false))
vim.api.nvim_buf_set_lines(0, start, end_line, false, lines)
return self:refresh()
end
Expand Down
93 changes: 93 additions & 0 deletions lua/orgmode/ui/virtual_indent.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---@class VirtualIndent
---@field private _ns_id number extmarks namespace id
local VirtualIndent = {
enabled = false,
lib = {},
}

function VirtualIndent:new()
if self.enabled then
return self
end
self._ns_id = vim.api.nvim_create_namespace('orgmode.ui.indent')
self.lib.headline = require('orgmode.treesitter.headline')
self.enabled = true
return self
end

function VirtualIndent:_delete_old_extmarks(buffer, start_line, end_line)
local old_extmarks = vim.api.nvim_buf_get_extmarks(
buffer,
self._ns_id,
{ start_line, 0 },
{ end_line, 0 },
{ type = 'virt_text' }
)
for _, ext in ipairs(old_extmarks) do
vim.api.nvim_buf_del_extmark(buffer, self._ns_id, ext[1])
end
end

function VirtualIndent:_get_indent_size(line)
local headline = self.lib.headline.from_cursor({ line + 1, 1 })

if headline then
local headline_line, _, _ = headline.headline:start()

if headline_line ~= line then
return headline:level() + 1
end
end

return 0
end

---@param bufnr number buffer id
---@param start_line number start line number to set the indentation, 0-based inclusive
---@param end_line number end line number to set the indentation, 0-based inclusive
---@param ignore_ts? boolean whether or not to skip the treesitter start & end lookup
function VirtualIndent:set_indent(bufnr, start_line, end_line, ignore_ts)
ignore_ts = ignore_ts or false
local headline = self.lib.headline.from_cursor({ start_line + 1, 1 })
if headline and not ignore_ts then
local parent = headline.headline:parent()
start_line = parent:start()
end_line = parent:end_()
end
if start_line > 0 then
start_line = start_line - 1
end
self:_delete_old_extmarks(bufnr, start_line, end_line)
for line = start_line, end_line do
local indent = self:_get_indent_size(line)

if indent > 0 then
-- NOTE: `ephemeral = true` is not implemented for `inline` virt_text_pos :(
vim.api.nvim_buf_set_extmark(bufnr, self._ns_id, line, 0, {
virt_text = { { string.rep(' ', indent), 'OrgIndent' } },
virt_text_pos = 'inline',
right_gravity = false,
})
end
end
end

---@param bufnr? number buffer id
function VirtualIndent:attach(bufnr)
bufnr = bufnr or 0
self:set_indent(0, 0, vim.api.nvim_buf_line_count(bufnr) - 1, true)

vim.api.nvim_buf_attach(bufnr, false, {
on_lines = function(_, _, _, start_line, _, end_line)
-- HACK: By calling `set_indent` twice, once synchronously and once in `vim.schedule` we get smooth usage of the
-- virtual indent in most cases and still properly handle undo redo. Unfortunately this is called *early* when
-- `undo` or `redo` is used causing the padding to be incorrect for some headlines.
self:set_indent(bufnr, start_line, end_line)
vim.schedule(function()
self:set_indent(bufnr, start_line, end_line)
end)
end,
})
end

return VirtualIndent
6 changes: 5 additions & 1 deletion lua/orgmode/utils/treesitter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ end
-- returns the nearest headline
function M.closest_headline(cursor)
vim.treesitter.get_parser(0, 'org', {}):parse()
return M.find_headline(M.get_node_at_cursor(cursor))
local node = M.get_node_at_cursor(cursor)
if not node then
return nil
end
return M.find_headline(node)
end

function M.find_parent_type(node, type)
Expand Down
Loading
Loading