Skip to content

Commit 87291af

Browse files
Add option to format tables with gq mapping (formatexpr)
1 parent 62a7873 commit 87291af

File tree

8 files changed

+200
-21
lines changed

8 files changed

+200
-21
lines changed

DOCS.md

+31-7
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@
1414
6. [Text objects](#text-objects)
1515
7. [Dot repeat](#dot-repeat)
1616
4. [Document Diagnostics](#document-diagnostics)
17-
5. [Autocompletion](#autocompletion)
18-
6. [Abbreviations](#abbreviations)
19-
7. [Colors](#colors)
20-
8. [Advanced search](#advanced-search)
21-
9. [Notifications (experimental)](#notifications-experimental)
22-
10. [Clocking](#clocking)
23-
11. [Changelog](#changelog)
17+
5. [Tables](#tables)
18+
6. [Autocompletion](#autocompletion)
19+
7. [Abbreviations](#abbreviations)
20+
8. [Colors](#colors)
21+
9. [Advanced search](#advanced-search)
22+
10. [Notifications (experimental)](#notifications-experimental)
23+
11. [Clocking](#clocking)
24+
12. [Changelog](#changelog)
2425

2526
## Getting started with Orgmode
2627
To get a basic idea how Orgmode works, look at this screencast from [@dhruvasagar](https://github.com/dhruvasagar)
@@ -945,6 +946,29 @@ To make all mappings dot repeatable, install [vim-repeat](https://github.com/tpo
945946
Since tree-sitter parser is being used to parse the file, if there are some syntax errors,
946947
it can potentially fail to parse specific parts of document when needed.
947948

949+
## Tables
950+
Tables can be formatted via built in `formatexpr` (see `:help gq`)
951+
952+
For example, having this content:
953+
```
954+
* TODO My headline
955+
DEADLINE: <2022-05-22 Sun>
956+
957+
|Header 1|Header 2
958+
|-
959+
| col 1| col 2|
960+
```
961+
962+
And going to line `4` and pressing `gqgq`, it will format it to this:
963+
```
964+
* TODO My headline
965+
DEADLINE: <2022-05-22 Sun>
966+
967+
| Header 1 | Header 2 |
968+
|----------+----------|
969+
| col 1 | col 2 |
970+
```
971+
948972
## Autocompletion
949973
By default, `omnifunc` is provided in `org` files that autocompletes these types:
950974
* Tags

lua/orgmode/clock/report.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ end
2828
function ClockReport:draw_for_agenda(start_line)
2929
local data = {
3030
{ 'File', 'Headline', 'Time' },
31-
{},
31+
'hr',
3232
{ '', 'ALL Total time', self.total_duration:to_string() },
33-
{},
33+
'hr',
3434
}
3535

3636
for _, file in ipairs(self.files) do

lua/orgmode/org/format.lua

+13-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
local Files = require('orgmode.parser.files')
2+
local Table = require('orgmode.treesitter.table')
23

34
local function format()
4-
local line = vim.fn.line('.')
5-
local ok, item = pcall(Files.get_closest_headline, line)
6-
if not ok or not item then
5+
if vim.tbl_contains({ 'i', 'R', 'ic', 'ix' }, vim.fn.mode()) then
6+
-- `formatexpr` is also called when exceeding `textwidth` in insert mode
7+
-- fall back to internal formatting
78
return 1
89
end
910

10-
if item.logbook and item.logbook.range:is_in_line_range(line) then
11+
local table = Table.format()
12+
13+
if table then
14+
return 0
15+
end
16+
17+
local line = vim.fn.line('.')
18+
local ok, item = pcall(Files.get_closest_headline, line)
19+
if ok and item and item.logbook and item.logbook.range:is_in_line_range(line) then
1120
return item.logbook:recalculate_estimate(line)
1221
end
1322

lua/orgmode/org/mappings.lua

-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ local config = require('orgmode.config')
1010
local constants = require('orgmode.utils.constants')
1111
local ts_utils = require('nvim-treesitter.ts_utils')
1212
local utils = require('orgmode.utils')
13-
local tree_utils = require('orgmode.utils.treesitter')
1413
local ts_org = require('orgmode.treesitter')
15-
local Listitem = require('orgmode.treesitter.listitem')
1614

1715
---@class OrgMappings
1816
---@field capture Capture

lua/orgmode/parser/table/row.lua

+8-4
Original file line numberDiff line numberDiff line change
@@ -61,18 +61,22 @@ function TableRow:to_string()
6161
return self.content
6262
end
6363

64-
---@param row table
64+
---@param row table | string
6565
---@param line number
6666
---@param parent_table Table
6767
---@return TableRow
6868
function TableRow.from_table_item(row, line, parent_table)
6969
local table_row = TableRow:new({
7070
table = parent_table,
7171
line = line,
72-
is_separator = #row == 0,
72+
is_separator = type(row) == 'string' and row == 'hr',
7373
})
74-
for col_nr, cell_data in ipairs(row) do
75-
table_row:add_cell(TableCell.from_row_item(cell_data, col_nr, table_row))
74+
if type(row) == 'string' then
75+
table_row:add_cell(TableCell.from_row_item('', 1, table_row))
76+
else
77+
for col_nr, cell_data in ipairs(row) do
78+
table_row:add_cell(TableCell.from_row_item(cell_data, col_nr, table_row))
79+
end
7680
end
7781
return table_row
7882
end

lua/orgmode/treesitter/table.lua

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
local ts_utils = require('nvim-treesitter.ts_utils')
2+
local Table = require('orgmode.parser.table')
3+
local utils = require('orgmode.utils')
4+
local config = require('orgmode.config')
5+
local query = vim.treesitter.query
6+
7+
---@class TsTable
8+
---@field node userdata
9+
---@field data table[]
10+
local TsTable = {}
11+
12+
function TsTable:new(opts)
13+
local data = {}
14+
data.node = opts.node
15+
setmetatable(data, self)
16+
self.__index = self
17+
data:_parse_data()
18+
return data
19+
end
20+
21+
---@private
22+
--- Parse table data from node
23+
function TsTable:_parse_data()
24+
local rows = {}
25+
for row in self.node:iter_children() do
26+
if row:type() == 'hr' then
27+
table.insert(rows, 'hr')
28+
end
29+
if row:type() == 'row' then
30+
local row_data = {}
31+
for cell in row:iter_children() do
32+
if cell:type() == 'cell' then
33+
local cell_val = ''
34+
local cell_content = cell:field('contents')
35+
if cell_content and #cell_content > 0 then
36+
cell_val = query.get_node_text(cell_content[1], 0)
37+
end
38+
table.insert(row_data, cell_val)
39+
end
40+
end
41+
table.insert(rows, row_data)
42+
end
43+
end
44+
45+
self.data = rows
46+
end
47+
48+
function TsTable:rerender()
49+
local start_row, start_col = self.node:range()
50+
local tbl = Table.from_list(self.data, start_row + 1, start_col + 1)
51+
local first_line = vim.api.nvim_buf_get_lines(0, start_row, start_row + 1, true)
52+
local indent = first_line and first_line[1]:match('^%s*') or ''
53+
indent = config:get_indent(indent:len())
54+
55+
local contents = tbl:draw()
56+
local indented = {}
57+
for _, content in ipairs(contents) do
58+
table.insert(indented, string.format('%s%s', indent, content))
59+
end
60+
local view = vim.fn.winsaveview()
61+
vim.api.nvim_buf_set_lines(0, tbl.range.start_line - 1, tbl.range.end_line - 1, false, indented)
62+
vim.fn.winrestview(view)
63+
end
64+
65+
local function format()
66+
local view = vim.fn.winsaveview()
67+
-- Go to first non blank char
68+
vim.cmd([[norm! _]])
69+
local node = ts_utils.get_node_at_cursor()
70+
vim.fn.winrestview(view)
71+
72+
if not node then
73+
return false
74+
end
75+
print(node:type())
76+
print(node:type())
77+
print(node:type())
78+
local table_node = utils.get_closest_parent_of_type(node, 'table', true)
79+
if not table_node then
80+
return false
81+
end
82+
TsTable:new({ node = table_node }):rerender()
83+
return true
84+
end
85+
86+
return {
87+
format = format,
88+
}

tests/plenary/parser/table_spec.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('Table', function()
66
local data = {
77
{ 'one', 'two', 'three' },
88
{ 'four', 'five', 'six', 'seven' },
9-
{},
9+
'hr',
1010
{ 'eight' },
1111
{ 'nine', 'ten' },
1212
}
@@ -37,7 +37,7 @@ describe('Table', function()
3737

3838
local data_with_long_names = {
3939
{ 'one', 'two', 'three' },
40-
{},
40+
'hr',
4141
{ 'four', 'five', 'six', 'seven longer long' },
4242
{ 'eight', 'iamverylong' },
4343
{ 'nine', 'ten', 'a' },

tests/plenary/ui/table_spec.lua

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
local helpers = require('tests.plenary.ui.helpers')
2+
3+
describe('Tables', function()
4+
after_each(function()
5+
vim.cmd([[silent! %bw!]])
6+
end)
7+
8+
it('should generate basic table structure from pipe or hr line', function()
9+
helpers.load_file_content({
10+
'|',
11+
})
12+
vim.fn.cursor({ 1, 1 })
13+
vim.cmd([[norm gqgq]])
14+
assert.are.same({ '| |' }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
15+
16+
helpers.load_file_content({
17+
'|-',
18+
})
19+
20+
vim.cmd([[norm gqgq]])
21+
assert.are.same({ '|--|' }, vim.api.nvim_buf_get_lines(0, 0, -1, false))
22+
end)
23+
24+
it('should format the table', function()
25+
helpers.load_file_content({
26+
' |head1 | ',
27+
' |- ',
28+
' |content| ',
29+
})
30+
vim.fn.cursor({ 2, 1 })
31+
vim.cmd([[norm! gqgq]])
32+
33+
assert.are.same({
34+
' | head1 |',
35+
' |---------|',
36+
' | content |',
37+
}, vim.api.nvim_buf_get_lines(0, 0, -1, false))
38+
end)
39+
40+
it('should format multi column table', function()
41+
helpers.load_file_content({
42+
'|first|second|',
43+
'|-',
44+
'|third cell| fourth cell|',
45+
'|fifth|sixth| seventh',
46+
})
47+
vim.fn.cursor({ 1, 1 })
48+
vim.cmd([[norm gqgq]])
49+
assert.are.same({
50+
'| first | second | |',
51+
'|------------+-------------+---------|',
52+
'| third cell | fourth cell | |',
53+
'| fifth | sixth | seventh |',
54+
}, vim.api.nvim_buf_get_lines(0, 0, -1, false))
55+
end)
56+
end)

0 commit comments

Comments
 (0)