mirror of
https://github.com/Ascyii/typstar.git
synced 2026-01-01 13:34:24 -05:00
56
README.md
Normal file
56
README.md
Normal file
@@ -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: `:<char>` → `$<char>$ ` in markup (e.g. `:X` → `$X$ `, `:5` → `$5$ `)
|
||||||
|
- Greek letters: `;<latin>` → `<greek>` in math and `$<greek>$ ` in markup (e.g. `;a` → `alpha`/`$alpha$ `)
|
||||||
|
- Common indices (numbers and letters `i-m`): `<letter><index>` → `<letter>_<index>` in math and `$<letter>$<index> ` → `$<letter>_<index>$ ` 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)): `<expression><operation>` → `<operation>(<expression>)` (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: `<size>ma` and `<size>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 = '<Tab>'`) 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).
|
||||||
|
|
||||||
@@ -43,13 +43,14 @@ function M.ri(insert_node_id)
|
|||||||
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
return luasnip.function_node(function(args) return args[1][1] end, insert_node_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.snip(trigger, expand, insert, condition, priority)
|
function M.snip(trigger, expand, insert, condition, priority, wordTrig)
|
||||||
priority = priority or 1000
|
priority = priority or 1000
|
||||||
return luasnip.snippet(
|
return luasnip.snippet(
|
||||||
{
|
{
|
||||||
trig = trigger,
|
trig = trigger,
|
||||||
trigEngine = M.engine,
|
trigEngine = M.engine,
|
||||||
trigEngineOpts = { condition = condition },
|
trigEngineOpts = { condition = condition },
|
||||||
|
wordTrig = wordTrig,
|
||||||
priority = priority,
|
priority = priority,
|
||||||
snippetType = 'autosnippet'
|
snippetType = 'autosnippet'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ local default_config = {
|
|||||||
excalidraw = {
|
excalidraw = {
|
||||||
assetsDir = 'assets',
|
assetsDir = 'assets',
|
||||||
filename = 'drawing-%Y-%m-%d-%H-%M-%S',
|
filename = 'drawing-%Y-%m-%d-%H-%M-%S',
|
||||||
|
fileExtension = '.excalidraw.md',
|
||||||
fileExtensionInserted = '.excalidraw.svg',
|
fileExtensionInserted = '.excalidraw.svg',
|
||||||
obsidianOpenConfig = nil,
|
uriOpenCommand = 'xdg-open', -- set depending on OS
|
||||||
|
templatePath = nil,
|
||||||
},
|
},
|
||||||
snippets = {
|
snippets = {
|
||||||
enable = true,
|
enable = true,
|
||||||
modules = {
|
modules = { -- enable modules from ./snippets
|
||||||
'document',
|
'document',
|
||||||
'letters',
|
'letters',
|
||||||
'math',
|
'math',
|
||||||
@@ -22,8 +24,10 @@ local default_config = {
|
|||||||
|
|
||||||
function M.merge_config(args)
|
function M.merge_config(args)
|
||||||
M.config = vim.tbl_deep_extend('force', default_config, args or {})
|
M.config = vim.tbl_deep_extend('force', default_config, args or {})
|
||||||
M.config.excalidraw.obsidianOpenConfig = M.config.excalidraw.obsidianOpenConfig or
|
M.config.excalidraw.templatePath = M.config.excalidraw.templatePath or
|
||||||
M.config.typstarRoot .. '/res/obsidian_open_config_example.json'
|
{
|
||||||
|
['%.excalidraw%.md$'] = M.config.typstarRoot .. '/res/excalidraw_template.excalidraw.md',
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
M.merge_config(nil)
|
M.merge_config(nil)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ local M = {}
|
|||||||
local config = require('typstar.config')
|
local config = require('typstar.config')
|
||||||
local utils = require('typstar.utils')
|
local utils = require('typstar.utils')
|
||||||
|
|
||||||
|
|
||||||
local cfg = config.config.excalidraw
|
local cfg = config.config.excalidraw
|
||||||
local affix = [[
|
local affix = [[
|
||||||
#figure(
|
#figure(
|
||||||
@@ -10,24 +9,35 @@ local affix = [[
|
|||||||
)
|
)
|
||||||
]]
|
]]
|
||||||
|
|
||||||
local function launch_obsidian_open(path)
|
local function launch_obsidian(path)
|
||||||
print(string.format('Opening %s in Excalidraw', path))
|
print(string.format('Opening %s in Excalidraw', path))
|
||||||
utils.run_shell_command('python3 ' ..
|
utils.run_shell_command(string.format('%s "obsidian://open?path=%s"', cfg.uriOpenCommand, utils.urlencode(path)))
|
||||||
config.config.typstarRoot .. '/python/obsidian_open.py ' ..
|
|
||||||
path .. ' --config ' .. cfg.obsidianOpenConfig)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
function M.insert_drawing()
|
function M.insert_drawing()
|
||||||
local assets_dir = vim.fn.expand('%:p:h') .. '/' .. cfg.assetsDir
|
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
|
if vim.fn.isdirectory(assets_dir) == 0 then
|
||||||
vim.fn.mkdir(assets_dir, 'p')
|
vim.fn.mkdir(assets_dir, 'p')
|
||||||
end
|
end
|
||||||
local filename = os.date(cfg.filename)
|
local found_match = false
|
||||||
local path = assets_dir .. '/' .. filename .. '.excalidraw.md'
|
for pattern, template_path in pairs(cfg.templatePath) do
|
||||||
local path_inserted = cfg.assetsDir .. '/' .. filename .. cfg.fileExtensionInserted
|
if string.match(path, pattern) then
|
||||||
utils.insert_snippet(string.format(affix, path_inserted))
|
found_match = true
|
||||||
launch_obsidian_open(path)
|
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(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open_drawing()
|
function M.open_drawing()
|
||||||
@@ -35,7 +45,7 @@ function M.open_drawing()
|
|||||||
local path = vim.fn.expand('%:p:h') ..
|
local path = vim.fn.expand('%:p:h') ..
|
||||||
'/' .. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) ..
|
'/' .. string.match(line, '"(.*)' .. string.gsub(cfg.fileExtensionInserted, '%.', '%%%.')) ..
|
||||||
'.excalidraw.md'
|
'.excalidraw.md'
|
||||||
launch_obsidian_open(path)
|
launch_obsidian(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -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 helper = require('typstar.autosnippets')
|
||||||
local snip = helper.snip
|
local snip = helper.snip
|
||||||
local cap = helper.cap
|
local cap = helper.cap
|
||||||
local math = helper.in_math
|
local math = helper.in_math
|
||||||
local markup = helper.in_markup
|
local markup = helper.in_markup
|
||||||
|
|
||||||
|
|
||||||
local letter_snippets = {}
|
local letter_snippets = {}
|
||||||
local greek_letters = {
|
local greek_letters_map = {
|
||||||
{ 'a', 'alpha' }, { 'A', 'Alpha' },
|
['a'] = 'alpha',
|
||||||
{ 'b', 'beta' }, { 'B', 'Beta' },
|
['b'] = 'beta',
|
||||||
{ 'c', 'chi' }, { 'C', 'Chi' },
|
['c'] = 'chi',
|
||||||
{ 'd', 'delta' }, { 'D', 'Delta' },
|
['d'] = 'delta',
|
||||||
{ 'e', 'epsilon' }, { 'E', 'Epsilon' },
|
['e'] = 'epsilon',
|
||||||
{ 'g', 'gamma' }, { 'G', 'Gamma' },
|
['g'] = 'gamma',
|
||||||
{ 'h', 'phi' }, { 'H', 'Phi' },
|
['h'] = 'phi',
|
||||||
{ 'i', 'iotta' }, { 'I', 'Iotta' },
|
['i'] = 'iotta',
|
||||||
{ 'j', 'theta' }, { 'J', 'Theta' },
|
['j'] = 'theta',
|
||||||
{ 'k', 'kappa' }, { 'K', 'Kappa' },
|
['k'] = 'kappa',
|
||||||
{ 'l', 'lambda' }, { 'L', 'Lambda' },
|
['l'] = 'lambda',
|
||||||
{ 'm', 'mu' }, { 'M', 'Mu' },
|
['m'] = 'mu',
|
||||||
{ 'n', 'nu' }, { 'N', 'Nu' },
|
['n'] = 'nu',
|
||||||
{ 'o', 'omega' }, { 'O', 'Omega' },
|
['o'] = 'omega',
|
||||||
{ 'p', 'pi' }, { 'P', 'Pi' },
|
['p'] = 'pi',
|
||||||
{ 'q', 'eta' }, { 'Q', 'Eta' },
|
['q'] = 'eta',
|
||||||
{ 'r', 'rho' }, { 'R', 'Rho' },
|
['r'] = 'rho',
|
||||||
{ 's', 'sigma' }, { 'S', 'Sigma' },
|
['s'] = 'sigma',
|
||||||
{ 't', 'tau' }, { 'T', 'Tau' },
|
['t'] = 'tau',
|
||||||
{ 'x', 'xi' }, { 'X', 'xi' },
|
['x'] = 'xi',
|
||||||
{ 'z', 'zeta' }, { 'Z', 'Zeta' },
|
['z'] = 'zeta',
|
||||||
}
|
}
|
||||||
local latin_letters = { 'f', 'u', 'v', 'w', 'y' } -- remaining ones are added dynamically
|
local greek_letters = {}
|
||||||
local common_indices = { '\\d+', 'i', 'j', 'k', 'n' }
|
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
|
local upper_first = function(str)
|
||||||
table.insert(latin_letters, letter:upper())
|
return str:sub(1, 1):upper() .. str:sub(2, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
local generate_index_snippets = function(letter)
|
local greek_full = {}
|
||||||
for _, index in pairs(common_indices) do
|
for latin, greek in pairs(greek_letters_map) do
|
||||||
table.insert(letter_snippets,
|
greek_full[latin] = greek
|
||||||
snip(letter .. '(' .. index .. ') ', letter .. '_(<>) ', { cap(1) }, math, 200))
|
greek_full[latin:upper()] = upper_first(greek)
|
||||||
table.insert(letter_snippets,
|
table.insert(greek_letters, greek)
|
||||||
snip('\\$' .. letter .. '\\$(' .. index .. ') ', '$' .. letter .. '_(<>)$ ', { cap(1) }, markup, 200))
|
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
|
end
|
||||||
|
return s(nil, t(letter .. '_' .. index))
|
||||||
end
|
end
|
||||||
|
|
||||||
for _, val in pairs(greek_letters) do
|
table.insert(letter_snippets, snip(':(' .. trigger_latin .. ')', '$<>$ ', { cap(1) }, markup))
|
||||||
table.insert(letter_snippets, snip(';' .. val[1], val[2], {}, math))
|
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '$<>$ ', { d(1, get_greek) }, markup))
|
||||||
table.insert(letter_snippets, snip(';' .. val[1], '$' .. val[2] .. '$ ', {}, markup))
|
table.insert(letter_snippets, snip(';(' .. trigger_greek .. ')', '<> ', { d(1, get_greek) }, math))
|
||||||
generate_index_snippets(val[2])
|
table.insert(letter_snippets,
|
||||||
table.insert(latin_letters, val[1])
|
snip('\\$(' .. trigger_index_pre .. ')\\$' .. '(' .. trigger_index_post .. ') ',
|
||||||
end
|
'$<>$ ', { d(1, get_index) }, markup, 500))
|
||||||
|
table.insert(letter_snippets,
|
||||||
for _, letter in pairs(latin_letters) do
|
snip('(' .. trigger_index_pre .. ')' .. '(' .. trigger_index_post .. ') ', '<> ', { d(1, get_index) }, math, 200))
|
||||||
generate_index_snippets(letter)
|
|
||||||
table.insert(letter_snippets, snip(':' .. letter, '$' .. letter .. '$ ', {}, markup))
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
unpack(letter_snippets)
|
unpack(letter_snippets)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ return {
|
|||||||
snip('een', 'exists epsilon>>0 ', {}, math),
|
snip('een', 'exists epsilon>>0 ', {}, math),
|
||||||
|
|
||||||
-- boolean logic
|
-- boolean logic
|
||||||
snip('an', 'and ', {}, math),
|
|
||||||
snip('no', 'not ', {}, math),
|
snip('no', 'not ', {}, math),
|
||||||
|
|
||||||
-- relations
|
-- relations
|
||||||
@@ -52,7 +51,7 @@ return {
|
|||||||
snip('nn', 'sect ', {}, math),
|
snip('nn', 'sect ', {}, math),
|
||||||
snip('uu', 'union ', {}, math),
|
snip('uu', 'union ', {}, math),
|
||||||
snip('bnn', 'sect.big ', {}, math, 1100),
|
snip('bnn', 'sect.big ', {}, math, 1100),
|
||||||
snip('buu', 'untion.big ', {}, math, 1100),
|
snip('buu', 'union.big ', {}, math, 1100),
|
||||||
snip('swo', 'without ', {}, math),
|
snip('swo', 'without ', {}, math),
|
||||||
|
|
||||||
-- misc
|
-- misc
|
||||||
@@ -62,11 +61,10 @@ return {
|
|||||||
snip('iso', 'tilde.equiv ', {}, math),
|
snip('iso', 'tilde.equiv ', {}, math),
|
||||||
snip('rrn', 'RR^n ', {}, math),
|
snip('rrn', 'RR^n ', {}, math),
|
||||||
snip('cc', 'cases(\n\t<>\n)\\', { i(1, '1') }, 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('(.*)iv', '<>^(-1)', { cap(1) }, math),
|
||||||
snip('(.*)sr', '<>^(2)', { cap(1) }, math),
|
snip('(.*)sr', '<>^2', { cap(1) }, math),
|
||||||
snip('(.*)rd', '<>^(<>)', { cap(1), i(1, 'n') }, 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('ddx', '(d <>)(d <>)', { i(1, 'f'), i(2, 'x') }, math),
|
||||||
snip('it', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math),
|
snip('it', 'integral_(<>)^(<>)', { i(1, 'a'), i(2, 'b') }, math),
|
||||||
|
|||||||
@@ -1,72 +1,90 @@
|
|||||||
|
local ts = vim.treesitter
|
||||||
local ls = require('luasnip')
|
local ls = require('luasnip')
|
||||||
local i = ls.insert_node
|
|
||||||
local d = ls.dynamic_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 helper = require('typstar.autosnippets')
|
||||||
local math = helper.in_math
|
local math = helper.in_math
|
||||||
local snip = helper.snip
|
local snip = helper.snip
|
||||||
local cap = helper.cap
|
|
||||||
local get_visual = helper.get_visual
|
|
||||||
|
|
||||||
local snippets = {}
|
local snippets = {}
|
||||||
local operations = { -- boolean denotes whether an additional layer of () brackets should be removed
|
local operations = { -- first boolean: existing brackets should be kept; second boolean: brackets should be added
|
||||||
{ 'vi', '1/(', ')', true },
|
{ 'vi', '1/', '', true, false },
|
||||||
{ 'bb', '(', ')', false },
|
{ 'bb', '(', ')', true, false }, -- add round brackets
|
||||||
{ 'sq', '[', ']', true },
|
{ 'sq', '[', ']', true, false }, -- add square brackets
|
||||||
{ 'abs', 'abs(', ')', false },
|
{ 'bB', '(', ')', false, false }, -- replace with round brackets
|
||||||
{ 'ul', 'underline(', ')', false },
|
{ 'sQ', '[', ']', false, false }, -- replace with square brackets
|
||||||
{ 'ol', 'overline(', ')', false },
|
{ 'BB', '', '', false, false }, -- remove brackets
|
||||||
{ 'ub', 'underbrace(', ')', false },
|
{ 'ss', '"', '"', false, false },
|
||||||
{ 'ob', 'overbrace(', ')', false },
|
{ 'abs', 'abs', '', true, true },
|
||||||
{ 'ht', 'hat(', ')', false },
|
{ 'ul', 'underline', '', true, true },
|
||||||
{ 'br', 'macron(', ')', false },
|
{ 'ol', 'overline', '', true, true },
|
||||||
{ 'dt', 'dot(', ')', false },
|
{ 'ub', 'underbrace', '', true, true },
|
||||||
{ 'ci', 'circle(', ')', false },
|
{ 'ob', 'overbrace', '', true, true },
|
||||||
{ 'td', 'tilde(', ')', false },
|
{ 'ht', 'hat', '', true, true },
|
||||||
{ 'nr', 'norm(', ')', false },
|
{ 'br', 'macron', '', true, true },
|
||||||
{ 'vv', 'vec(', ')', false },
|
{ 'dt', 'dot', '', true, true },
|
||||||
{ 'rt', 'sqrt(', ')', false },
|
{ '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 ts_wrap_query = ts.query.parse('typst', '[(call) (ident) (letter) (number)] @wrap')
|
||||||
local captured = snippet.captures[1]
|
local ts_wrapnobrackets_query = ts.query.parse('typst', '(group) @wrapnobrackets')
|
||||||
local bracket_types = { [')'] = '(', [']'] = '[', ['}'] = '{' }
|
|
||||||
local closing_bracket = captured:sub(-1, -1)
|
|
||||||
local opening_bracket = bracket_types[closing_bracket]
|
|
||||||
|
|
||||||
if opening_bracket == nil then
|
local process_ts_query = function(bufnr, cursor, query, root, insert1, insert2, cut_offset)
|
||||||
return captured
|
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
|
end
|
||||||
|
local old_len2, new_len2 = utils.insert_text(
|
||||||
local n_brackets = 0
|
bufnr, end_row, cursor[2] + cursor_offset, insert2, cut_offset, 0)
|
||||||
local char
|
if end_row == cursor[1] then
|
||||||
|
cursor_offset = cursor_offset + (new_len2 - old_len2)
|
||||||
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
|
end
|
||||||
|
vim.api.nvim_win_set_cursor(0, { cursor[1] + 1, cursor[2] + cursor_offset })
|
||||||
if n_brackets == 0 then
|
end)
|
||||||
local remove_additional = val[4] and opening_bracket == '('
|
return true
|
||||||
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]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return captured
|
end
|
||||||
|
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
|
end
|
||||||
|
|
||||||
for _, val in pairs(operations) do
|
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(val[1], '<>', { d(1, smart_wrap, {}, { user_args = { val } }) }, math, 1000, false))
|
||||||
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))
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,27 +7,59 @@ function M.get_cursor_pos()
|
|||||||
return { cursor_row, cursor_col }
|
return { cursor_row, cursor_col }
|
||||||
end
|
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 line_num = M.get_cursor_pos()[1] + 1
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for line in snip:gmatch '[^\r\n]+' do
|
for line in snip:gmatch '[^\r\n]+' do
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
function M.run_shell_command(cmd)
|
function M.run_shell_command(cmd)
|
||||||
vim.fn.jobstart(cmd)
|
vim.fn.jobstart(cmd)
|
||||||
end
|
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
|
||||||
|
|
||||||
|
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)
|
function M.cursor_within_treesitter_query(query, match_tolerance, cursor)
|
||||||
cursor = cursor or M.get_cursor_pos()
|
cursor = cursor or M.get_cursor_pos()
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
local root = ts.get_parser(bufnr):parse()[1]:root()
|
for _, match, _ in query:iter_matches(M.get_treesitter_root(bufnr), bufnr, cursor[1], cursor[1] + 1) do
|
||||||
for _, match, _ in query:iter_matches(root, bufnr, cursor[1], cursor[1] + 1) do
|
|
||||||
if match then
|
if match then
|
||||||
local start_row, start_col, _, _ = match[1]:range()
|
local start_row, start_col, end_row, end_col = M.treesitter_match_start_end(match)
|
||||||
local _, _, end_row, end_col = match[#match]:range()
|
|
||||||
local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col,
|
local matched = M.cursor_within_coords(cursor, start_row, end_row, start_col, end_col,
|
||||||
match_tolerance)
|
match_tolerance)
|
||||||
if matched then
|
if matched then
|
||||||
|
|||||||
@@ -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}")
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"\\.excalidraw\\.md$": "~/typstar/res/excalidraw_template.excalidraw.md"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user