···11+vim.diagnostic.config({
22+ underline = true,
33+ -- virtual_text
44+ -- signs
55+ -- float
66+ -- update_in_insert
77+ servirty_sort = true,
88+})
99+1010+do
1111+ local ok, mason = pcall(require, "mason")
1212+ if ok then
1313+ mason.setup()
1414+ end
1515+end
1616+-- do
1717+-- local ok, neodev = pcall(require, "neodev")
1818+-- if ok then
1919+-- neodev.setup()
2020+-- end
2121+-- end
2222+2323+vim.api.nvim_create_autocmd("LspAttach", {
2424+ group = vim.api.nvim_create_augroup("UserLspAttach", { clear = false }),
2525+ callback = function(ev)
2626+ vim.lsp.completion.enable(true, ev.data.client_id, ev.buf, { autotrigger = false })
2727+ end,
2828+})
2929+3030+local ok, lspconfig = pcall(require, "lspconfig")
3131+if not ok then return end
3232+3333+local function setup(server, opts)
3434+ -- NOTE: This isn't perfect, but it should work for 99% of uninstalled servers
3535+ local cmd = lspconfig[server].document_config.default_config.cmd[1]
3636+ if vim.fn.executable(cmd) == 0 then
3737+ return
3838+ end
3939+ opts = opts or {}
4040+ lspconfig[server].setup(opts)
4141+end
4242+4343+for name, opts in pairs(require("core.lsp.servers")) do
4444+ setup(name, opts)
4545+end
···11+require("luasnip.session.snippet_collection").clear_snippets("rust")
22+33+local ls = require("luasnip")
44+local s = ls.snippet
55+local sn = ls.snippet_node
66+local i = ls.insert_node
77+local t = ls.text_node
88+local c = ls.choice_node
99+local fmt = require("luasnip.extras.fmt").fmt
1010+1111+-- stylua: ignore
1212+ls.add_snippets("rust", {
1313+ s("cl", {
1414+ t"|", i(1), t"|", t" {", i(2), t"}",
1515+ }),
1616+})
+103
lua/snippets/tsx.lua
···11+require("luasnip.session.snippet_collection").clear_snippets("typescriptreact")
22+33+local ls = require("luasnip")
44+local s = ls.snippet
55+local i = ls.insert_node
66+local t = ls.text_node
77+local c = ls.choice_node
88+local fn = ls.function_node
99+local dn = ls.dynamic_node
1010+local sn = ls.snippet_node
1111+local rep = require("luasnip.extras").rep
1212+local fmt = require("luasnip.extras.fmt").fmt
1313+local fmta = require("luasnip.extras.fmt").fmta
1414+1515+-- Get a list of the property names given an `interface_declaration`
1616+-- treesitter *tsx* node.
1717+-- Ie, if the treesitter node represents:
1818+-- interface {
1919+-- prop1: string;
2020+-- prop2: number;
2121+-- }
2222+-- Then this function would return `{"prop1", "prop2"}
2323+---@param node TSNode interface declaration node
2424+---@return string[]
2525+local function get_prop_names(node)
2626+ local interface_body = node:field("body")[1]
2727+ if not interface_body then
2828+ return {}
2929+ end
3030+3131+ local prop_names = {}
3232+3333+ for prop_signature in interface_body:iter_children() do
3434+ if prop_signature:type() == "property_signature" then
3535+ local prop_iden = prop_signature:child(0)
3636+ local prop_name = vim.treesitter.get_node_text(prop_iden, 0)
3737+ prop_names[#prop_names + 1] = prop_name
3838+ end
3939+ end
4040+4141+ return prop_names
4242+end
4343+4444+-- original: https://gist.github.com/davidatsurge/9873d9cb1781f1a37c0f25d24cb1b3ab
4545+-- https://www.reddit.com/r/neovim/comments/uuhk1t/feedback_on_luasniptreesitter_snippet_that_fills/
4646+local componentWithProps = fmta(
4747+ [[
4848+ <>interface <>Props {
4949+ <><>
5050+ }
5151+5252+ export function <>({ <> }: <>Props) {
5353+ <>return <>;
5454+ };
5555+ ]],
5656+ {
5757+ c(1, { t "export ", t "" }),
5858+ -- Initialize component name to file name
5959+ rep(3),
6060+ t "\t",
6161+ i(2, "// Props"),
6262+ dn(3, function(_, snip)
6363+ local filename = vim.fn.expand("%:t:r")
6464+ local comp_name
6565+ -- TODO: rewrite this with `vim.fs` lua api
6666+ if filename == "index" then
6767+ comp_name = vim.fn.expand("%"):match("([^/]+)/[^/]+$")
6868+ else
6969+ comp_name = vim.fn.substitute(snip.env.TM_FILENAME, "\\..*$", "", "g")
7070+ end
7171+ return sn(nil, { i(1, comp_name) })
7272+ end, { 1 }),
7373+ fn(function(_, snip, _)
7474+ local pos_begin = snip.nodes[4].mark:pos_begin()
7575+ local pos_end = snip.nodes[4].mark:pos_end()
7676+ local parser = vim.treesitter.get_parser(0, "tsx")
7777+ local tstree = parser:parse()
7878+7979+ local node = tstree[1]:root():named_descendant_for_range(pos_begin[1], pos_begin[2], pos_end[1], pos_end[2])
8080+8181+ while node ~= nil and node:type() ~= "interface_declaration" do
8282+ node = node:parent()
8383+ end
8484+8585+ if node == nil then
8686+ return ""
8787+ end
8888+8989+ -- `node` is now surely of type "interface_declaration"
9090+ local prop_names = get_prop_names(node)
9191+9292+ return vim.fn.join(prop_names, ", ")
9393+ end, { 2 }),
9494+ rep(3),
9595+ t "\t",
9696+ i(4, "null"),
9797+ }
9898+)
9999+100100+-- stylua: ignore
101101+ls.add_snippets("typescriptreact", {
102102+ s("cp", componentWithProps),
103103+})
+90
lua/utils/format.lua
···11+local Util = require("utils")
22+33+---@class bt.util.format
44+local M = {}
55+66+---@param buf? number
77+---@return boolean
88+function M.enabled(buf)
99+ buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf
1010+1111+ -- autoformat options fallback. buffer > editorconfig > global
1212+ -- stylua: ignore
1313+ return vim.F.if_nil(
1414+ vim.b[buf].autoformat,
1515+ vim.b[buf].editorconfig_autoformat,
1616+ vim.g.autoformat
1717+ )
1818+end
1919+2020+function M.toggle()
2121+ -- TODO: if editorconfig says autoformat in that buffer is enabled,
2222+ -- disable it with buffer-local variable
2323+ if vim.b.editorconfig_autoformat ~= nil or vim.b.autoformat ~= nil then
2424+ vim.b.autoformat = not M.enabled()
2525+ else
2626+ vim.g.autoformat = not M.enabled()
2727+ vim.b.autoformat = nil
2828+ end
2929+ M.info()
3030+end
3131+3232+---@param buf? number
3333+function M.info(buf)
3434+ buf = buf or vim.api.nvim_get_current_buf()
3535+ Util.notify.info({
3636+ ("* Status *%s*"):format(M.enabled(buf) and "enabled" or "disabled"),
3737+ ("- (%s) global"):format(vim.g.autoformat and "x" or " "),
3838+ ("- (%s) editorconfig"):format(vim.b[buf].editorconfig_autoformat and "x" or " "),
3939+ ("- (%s) buffer"):format(vim.b[buf].autoformat and "x" or " "),
4040+ })
4141+end
4242+4343+function M.setup()
4444+ vim.g.autoformat = false
4545+ -- Autoformat autocmd
4646+ vim.api.nvim_create_autocmd("BufWritePre", {
4747+ group = vim.api.nvim_create_augroup("AutoFormat", {}),
4848+ callback = function(event)
4949+ if M.enabled(event.buf) then
5050+ require("conform").format({
5151+ lsp_fallback = true,
5252+ async = false,
5353+ })
5454+ end
5555+ end,
5656+ })
5757+5858+ vim.api.nvim_create_user_command("Format", function(args)
5959+ local range = nil
6060+ if args.count ~= -1 then
6161+ local end_line = vim.api.nvim_buf_get_lines(0, args.line2 - 1, args.line2, true)[1]
6262+ range = {
6363+ start = { args.line1, 0 },
6464+ ["end"] = { args.line2, end_line:len() },
6565+ }
6666+ end
6767+6868+ local ok = require("conform").format({
6969+ lsp_fallback = true,
7070+ async = true,
7171+ range = range,
7272+ })
7373+ -- TODO: check that ranged-format failed
7474+ if not ok then
7575+ Util.notify.warn("No formatter available", { title = "Formatter" })
7676+ end
7777+ end, {
7878+ desc = "Format selection or buffer",
7979+ range = true,
8080+ })
8181+8282+ vim.api.nvim_create_user_command("FormatInfo", function()
8383+ M.info()
8484+ end, { desc = "Show info about the formatters for the current buffer" })
8585+8686+ vim.api.nvim_create_user_command("FormatToggle", function()
8787+ M.toggle()
8888+ end, { desc = "Toggle autoformat option" })
8989+end
9090+return M
+215
lua/utils/highlights.lua
···11+---@class bt.util.highlights
22+local M = {}
33+44+-- TODO: fuck. rewrite this sometime
55+-- dealing with types is way hard then I thought
66+77+---@alias HLAttr {from: string, attr: "fg" | "bg", alter: integer}
88+99+---@class HLData
1010+---@field fg? string foreground
1111+---@field bg? string background
1212+---@field sp? string special
1313+---@field blend? integer between 0 and 100
1414+---@field bold? boolean
1515+---@field standout? boolean
1616+---@field underline? boolean
1717+---@field undercurl? boolean
1818+---@field underdouble? boolean
1919+---@field underdotted? boolean
2020+---@field underdashed? boolean
2121+---@field strikethrough? boolean
2222+---@field italic? boolean
2323+---@field reverse? boolean
2424+---@field nocombine? boolean
2525+---@field link? string
2626+---@field default? boolean
2727+2828+---@alias HLAttrName
2929+---| '"fg"'
3030+---| '"bg"'
3131+---| '"sp"'
3232+---| '"blend"'
3333+---| '"bold"'
3434+---| '"standout"'
3535+---| '"underline"'
3636+---| '"undercurl"'
3737+---| '"underdouble"'
3838+---| '"underdotted"'
3939+---| '"underdashed"'
4040+---| '"strikethrough"'
4141+---| '"italic"'
4242+---| '"reverse"'
4343+---| '"nocombine"'
4444+---| '"link"'
4545+---| '"default"'
4646+4747+---@class HLArgs: HLData
4848+---@field fg? string | HLAttr
4949+---@field bg? string | HLAttr
5050+---@field sp? string | HLAttr
5151+---@field clear? boolean clear existing highlight
5252+---@field inherit? string inherit other highlight
5353+5454+---@private
5555+---@param opts? {name?: string, link?: boolean}
5656+---@param ns? integer
5757+---@return vim.api.keyset.hl_info|nil
5858+local function get_hl_as_hex(opts, ns)
5959+ opts = opts or {}
6060+ ns = ns or 0
6161+ opts.link = opts.link ~= nil and opts.link or false
6262+ local hl = vim.api.nvim_get_hl(ns, opts)
6363+ if vim.tbl_isempty(hl) then
6464+ return nil
6565+ end
6666+ hl.fg = hl.fg and ("#%06x"):format(hl.fg)
6767+ hl.bg = hl.bg and ("#%06x"):format(hl.bg)
6868+ return hl
6969+end
7070+7171+---Change the brightness of a color, negative numbers darken and positive ones brighten
7272+---see:
7373+---1. https://stackoverflow.com/q/5560248
7474+---2. https://stackoverflow.com/a/37797380
7575+---@param color string A hex color
7676+---@param percent float a negative number darkens and a positive one brightens
7777+---@return string
7878+function M.tint(color, percent)
7979+ assert(color and percent, "cannot alter a color without specifying a color and percentage")
8080+ local r = tonumber(color:sub(2, 3), 16)
8181+ local g = tonumber(color:sub(4, 5), 16)
8282+ local b = tonumber(color:sub(6), 16)
8383+ if not r or not g or not b then
8484+ return "NONE"
8585+ end
8686+ local blend = function(component)
8787+ component = math.floor(component * (1 + percent))
8888+ return math.min(math.max(component, 0), 255)
8989+ end
9090+ return string.format("#%02x%02x%02x", blend(r), blend(g), blend(b))
9191+end
9292+9393+---Get the value a highlight group whilst handling errors and fallbacks as well as returning a gui value
9494+---If no attribute is specified return the entire highlight table
9595+---in the right format
9696+---@param group string
9797+---@param attribute HLAttrName
9898+---@param fallback string?
9999+---@return string
100100+function M.get(group, attribute, fallback)
101101+ local data = get_hl_as_hex({ name = group })
102102+ local color = (data and data[attribute]) or fallback or "NONE"
103103+ if not color then
104104+ local error_msg =
105105+ string.format("failed to get highlight %s for attribute %s\n%s", group, attribute, debug.traceback())
106106+ local error_title = string.format("Highlight - get(%s)", group)
107107+ vim.schedule(function()
108108+ vim.notify(error_msg, vim.log.levels.ERROR, { title = error_title })
109109+ end)
110110+ return "NONE"
111111+ end
112112+ return color
113113+end
114114+115115+---resolve fg/bg/sp attribute type
116116+---@param hl string | HLAttr
117117+---@param attr string
118118+---@return string
119119+local function resolve_from_attr(hl, attr)
120120+ if type(hl) ~= "table" then
121121+ return hl
122122+ end
123123+ local color = M.get(hl.from, hl.attr or attr)
124124+ color = color == "NONE" and M.get("Normal", hl.attr or attr) or color
125125+ -- TODO: tint color
126126+ return color
127127+end
128128+129129+--- Sets a neovim highlight with some syntactic sugar. It takes a highlight table and converts
130130+--- any highlights specified as `GroupName = {fg = { from = 'group'}}` into the underlying colour
131131+--- by querying the highlight property of the from group so it can be used when specifying highlights
132132+--- as a shorthand to derive the right colour.
133133+--- For example:
134134+--- ```lua
135135+--- M.set({ MatchParen = {fg = {from = 'ErrorMsg'}}})
136136+--- ```
137137+--- This will take the foreground colour from ErrorMsg and set it to the foreground of MatchParen.
138138+--- NOTE: this function must NOT mutate the options table as these are re-used when the colorscheme is updated
139139+---
140140+---@param ns integer
141141+---@param name string
142142+---@param opts HLArgs
143143+---@overload fun(name: string, opts: HLArgs)
144144+function M.set(ns, name, opts)
145145+ if type(ns) == "string" and type(name) == "table" then
146146+ opts, name, ns = name, ns, 0
147147+ end
148148+149149+ local hl = opts.clear and {} or get_hl_as_hex({ name = opts.inherit or name }) or {}
150150+ for attribute, data in pairs(opts) do
151151+ if attribute ~= "clear" and attribute ~= "inherit" then
152152+ local new_data = resolve_from_attr(data, attribute)
153153+ hl[attribute] = new_data
154154+ end
155155+ end
156156+157157+ -- FIXME: this part
158158+ vim.api.nvim_set_hl(ns, name, hl --[[@as vim.api.keyset.highlight]])
159159+end
160160+161161+---Apply a list of highlights
162162+---@param hls table<string, HLArgs>
163163+---@param namespace integer?
164164+function M.all(hls, namespace)
165165+ for name, args in pairs(hls) do
166166+ M.set(namespace or 0, name, args)
167167+ end
168168+end
169169+170170+---Set window local highlights
171171+---@param name string
172172+---@param win_id number
173173+---@param hls table<string, HLArgs>
174174+function M.set_winhl(name, win_id, hls)
175175+ local namespace = vim.api.nvim_create_namespace(name)
176176+ M.all(hls, namespace)
177177+ vim.api.nvim_win_set_hl_ns(win_id, namespace)
178178+end
179179+180180+---Run `cb()` on `ColorScheme` event.
181181+---This is useful when *color override* code is quite complicate
182182+---@param name string
183183+---@param cb function
184184+function M.plugin_wrap(name, cb)
185185+ cb()
186186+ local augroup_name = name:gsub("^%l", string.upper) .. "HighlightOverrides"
187187+ vim.api.nvim_create_autocmd({ "ColorScheme", "UIEnter" }, {
188188+ group = vim.api.nvim_create_augroup(augroup_name, { clear = true }),
189189+ callback = function()
190190+ -- Defer resetting these highlights to ensure they apply *after* other overrides
191191+ vim.defer_fn(function()
192192+ cb()
193193+ end, 1)
194194+ end,
195195+ })
196196+end
197197+198198+---Apply highlights for a plugin and refresh on colorscheme change
199199+---@param name string plugin name
200200+---@param hls table<string, HLArgs>
201201+function M.plugin(name, hls)
202202+ M.plugin_wrap(name, function()
203203+ M.all(hls)
204204+ end)
205205+end
206206+207207+---Apply highlight to given text
208208+---@param content any
209209+---@param hlgroup string
210210+---@return string
211211+function M.hl_text(content, hlgroup)
212212+ return string.format("%%#%s#%s%%*", hlgroup, content)
213213+end
214214+215215+return M
+27
lua/utils/init.lua
···11+---@class bt.util
22+---@field notify bt.util.notify
33+---@field highlights bt.util.highlights
44+---@field format bt.util.format
55+local M = {}
66+setmetatable(M, {
77+ __index = function(t, k)
88+ t[k] = require("utils." .. k)
99+ return t[k]
1010+ end,
1111+})
1212+1313+---@param ms number
1414+---@param fn function
1515+---@return function
1616+function M.debounce(ms, fn)
1717+ local timer = vim.uv.new_timer()
1818+ return function(...)
1919+ local argv = {...}
2020+ timer:start(ms, 0, function()
2121+ timer:stop()
2222+ vim.schedule_wrap(fn)(unpack(argv))
2323+ end)
2424+ end
2525+end
2626+2727+return M
+53
lua/utils/notify.lua
···11+---@class bt.util.notify
22+local M = {}
33+44+---@alias BtNotifyOpts {lang?:string, title?:string, level?:number, once?:boolean}
55+66+---@param msg string|string[]
77+---@param opts? BtNotifyOpts
88+function M.notify(msg, opts)
99+ if vim.in_fast_event() then
1010+ return vim.schedule(function()
1111+ M.notify(msg, opts)
1212+ end)
1313+ end
1414+1515+ opts = opts or {}
1616+ if type(msg) == "table" then
1717+ msg = table.concat(
1818+ vim.tbl_filter(function(line)
1919+ return line or false
2020+ end, msg),
2121+ "\n"
2222+ )
2323+ end
2424+ vim.notify(msg, opts.level or vim.log.levels.INFO, {
2525+ title = opts.title or "config",
2626+ })
2727+end
2828+2929+---@param msg string|string[]
3030+---@param opts? BtNotifyOpts
3131+function M.error(msg, opts)
3232+ opts = opts or {}
3333+ opts.level = vim.log.levels.ERROR
3434+ M.notify(msg, opts)
3535+end
3636+3737+---@param msg string|string[]
3838+---@param opts? BtNotifyOpts
3939+function M.info(msg, opts)
4040+ opts = opts or {}
4141+ opts.level = vim.log.levels.INFO
4242+ M.notify(msg, opts)
4343+end
4444+4545+---@param msg string|string[]
4646+---@param opts? BtNotifyOpts
4747+function M.warn(msg, opts)
4848+ opts = opts or {}
4949+ opts.level = vim.log.levels.WARN
5050+ M.notify(msg, opts)
5151+end
5252+5353+return M
+82
rocks.toml
···11+# This is your rocks.nvim plugins declaration file.
22+# Here is a small yet pretty detailed example on how to use it:
33+#
44+# [plugins]
55+# nvim-treesitter = "semver_version" # e.g. "1.0.0"
66+77+# List of non-Neovim rocks.
88+# This includes things like `toml` or other lua packages.
99+[rocks]
1010+1111+# List of Neovim plugins to install alongside their versions.
1212+# If the plugin name contains a dot then you must add quotes to the key name!
1313+[plugins]
1414+"rocks.nvim" = "2.35.0"
1515+"rocks-git.nvim" = "1.5.1"
1616+"rocks-dev.nvim" = "1.2.3"
1717+"rocks-config.nvim" = "2.1.3"
1818+"rocks-edit.nvim" = "scm"
1919+"rocks-treesitter.nvim" = "scm"
2020+# harpoon uses plenary.nvim
2121+"plenary.nvim" = "scm"
2222+github-nvim-theme = "1.0.2"
2323+nvim-lspconfig = "0.1.8"
2424+"mason.nvim" = "1.10.0"
2525+"indent-blankline.nvim" = "3.7.1"
2626+"conform.nvim" = "6.0.0"
2727+"fidget.nvim" = "1.4.1"
2828+nvim-ufo = "1.4.0"
2929+luasnip = "2.3.0"
3030+neogit = "scm"
3131+"kanagawa.nvim" = "scm"
3232+"rest.nvim" = "2.0.1"
3333+"mini.ai" = "scm"
3434+nvim-surround = "2.1.5"
3535+nvim-treesitter-legacy-api = "0.9.2"
3636+fzf-lua = "0.0.1363"
3737+3838+tree-sitter-css = "scm"
3939+tree-sitter-ecma = "scm"
4040+tree-sitter-go = "scm"
4141+tree-sitter-html = "scm"
4242+tree-sitter-http = "scm"
4343+tree-sitter-javascript = "scm"
4444+tree-sitter-json = "scm"
4545+tree-sitter-jsx = "scm"
4646+tree-sitter-lua = "scm"
4747+tree-sitter-make = "scm"
4848+tree-sitter-query = "scm"
4949+tree-sitter-rust = "scm"
5050+tree-sitter-templ = "scm"
5151+tree-sitter-toml = "scm"
5252+tree-sitter-tsx = "scm"
5353+tree-sitter-typescript = "scm"
5454+tree-sitter-yaml = "scm"
5555+5656+"oil.nvim" = { git = "stevearc/oil.nvim", rev = "v2.9.0" }
5757+nvim-cmp = { git = "hrsh7th/nvim-cmp" }
5858+cmp-nvim-lsp = { git = "hrsh7th/cmp-nvim-lsp" }
5959+"leap.nvim" = { git = "ggandor/leap.nvim" }
6060+harpoon = { git = "ThePrimeagen/harpoon", branch = "harpoon2" }
6161+# for some plugins that needs legacy api (e.g. nvim-surround)
6262+nvim-treesitter-textobjects = { dir = "~/repo/nvim-treesitter-textobjects" }
6363+# neodev doesn't work when installed from luarocks
6464+# "neodev.nvim" = { git = "folke/neodev.nvim", rev = "v3.0.0" }
6565+nvim-lint = "scm"
6666+tree-sitter-gomod = "scm"
6767+tree-sitter-dockerfile = "scm"
6868+tree-sitter-regex = "scm"
6969+7070+[plugins."crates.nvim" ]
7171+git = "Saecki/crates.nvim"
7272+rev = "v0.5.1"
7373+7474+[plugins."gitsigns.nvim" ]
7575+git = "lewis6991/gitsigns.nvim"
7676+rev = "v0.9.0"
7777+7878+[config]
7979+auto_setup = false
8080+8181+[treesitter]
8282+auto_highlight = "all"
+4
todo.norg
···11+- (x) `ds'` to delete surrounding `'`
22+- ( ) `ds%` to delete any surrounding under cursor
33+- ( ) `:h 'matchpairs'`
44+- ( ) statusbar