From e32609f9cd3a8cba3f6535a5185fa08f34820f92 Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Wed, 11 Dec 2024 22:01:42 +0100 Subject: [PATCH 1/4] feat: smart snippets using treesitter --- lua/typstar/autosnippets.lua | 3 +- lua/typstar/excalidraw.lua | 2 +- lua/typstar/snippets/letters.lua | 4 +- lua/typstar/snippets/math.lua | 5 +- lua/typstar/snippets/visual.lua | 124 ++++++++++++++++++------------- lua/typstar/utils.lua | 32 ++++++-- 6 files changed, 104 insertions(+), 66 deletions(-) diff --git a/lua/typstar/autosnippets.lua b/lua/typstar/autosnippets.lua index 1790302..ef9b780 100644 --- a/lua/typstar/autosnippets.lua +++ b/lua/typstar/autosnippets.lua @@ -43,13 +43,14 @@ function M.ri(insert_node_id) return luasnip.function_node(function(args) return args[1][1] end, insert_node_id) end -function M.snip(trigger, expand, insert, condition, priority) +function M.snip(trigger, expand, insert, condition, priority, wordTrig) priority = priority or 1000 return luasnip.snippet( { trig = trigger, trigEngine = M.engine, trigEngineOpts = { condition = condition }, + wordTrig = wordTrig, priority = priority, snippetType = 'autosnippet' }, diff --git a/lua/typstar/excalidraw.lua b/lua/typstar/excalidraw.lua index f2202da..f9d6503 100644 --- a/lua/typstar/excalidraw.lua +++ b/lua/typstar/excalidraw.lua @@ -26,7 +26,7 @@ function M.insert_drawing() local filename = os.date(cfg.filename) local path = assets_dir .. '/' .. filename .. '.excalidraw.md' local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted - utils.insert_snippet(string.format(affix, path_inserted)) + utils.insert_text_block(string.format(affix, path_inserted)) launch_obsidian_open(path) end diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 4230039..292ebc0 100644 --- a/lua/typstar/snippets/letters.lua +++ b/lua/typstar/snippets/letters.lua @@ -38,9 +38,9 @@ end local generate_index_snippets = function(letter) for _, index in pairs(common_indices) do table.insert(letter_snippets, - snip(letter .. '(' .. index .. ') ', letter .. '_(<>) ', { cap(1) }, math, 200)) + snip(letter .. '(' .. index .. ') ', letter .. '_<> ', { cap(1) }, math, 200)) table.insert(letter_snippets, - snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200)) + snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_<>$ ', { cap(1) }, markup, 200)) end end diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index b9d5aa8..1af1fc8 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -65,8 +65,9 @@ return { snip('pi', 'pi ', {}, math), snip('in', 'in ', {}, math), snip('(.*)iv', '<>^(-1)', { cap(1) }, math), - snip('(.*)sr', '<>^(2)', { cap(1) }, math), - snip('(.*)rd', '<>^(<>)', { cap(1), i(1, 'n') }, math), + snip('(.*)sr', '<>^2', { cap(1) }, math), + snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math), + snip('(.*)kk', '<>^(<>)', { cap(1), i(1, 'n') }, math), snip('ddx', '(d <>)(d <>)', { i(1, 'f'), i(2, 'x') }, math), snip('it', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math), diff --git a/lua/typstar/snippets/visual.lua b/lua/typstar/snippets/visual.lua index fef8eab..49c7676 100644 --- a/lua/typstar/snippets/visual.lua +++ b/lua/typstar/snippets/visual.lua @@ -1,72 +1,90 @@ +local ts = vim.treesitter local ls = require('luasnip') -local i = ls.insert_node local d = ls.dynamic_node -local f = ls.function_node +local i = ls.insert_node +local s = ls.snippet_node +local t = ls.text_node +local utils = require('typstar.utils') local helper = require('typstar.autosnippets') local math = helper.in_math local snip = helper.snip -local cap = helper.cap -local get_visual = helper.get_visual local snippets = {} -local operations = { -- boolean denotes whether an additional layer of () brackets should be removed - { 'vi', '1/(', ')', true }, - { 'bb', '(', ')', false }, - { 'sq', '[', ']', true }, - { 'abs', 'abs(', ')', false }, - { 'ul', 'underline(', ')', false }, - { 'ol', 'overline(', ')', false }, - { 'ub', 'underbrace(', ')', false }, - { 'ob', 'overbrace(', ')', false }, - { 'ht', 'hat(', ')', false }, - { 'br', 'macron(', ')', false }, - { 'dt', 'dot(', ')', false }, - { 'ci', 'circle(', ')', false }, - { 'td', 'tilde(', ')', false }, - { 'nr', 'norm(', ')', false }, - { 'vv', 'vec(', ')', false }, - { 'rt', 'sqrt(', ')', false }, +local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added + { 'vi', '1/', '', true, false }, + { 'bb', '(', ')', true, false }, -- add round brackets + { 'sq', '[', ']', true, false }, -- add square brackets + { 'bB', '(', ')', false, false }, -- replace with round brackets + { 'sQ', '[', ']', false, false }, -- replace with square brackets + { 'BB', '', '', false, false }, -- remove brackets + { 'ss', '"', '"', false, false }, + { 'abs', 'abs', '', true, true }, + { 'ul', 'underline', '', true, true }, + { 'ol', 'overline', '', true, true }, + { 'ub', 'underbrace', '', true, true }, + { 'ob', 'overbrace', '', true, true }, + { 'ht', 'hat', '', true, true }, + { 'br', 'macron', '', true, true }, + { 'dt', 'dot', '', true, true }, + { 'ci', 'circle', '', true, true }, + { 'td', 'tilde', '', true, true }, + { 'nr', 'norm', '', true, true }, + { 'vv', 'vec', '', true, true }, + { 'rt', 'sqrt', '', true, true }, } -local wrap_brackets = function(args, snippet, val) - local captured = snippet.captures[1] - local bracket_types = { [')'] = '(', [']'] = '[', ['}'] = '{' } - local closing_bracket = captured:sub(-1, -1) - local opening_bracket = bracket_types[closing_bracket] +local ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap') +local ts_wrapnobrackets_query = ts.query.parse('typst', '(group) @wrapnobrackets') - if opening_bracket == nil then - return captured - end - - local n_brackets = 0 - local char - - for i = #captured, 1, -1 do - char = captured:sub(i, i) - if char == closing_bracket then - n_brackets = n_brackets + 1 - elseif char == opening_bracket then - n_brackets = n_brackets - 1 - end - - if n_brackets == 0 then - local remove_additional = val[4] and opening_bracket == '(' - return captured:sub(1, i - 1) .. val[2] - .. captured:sub(i + (remove_additional and 1 or 0), -(remove_additional and 2 or 1)) .. val[3] +local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2, cut_offset) + for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do + if match then + local start_row, start_col, end_row, end_col = utils.treesitter_match_start_end(match) + if end_row == cursor[1] and end_col == cursor[2] then + vim.schedule(function() -- to not interfere with luasnip + local cursor_offset = 0 + local old_len1, new_len1 = utils.insert_text( + bufnr, start_row, start_col, insert1, 0, cut_offset) + if start_row == cursor[1] then + cursor_offset = cursor_offset + (new_len1 - old_len1) + end + local old_len2, new_len2 = utils.insert_text( + bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0) + if end_row == cursor[1] then + cursor_offset = cursor_offset + (new_len2 - old_len2) + end + vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset }) + end) + return true + end end end - return captured + return false +end + +local smart_wrap = function(args, parent, old_state, expand) + local bufnr = vim.api.nvim_get_current_buf() + local cursor = utils.get_cursor_pos() + local root = utils.get_treesitter_root(bufnr) + + if process_ts_query(bufnr, cursor, ts_wrapnobrackets_query, root, expand[2], expand[3], expand[4] and 0 or 1) then + return s(nil, t()) + end + + local expand1 = expand[5] and expand[2] .. '(' or expand[2] + local expand2 = expand[5] and expand[3] .. ')' or expand[3] + if process_ts_query(bufnr, cursor, ts_wrap_query, root, expand1, expand2) then + return s(nil, t()) + end + if #parent.env.LS_SELECT_RAW > 0 then + return s(nil, t(expand1 .. table.concat(parent.env.LS_SELECT_RAW) .. expand2)) + end + return s(nil, { t(expand1), i(1, '1+1'), t(expand2) }) end for _, val in pairs(operations) do - table.insert(snippets, snip(val[1], val[2] .. '<>' .. val[3], { d(1, get_visual) }, math)) - table.insert(snippets, snip('[\\s$]' .. val[1], val[2] .. '<>' .. val[3], { i(1, '1') }, math)) - table.insert(snippets, - snip('([\\w]+)' - .. val[1], val[2] .. '<>' .. val[3], { cap(1) }, math, 900)) - table.insert(snippets, - snip('(.*[\\)|\\]|\\}])' .. val[1], '<>', { f(wrap_brackets, {}, { user_args = { val } }), nil }, math, 1100)) + table.insert(snippets, snip(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1000, false)) end return { diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index e1c8dbe..b6b5f6d 100644 --- a/lua/typstar/utils.lua +++ b/lua/typstar/utils.lua @@ -7,28 +7,46 @@ function M.get_cursor_pos() return { cursor_row, cursor_col } end -function M.insert_snippet(snip) +function M.insert_text(bufnr, row, col, snip, begin_offset, end_offset) + begin_offset = begin_offset or 0 + end_offset = end_offset or 0 + local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, true)[1] + local old_len = #line + line = line:sub(1, col - begin_offset) .. snip .. line:sub(col + 1 + end_offset, #line) + vim.api.nvim_buf_set_lines(bufnr, row, row + 1, false, { line }) + return old_len, #line +end + +function M.insert_text_block(snip) local line_num = M.get_cursor_pos()[1] + 1 local lines = {} for line in snip:gmatch '[^\r\n]+' do table.insert(lines, line) end - vim.api.nvim_buf_set_lines(0, line_num, line_num, false, lines) + vim.api.nvim_buf_set_lines(vim.api.nvim_get_current_buf(), line_num, line_num, false, lines) end function M.run_shell_command(cmd) vim.fn.jobstart(cmd) end +function M.get_treesitter_root(bufnr) + return ts.get_parser(bufnr):parse()[1]:root() +end + +function M.treesitter_match_start_end(match) + local start_row, start_col, _, _ = match[1]:range() + local _, _, end_row, end_col = match[#match]:range() + return start_row, start_col, end_row, end_col +end + function M.cursor_within_treesitter_query(query, match_tolerance, cursor) cursor = cursor or M.get_cursor_pos() local bufnr = vim.api.nvim_get_current_buf() - local root = ts.get_parser(bufnr):parse()[1]:root() - for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do + for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do if match then - local start_row, start_col, _, _ = match[1]:range() - local _, _, end_row, end_col = match[#match]:range() - local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col, + local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match) + local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col, match_tolerance) if matched then return true From 5d29ba3f3eec3da935c7f7f09d76cb33788068aa Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:20:47 +0100 Subject: [PATCH 2/4] perf: better letter and index snippet matching --- lua/typstar/snippets/letters.lua | 118 ++++++++++++++++++++----------- lua/typstar/snippets/math.lua | 5 +- 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/lua/typstar/snippets/letters.lua b/lua/typstar/snippets/letters.lua index 292ebc0..69435fd 100644 --- a/lua/typstar/snippets/letters.lua +++ b/lua/typstar/snippets/letters.lua @@ -1,60 +1,92 @@ +local ls = require('luasnip') +local d = ls.dynamic_node +local s = ls.snippet_node +local t = ls.text_node local helper = require('typstar.autosnippets') local snip = helper.snip local cap = helper.cap local math = helper.in_math local markup = helper.in_markup + local letter_snippets = {} -local greek_letters = { - { 'a', 'alpha' }, { 'A', 'Alpha' }, - { 'b', 'beta' }, { 'B', 'Beta' }, - { 'c', 'chi' }, { 'C', 'Chi' }, - { 'd', 'delta' }, { 'D', 'Delta' }, - { 'e', 'epsilon' }, { 'E', 'Epsilon' }, - { 'g', 'gamma' }, { 'G', 'Gamma' }, - { 'h', 'phi' }, { 'H', 'Phi' }, - { 'i', 'iotta' }, { 'I', 'Iotta' }, - { 'j', 'theta' }, { 'J', 'Theta' }, - { 'k', 'kappa' }, { 'K', 'Kappa' }, - { 'l', 'lambda' }, { 'L', 'Lambda' }, - { 'm', 'mu' }, { 'M', 'Mu' }, - { 'n', 'nu' }, { 'N', 'Nu' }, - { 'o', 'omega' }, { 'O', 'Omega' }, - { 'p', 'pi' }, { 'P', 'Pi' }, - { 'q', 'eta' }, { 'Q', 'Eta' }, - { 'r', 'rho' }, { 'R', 'Rho' }, - { 's', 'sigma' }, { 'S', 'Sigma' }, - { 't', 'tau' }, { 'T', 'Tau' }, - { 'x', 'xi' }, { 'X', 'xi' }, - { 'z', 'zeta' }, { 'Z', 'Zeta' }, +local greek_letters_map = { + ['a'] = 'alpha', + ['b'] = 'beta', + ['c'] = 'chi', + ['d'] = 'delta', + ['e'] = 'epsilon', + ['g'] = 'gamma', + ['h'] = 'phi', + ['i'] = 'iotta', + ['j'] = 'theta', + ['k'] = 'kappa', + ['l'] = 'lambda', + ['m'] = 'mu', + ['n'] = 'nu', + ['o'] = 'omega', + ['p'] = 'pi', + ['q'] = 'eta', + ['r'] = 'rho', + ['s'] = 'sigma', + ['t'] = 'tau', + ['x'] = 'xi', + ['z'] = 'zeta', } -local latin_letters = { 'f', 'u', 'v', 'w', 'y' } -- remaining ones are added dynamically -local common_indices = { '\\d+', 'i', 'j', 'k', 'n' } +local greek_letters = {} +local greek_keys = {} +local common_indices = { '\\d+', '[i-n]' } +local index_conflicts = { 'in', 'pi', 'xi' } +local index_conflicts_set = {} +local trigger_latin = '[A-Za-z0-9]' +local trigger_greek = '' +local trigger_index_pre = '' +local trigger_index_post = '' -for _, letter in ipairs({ unpack(latin_letters) }) do - table.insert(latin_letters, letter:upper()) +local upper_first = function(str) + return str:sub(1, 1):upper() .. str:sub(2, -1) end -local generate_index_snippets = function(letter) - for _, index in pairs(common_indices) do - table.insert(letter_snippets, - snip(letter .. '(' .. index .. ') ', letter .. '_<> ', { cap(1) }, math, 200)) - table.insert(letter_snippets, - snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_<>$ ', { cap(1) }, markup, 200)) +local greek_full = {} +for latin, greek in pairs(greek_letters_map) do + greek_full[latin] = greek + greek_full[latin:upper()] = upper_first(greek) + table.insert(greek_letters, greek) + table.insert(greek_letters, upper_first(greek)) + table.insert(greek_keys, latin) + table.insert(greek_keys, latin:upper()) +end + +for _, conflict in ipairs(index_conflicts) do + index_conflicts_set[conflict] = true +end + +greek_letters_map = greek_full +trigger_greek = table.concat(greek_keys, '|') +trigger_index_pre = trigger_latin .. '|' .. table.concat(greek_letters, '|') +trigger_index_post = table.concat(common_indices, '|') + +local get_greek = function(_, snippet) + return s(nil, t(greek_letters_map[snippet.captures[1]])) +end + +local get_index = function(_, snippet) + local letter, index = snippet.captures[1], snippet.captures[2] + local trigger = letter .. index + if index_conflicts_set[trigger] then + return s(nil, t(trigger)) end + return s(nil, t(letter .. '_' .. index)) end -for _, val in pairs(greek_letters) do - table.insert(letter_snippets, snip(';' .. val[1], val[2], {}, math)) - table.insert(letter_snippets, snip(';' .. val[1], '$' .. val[2] .. '$ ', {}, markup)) - generate_index_snippets(val[2]) - table.insert(latin_letters, val[1]) -end - -for _, letter in pairs(latin_letters) do - generate_index_snippets(letter) - table.insert(letter_snippets, snip(':' .. letter, '$' .. letter .. '$ ', {}, markup)) -end +table.insert(letter_snippets, snip(':(' .. trigger_latin .. ')', '$<>$ ', { cap(1) }, markup)) +table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup)) +table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '<> ', { d(1, get_greek) }, math)) +table.insert(letter_snippets, + snip('\\$(' .. trigger_index_pre .. ')\\$' .. '(' .. trigger_index_post .. ') ', + '$<>$ ', { d(1, get_index) }, markup, 500)) +table.insert(letter_snippets, + snip('(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ') ', '<> ', { d(1, get_index) }, math, 200)) return { unpack(letter_snippets) diff --git a/lua/typstar/snippets/math.lua b/lua/typstar/snippets/math.lua index 1af1fc8..2346864 100644 --- a/lua/typstar/snippets/math.lua +++ b/lua/typstar/snippets/math.lua @@ -19,7 +19,6 @@ return { snip('een', 'exists epsilon>>0 ', {}, math), -- boolean logic - snip('an', 'and ', {}, math), snip('no', 'not ', {}, math), -- relations @@ -52,7 +51,7 @@ return { snip('nn', 'sect ', {}, math), snip('uu', 'union ', {}, math), snip('bnn', 'sect.big ', {}, math, 1100), - snip('buu', 'untion.big ', {}, math, 1100), + snip('buu', 'union.big ', {}, math, 1100), snip('swo', 'without ', {}, math), -- misc @@ -62,8 +61,6 @@ return { snip('iso', 'tilde.equiv ', {}, math), snip('rrn', 'RR^n ', {}, math), snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, math), - snip('pi', 'pi ', {}, math), - snip('in', 'in ', {}, math), snip('(.*)iv', '<>^(-1)', { cap(1) }, math), snip('(.*)sr', '<>^2', { cap(1) }, math), snip('(.*)jj', '<>_(<>)', { cap(1), i(1, 'n') }, math), From 7b58c63b142bc43555e1c1bd4f7df6bab9923060 Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Fri, 13 Dec 2024 22:56:28 +0100 Subject: [PATCH 3/4] refactor: handle obsidian uri & templating in lua --- lua/typstar/config.lua | 10 ++++-- lua/typstar/excalidraw.lua | 34 +++++++++++------- lua/typstar/utils.lua | 14 ++++++++ python/obsidian_open.py | 52 --------------------------- res/obsidian_open_config_example.json | 3 -- 5 files changed, 43 insertions(+), 70 deletions(-) delete mode 100644 python/obsidian_open.py delete mode 100644 res/obsidian_open_config_example.json diff --git a/lua/typstar/config.lua b/lua/typstar/config.lua index 403d7d4..44fd604 100644 --- a/lua/typstar/config.lua +++ b/lua/typstar/config.lua @@ -5,8 +5,10 @@ local default_config = { excalidraw = { assetsDir = 'assets', filename = 'drawing-%Y-%m-%d-%H-%M-%S', + fileExtension = '.excalidraw.md', fileExtensionInserted = '.excalidraw.svg', - obsidianOpenConfig = nil, + uriOpenCommand = 'xdg-open', + templatePath = nil, }, snippets = { enable = true, @@ -22,8 +24,10 @@ local default_config = { function M.merge_config(args) M.config = vim.tbl_deep_extend('force', default_config, args or {}) - M.config.excalidraw.obsidianOpenConfig = M.config.excalidraw.obsidianOpenConfig or - M.config.typstarRoot .. '/res/obsidian_open_config_example.json' + M.config.excalidraw.templatePath = M.config.excalidraw.templatePath or + { + ['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md', + } end M.merge_config(nil) diff --git a/lua/typstar/excalidraw.lua b/lua/typstar/excalidraw.lua index f9d6503..fe0c7cd 100644 --- a/lua/typstar/excalidraw.lua +++ b/lua/typstar/excalidraw.lua @@ -2,32 +2,42 @@ local M = {} local config = require('typstar.config') local utils = require('typstar.utils') - local cfg = config.config.excalidraw local affix = [[ #figure( - image("%s"), + image("%s"), ) ]] -local function launch_obsidian_open(path) +local function launch_obsidian(path) print(string.format('Opening %s in Excalidraw', path)) - utils.run_shell_command('python3 ' .. - config.config.typstarRoot .. '/python/obsidian_open.py ' .. - path .. ' --config ' .. cfg.obsidianOpenConfig) + utils.run_shell_command(string.format('%s "obsidian://open?path=%s"', cfg.uriOpenCommand, utils.urlencode(path))) end - function M.insert_drawing() local assets_dir = vim.fn.expand('%:p:h') .. '/' .. cfg.assetsDir + local filename = os.date(cfg.filename) + local path = assets_dir .. '/' .. filename .. cfg.fileExtension + local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted + if vim.fn.isdirectory(assets_dir) == 0 then vim.fn.mkdir(assets_dir, 'p') end - local filename = os.date(cfg.filename) - local path = assets_dir .. '/' .. filename .. '.excalidraw.md' - local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted + local found_match = false + for pattern, template_path in pairs(cfg.templatePath) do + if string.match(path, pattern) then + found_match = true + utils.run_shell_command(string.format('cat %s > %s', template_path, path)) -- don't copy file metadata + break + end + end + if not found_match then + print('No matching template found for the path: ' .. path) + return + end + utils.insert_text_block(string.format(affix, path_inserted)) - launch_obsidian_open(path) + launch_obsidian(path) end function M.open_drawing() @@ -35,7 +45,7 @@ function M.open_drawing() local path = vim.fn.expand('%:p:h') .. '/' .. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) .. '.excalidraw.md' - launch_obsidian_open(path) + launch_obsidian(path) end return M diff --git a/lua/typstar/utils.lua b/lua/typstar/utils.lua index b6b5f6d..3343e98 100644 --- a/lua/typstar/utils.lua +++ b/lua/typstar/utils.lua @@ -30,6 +30,20 @@ function M.run_shell_command(cmd) vim.fn.jobstart(cmd) end +function M.char_to_hex(c) + return string.format("%%%02X", string.byte(c)) +end + +function M.urlencode(url) + if url == nil then + return '' + end + url = string.gsub(url, '\n', '\r\n') + url = string.gsub(url, '([^%w _%%%-%.~])', M.char_to_hex) + url = string.gsub(url, ' ', '%%20') + return url +end + function M.get_treesitter_root(bufnr) return ts.get_parser(bufnr):parse()[1]:root() end diff --git a/python/obsidian_open.py b/python/obsidian_open.py deleted file mode 100644 index b7b07dd..0000000 --- a/python/obsidian_open.py +++ /dev/null @@ -1,52 +0,0 @@ -import argparse -import json -import os -import re -import urllib.parse - -argument_parser = argparse.ArgumentParser( - formatter_class=argparse.RawDescriptionHelpFormatter, - prog="Obsidian File Opener", - description="Open and create files in Obsidian with template support.", - epilog=""" - \n\n - Template config file format (json): - - { - "filename_regex": "template_path" - } - - example: - { - "\\\\.md$": "~/Templates/default.md" - "\\\\.excalidraw\\\\.md$": "~/Templates/excalidraw.md" - } - """, -) -argument_parser.add_argument("file", help="file to open/create") -argument_parser.add_argument( - "-c", "--config", help="path to json template config file", required=True -) -args = argument_parser.parse_args() -path = os.path.abspath(args.file) -url_params = { - "path": path, -} - -if not os.path.exists(path): - filename = os.path.basename(path) - with open(args.config) as f: - config = json.loads(f.read()) - template_content = "" - - for regex, template in config.items(): - if re.search(regex, filename): - print(f"Template regex is matching: {regex}") - with open(os.path.expanduser(template), encoding="utf-8") as f: - template_content = f.read() - break - with open(path, "w", encoding="utf-8") as f: - f.write(template_content) - -encoded = urllib.parse.urlencode(url_params, quote_via=urllib.parse.quote) -os.system(f"xdg-open obsidian://open?{encoded}") diff --git a/res/obsidian_open_config_example.json b/res/obsidian_open_config_example.json deleted file mode 100644 index 46fa4a3..0000000 --- a/res/obsidian_open_config_example.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "\\.excalidraw\\.md$": "~/typstar/res/excalidraw_template.excalidraw.md" -} From 9ed884fdac4324581adb26c2d21f9b1707ecac7d Mon Sep 17 00:00:00 2001 From: arne314 <73391160+arne314@users.noreply.github.com> Date: Sat, 14 Dec 2024 23:22:12 +0100 Subject: [PATCH 4/4] docs: add README --- README.md | 56 ++++++++++++++++++++++++++++++++++++++++++ lua/typstar/config.lua | 4 +-- 2 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..1d70afc --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Typstar +Neovim plugin for efficient note taking in typst + +## Features +- Powerful autosnippets using [LuaSnip](https://github.com/L3MON4D3/LuaSnip/) and [Tree-sitter](https://tree-sitter.github.io/) (inspired by [fastex.nvim](https://github.com/lentilus/fastex.nvim)) +- Easy insertion of drawings using [Obsidian Excalidraw](https://github.com/zsviczian/obsidian-excalidraw-plugin) +- \[WIP\] Export of [Anki](https://apps.ankiweb.net/) flashcards + +## Usage + +### Snippets +Use `:TypstarToggleSnippets` to toggle all snippets at any time. +Available snippets can mostly be intuitively derived from [here](././lua/typstar/snippets), they include: + +- Alphanumeric characters: `:` → `$$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `) +- Greek letters: `;` → `` in math and `$$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `) +- Common indices (numbers and letters `i-m`): `` → `_` in math and `$$ ` → `$_$ ` in markup (e.g `A314` → `A_314`, `$alpha$n ` → `$alpha_n$ `) +- Wrapping of any mathematical expression (see [operations](./lua/typstar/snippets/visual.lua), works nested, multiline and in visual mode via the [selection key](#installation)): `` → `()` (e.g. `(a^2+b^2)rt` → `sqrt(a^2+b^2)`, `lambdatd` → `tilde(lambda)`, `(1+1)sQ` → `[1+1]`, `(1+1)sq` → `[(1+1)]`) +- Matrices: `ma` and `lma` (e.g. `23ma` → 2x3 matrix) +- [ctheorems shorthands](./lua/typstar/snippets/document.lua) (e.g. `tem` → empty theorem, `exa` → empty example) +- [Many shorthands](./lua/typstar/snippets/math.lua) for mathematical expressions + +Note that you can enable and disable collections of snippets in the [config](#configuration). + +### Excalidraw +Use `:TypstarInsertExcalidraw` to create a new drawing using the configured template, insert a figure displaying it and open it in Obsidian. +To open an inserted drawing in Obsidian, simply run `:TypstarOpenExcalidraw` while your cursor is on a line referencing the drawing. + +### Anki +WIP + +## Installation +Install the plugin in neovim and set the `typstarRoot` config or alternatively clone typstar into `~/typstar`. +```lua +require('typstar').setup({ + typstarRoot = '/path/to/typstar/repo' -- depending on your nvim plugin system +}) +``` + +### Snippets +1. Install [LuaSnip](https://github.com/L3MON4D3/LuaSnip/), set `enable_autosnippets = true` and set a visual mode selection key (e.g. `store_selection_keys = ''`) in the configuration +2. Install [jsregexp](https://github.com/kmarius/jsregexp) as described [here](https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#transformations) (running `:lua require('jsregexp')` in nvim should not result in an error) +3. Install [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) and run `:TSInstall typst` +4. Optional: Setup [ctheorems](https://typst.app/universe/package/ctheorems/) with names like [here](./lua/typstar/snippets/document.lua) + +### Excalidraw +1. Install [Obsidian](https://obsidian.md/) and create a vault in your typst note taking directory +2. Install the [obsidian-excalidraw-plugin](https://github.com/zsviczian/obsidian-excalidraw-plugin) and enable `Auto-export SVG` (in plugin settings at `Embedding Excalidraw into your Notes and Exporting > Export Settings > Auto-export Settings`) +3. Have the `xdg-open` command working or set a different command at `uriOpenCommand` in the [config](#configuration) + +### Anki +WIP + +## Configuration +Configuration options can be intuitively derived from the table [here](./lua/typstar/config.lua). + diff --git a/lua/typstar/config.lua b/lua/typstar/config.lua index 44fd604..3890c5d 100644 --- a/lua/typstar/config.lua +++ b/lua/typstar/config.lua @@ -7,12 +7,12 @@ local default_config = { filename = 'drawing-%Y-%m-%d-%H-%M-%S', fileExtension = '.excalidraw.md', fileExtensionInserted = '.excalidraw.svg', - uriOpenCommand = 'xdg-open', + uriOpenCommand = 'xdg-open', -- set depending on OS templatePath = nil, }, snippets = { enable = true, - modules = { + modules = { -- enable modules from ./snippets 'document', 'letters', 'math',