commit 02e26b00b72aa19706d993938c1208a9fd4a184a Author: Jeremie Fraeys Date: Sat Feb 7 21:06:45 2026 -0500 chore(nvim): reinitialize with working config diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100755 index 0000000..2ad4d31 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + + + +## Describe the bug + + +## To Reproduce + +1. ... + +## Desktop + +- OS: +- Terminal: + +## Neovim Version + + +``` +``` diff --git a/.github/workflows/stylua.yaml b/.github/workflows/stylua.yaml new file mode 100644 index 0000000..309f8c2 --- /dev/null +++ b/.github/workflows/stylua.yaml @@ -0,0 +1,30 @@ +--- +# Check Lua Formatting +name: Check Lua Formatting in MyRepo + +on: + pull_request_target: + branches: + - main + push: + branches: + - main + +jobs: + stylua-check: + name: Stylua Check + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + steps: + - name: Checkout Code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Stylua Check + uses: JohnnyMorganz/stylua-action@v3 + with: + token: ${{ secrets.GITHUB_TOKEN }} + version: latest + args: --check . diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..cd00aaf --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +tags +test.sh +.luarc.json +nvim +lazy-lock.json +.DS_Store + diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..b4cde64 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a6218fe --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.stylua.toml b/.stylua.toml new file mode 100755 index 0000000..77c3370 --- /dev/null +++ b/.stylua.toml @@ -0,0 +1,10 @@ +column_width = 120 +line_endings = "Unix" +indent_type = "Spaces" +indent_width = 2 +call_parentheses = "Always" +quote_style = "AutoPreferSingle" +collapse_simple_statement = "Never" + +[sort_requires] +enabled = false diff --git a/LICENSE.md b/LICENSE.md new file mode 100755 index 0000000..9cf1062 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..49fa0f3 --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# kickstart.nvim + +https://github.com/kdheepak/kickstart.nvim/assets/1813121/f3ff9a2b-c31f-44df-a4fa-8a0d7b17cf7b + +### Introduction + +A starting point for Neovim that is: + +* Small +* Single-file (with examples of moving to multi-file) +* Documented +* Modular + +This repo is meant to be used by **YOU** to begin your Neovim journey; remove the things you don't use and add what you miss. + +Kickstart.nvim targets *only* the latest ['stable'](https://github.com/neovim/neovim/releases/tag/stable) and latest ['nightly'](https://github.com/neovim/neovim/releases/tag/nightly) of Neovim. If you are experiencing issues, please make sure you have the latest versions. + +Distribution Alternatives: +- [LazyVim](https://www.lazyvim.org/): A delightful distribution maintained by @folke (the author of lazy.nvim, the package manager used here) + +### Installation + +> **NOTE** +> [Backup](#FAQ) your previous configuration (if any exists) + +Requirements: +* Make sure to review the readmes of the plugins if you are experiencing errors. In particular: + * [ripgrep](https://github.com/BurntSushi/ripgrep#installation) is required for multiple [telescope](https://github.com/nvim-telescope/telescope.nvim#suggested-dependencies) pickers. +* See [Windows Installation](#Windows-Installation) if you have trouble with `telescope-fzf-native` + +Neovim's configurations are located under the following paths, depending on your OS: + +| OS | PATH | +| :- | :--- | +| Linux | `$XDG_CONFIG_HOME/nvim`, `~/.config/nvim` | +| MacOS | `$XDG_CONFIG_HOME/nvim`, '~/.config/nvim` | +| Windows | `%userprofile%\AppData\Local\nvim\` | + +Clone kickstart.nvim: + +```sh +# on Linux and Mac +git clone https://github.com/nvim-lua/kickstart.nvim.git "${XDG_CONFIG_HOME:-$HOME/.config}"/nvim +# on Windows +git clone https://github.com/nvim-lua/kickstart.nvim.git %userprofile%\AppData\Local\nvim\ +``` + +### Post Installation + +Run the following command and then **you are ready to go**! + +```sh +nvim --headless "+Lazy! sync" +qa +``` + +### Recommended Steps + +[Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this repo (so that you have your own copy that you can modify) and then installing you can install to your machine using the methods above. + +> **NOTE** +> Your fork's url will be something like this: `https://github.com//kickstart.nvim.git` + +### Configuration And Extension + +* Inside of your copy, feel free to modify any file you like! It's your copy! +* Feel free to change any of the default options in `init.lua` to better suit your needs. +* For adding plugins, there are 3 primary options: + * Add new configuration in `lua/custom/plugins/*` files, which will be auto sourced using `lazy.nvim` (uncomment the line importing the `custom/plugins` directory in the `init.lua` file to enable this) + * Modify `init.lua` with additional plugins. + * Include the `lua/kickstart/plugins/*` files in your configuration. + +You can also merge updates/changes from the repo back into your fork, to keep up-to-date with any changes for the default configuration. + +#### Example: Adding an autopairs plugin + +In the file: `lua/custom/plugins/autopairs.lua`, add: + +```lua +-- File: lua/custom/plugins/autopairs.lua + +return { + "windwp/nvim-autopairs", + -- Optional dependency + dependencies = { 'hrsh7th/nvim-cmp' }, + config = function() + require("nvim-autopairs").setup {} + -- If you want to automatically add `(` after selecting a function or method + local cmp_autopairs = require('nvim-autopairs.completion.cmp') + local cmp = require('cmp') + cmp.event:on( + 'confirm_done', + cmp_autopairs.on_confirm_done() + ) + end, +} +``` + + +This will automatically install [windwp/nvim-autopairs](https://github.com/windwp/nvim-autopairs) and enable it on startup. For more information, see documentation for [lazy.nvim](https://github.com/folke/lazy.nvim). + +#### Example: Adding a file tree plugin + +In the file: `lua/custom/plugins/filetree.lua`, add: + +```lua +-- Unless you are still migrating, remove the deprecated commands from v1.x +vim.cmd([[ let g:neo_tree_remove_legacy_commands = 1 ]]) + +return { + "nvim-neo-tree/neo-tree.nvim", + version = "*", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended + "MunifTanjim/nui.nvim", + }, + config = function () + require('neo-tree').setup {} + end, +} +``` + +This will install the tree plugin and add the command `:Neotree` for you. You can explore the documentation at [neo-tree.nvim](https://github.com/nvim-neo-tree/neo-tree.nvim) for more information. + +### Contribution + +Pull-requests are welcome. The goal of this repo is not to create a Neovim configuration framework, but to offer a starting template that shows, by example, available features in Neovim. Some things that will not be included: + +* Custom language server configuration (null-ls templates) +* Theming beyond a default colorscheme necessary for LSP highlight groups + +Each PR, especially those which increase the line count, should have a description as to why the PR is necessary. + +### FAQ + +* What should I do if I already have a pre-existing neovim configuration? + * You should back it up, then delete all files associated with it. + * This includes your existing init.lua and the neovim files in `~/.local` which can be deleted with `rm -rf ~/.local/share/nvim/` + * You may also want to look at the [migration guide for lazy.nvim](https://github.com/folke/lazy.nvim#-migration-guide) +* What if I want to "uninstall" this configuration: + * See [lazy.nvim uninstall](https://github.com/folke/lazy.nvim#-uninstalling) information +* Are there any cool videos about this plugin? + * Current iteration of kickstart (coming soon) + * Here is one about the previous iteration of kickstart: [video introduction to Kickstart.nvim](https://youtu.be/stqUbv-5u2s). Note the install via init.lua no longer works as specified. Please follow the install instructions in this file instead as they're up to date. + +### Windows Installation + +Installation may require installing build tools, and updating the run command for `telescope-fzf-native` + +See `telescope-fzf-native` documentation for [more details](https://github.com/nvim-telescope/telescope-fzf-native.nvim#installation) + +This requires: + +- Install CMake, and the Microsoft C++ Build Tools on Windows + +```lua +{'nvim-telescope/telescope-fzf-native.nvim', build = 'cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && cmake --build build --config Release && cmake --install build --prefix build' } +``` + diff --git a/after/queries/go/highlights.scm b/after/queries/go/highlights.scm new file mode 100644 index 0000000..4aa422f --- /dev/null +++ b/after/queries/go/highlights.scm @@ -0,0 +1,66 @@ +;; Custom highlights for Go +;; Place this in: ~/.config/nvim/after/queries/go/highlights.scm + +;; Highlight struct field names +(field_declaration + name: (field_identifier) @struct.field) + +;; Highlight function receivers in method declarations +(parameter_declaration + name: (identifier) @function.receiver + type: (pointer_type (type_identifier))) ;; Pointer receivers + +(parameter_declaration + name: (identifier) @function.receiver + type: (type_identifier)) ;; Value receivers + +;; Highlight type parameters (Go 1.18+ Generics) +; (type_parameter +; name: (type_identifier) @type.parameter) + +;; Better highlight for method calls +(selector_expression + field: (field_identifier) @method.call) + +;; Improved context highlighting for function parameters +(function_declaration + name: (identifier) @function.name + parameters: (parameter_list + (parameter_declaration + name: (identifier) @function.parameter))) + +;; Interface method names (fixed issue with invalid method_spec) +; (interface_type +; (field +; name: (field_identifier) @interface.method)) ;; Interface methods + +;; Highlight builtin functions +((identifier) @function.builtin + (#any-of? @function.builtin "append" "cap" "close" "complex" "copy" "delete" + "imag" "len" "make" "new" "panic" "print" "println" + "real" "recover")) + +;; Constants in ALL_CAPS (common Go convention) +((identifier) @constant + (#match? @constant "^[A-Z][A-Z0-9_]+$")) + +;; Error variables (by Go convention, starting with "Err") +((identifier) @variable.error + (#match? @variable.error "^Err[A-Z]\\w*")) + +;; Type declaration highlighting +(type_spec + name: (type_identifier) @type.definition) + +;; Enhance composite literal highlighting +(composite_literal + type: (_) @composite.type) + +;; Go build tags +(comment) @preproc.build + (#match? @preproc.build "^//go:build") + +;; Go generate directives +(comment) @preproc.generate + (#match? @preproc.generate "^//go:generate") + diff --git a/after/queries/go/textobjects.scm b/after/queries/go/textobjects.scm new file mode 100644 index 0000000..0fc0105 --- /dev/null +++ b/after/queries/go/textobjects.scm @@ -0,0 +1,99 @@ +;; extends +; Custom text objects for Go +; Place this in: ~/.config/nvim/after/queries/go/textobjects.scm + +; Function text objects +(function_declaration) @function.outer + +(function_declaration + body: (block) @function.inner) + +; Method text objects +(method_declaration) @function.outer + +(method_declaration + body: (block) @function.inner) + +; Function literal (closure) text objects +(func_literal) @function.outer + +(func_literal + body: (block) @function.inner) + +; Class text objects (structs, interfaces) +(type_declaration) @class.outer + +; (struct_type +; "{" "}" @class.inner) + +(interface_type + "{" "}" @class.inner) + +; Parameter text objects +(parameter_list) @parameter.outer + +(parameter_list + "(" . (_) @_start (_)? @_end . ")" + (#make-range! "parameter.inner" @_start @_end)) + +; Argument text objects +(argument_list) @parameter.outer + +(argument_list + "(" . (_) @_start (_)? @_end . ")" + (#make-range! "parameter.inner" @_start @_end)) + +; Comment text objects +(comment) @comment.outer + +; Block text objects +(block) @block.outer + +(block + "{" . (_) @_start (_)? @_end . "}" + (#make-range! "block.inner" @_start @_end)) + +; Statement text objects +; (simple_statement) @statement.outer +(expression_statement) @statement.outer +(if_statement) @statement.outer +(for_statement) @statement.outer +; (switch_statement) @statement.outer +(select_statement) @statement.outer +(return_statement) @statement.outer +(defer_statement) @statement.outer +(go_statement) @statement.outer + +; Conditional text objects +(if_statement) @conditional.outer + +; (if_statement +; body: (block) @conditional.inner) + +; Loop text objects +(for_statement) @loop.outer + +(for_statement + body: (block) @loop.inner) + +; Call text objects +(call_expression) @call.outer + +(call_expression + arguments: (argument_list) @call.inner) + +; Assignment text objects +(assignment_statement) @assignment.outer +(short_var_declaration) @assignment.outer + +; Import text objects +(import_declaration) @import.outer + +(import_spec_list) @import.inner + +; Package text objects +(package_clause) @package.outer + +; Attribute/field text objects +(field_declaration) @attribute.outer + diff --git a/after/queries/markdown/textobjects.scm b/after/queries/markdown/textobjects.scm new file mode 100644 index 0000000..d3cfccc --- /dev/null +++ b/after/queries/markdown/textobjects.scm @@ -0,0 +1,15 @@ +; (atx_heading +; heading_content: (_) @class.inner) @class.outer +; +; (setext_heading +; heading_content: (_) @class.inner) @class.outer +; +; (thematic_break) @class.outer + +(fenced_code_block (code_fence_content) @block.inner) @block.outer + +[ + (paragraph) + (list) +] @block.outer + diff --git a/after/queries/markdown_inline/highlights.scm b/after/queries/markdown_inline/highlights.scm new file mode 100644 index 0000000..445b09e --- /dev/null +++ b/after/queries/markdown_inline/highlights.scm @@ -0,0 +1,13 @@ +;extends + +[ + (shortcut_link) +] @nospell + +(strikethrough +(emphasis_delimiter) +(strikethrough + (emphasis_delimiter) + (emphasis_delimiter)) +(emphasis_delimiter))@markup.doublestrikethrough + diff --git a/after/queries/norg/injections.scm b/after/queries/norg/injections.scm new file mode 100644 index 0000000..fce214a --- /dev/null +++ b/after/queries/norg/injections.scm @@ -0,0 +1,12 @@ +; Injection for code blocks +(ranged_verbatim_tag (tag_name) @_tagname (tag_parameters .(tag_param) @injection.language) (ranged_verbatim_tag_content) @injection.content (#any-of? @_tagname "code" "embed")) +(ranged_verbatim_tag (tag_name) @_tagname (tag_parameters)? (ranged_verbatim_tag_content) @injection.content (#eq? @_tagname "math") (#set! injection.language "latex")) + +( + (inline_math) @injection.content + (#offset! @injection.content 0 1 0 -1) + (#set! injection.language "latex") +) + +(ranged_verbatim_tag (tag_name) @_tagname (ranged_verbatim_tag_content) @injection.content (#eq? @_tagname "document.meta") (#set! injection.language "norg_meta")) + diff --git a/after/queries/python/highlights.scm b/after/queries/python/highlights.scm new file mode 100644 index 0000000..db7a7b0 --- /dev/null +++ b/after/queries/python/highlights.scm @@ -0,0 +1,12 @@ +;extends +( +(comment) @comment +(#match? @comment "^\\#\\|") +) @text.literal + + +( +(comment) @content +(#match? @content "^\\# ?\\%\\%") +) @class.outer @text.literal + diff --git a/after/queries/python/textobjects.scm b/after/queries/python/textobjects.scm new file mode 100644 index 0000000..c5ec144 --- /dev/null +++ b/after/queries/python/textobjects.scm @@ -0,0 +1,7 @@ +;extends + +( +(comment) @content1 +(#match? @content1 "^\\# ?\\%\\%") +) @class.inner + diff --git a/after/queries/r/textobjects.scm b/after/queries/r/textobjects.scm new file mode 100644 index 0000000..2b834de --- /dev/null +++ b/after/queries/r/textobjects.scm @@ -0,0 +1,50 @@ +; block +; call +(call) @call.outer + +(arguments) @call.inner + +; class +; comment +(comment) @comment.outer + +; conditional +(if_statement + condition: (_)? @conditional.inner) @conditional.outer + +; function +[ + (function_definition) +] @function.outer + +(function_definition + [ + (call) + (binary_operator) + ] @function.inner) @function.outer + + +; loop +[ + (while_statement) + (for_statement) + (repeat_statement) +] @loop.outer + +(while_statement + body: (_) @loop.inner) + +(repeat_statement + body: (_) @loop.inner) + +(for_statement + body: (_) @loop.inner) + +; statement + +(program + (_) @statement.outer) + +; number +(float) @number.inner + diff --git a/after/queries/rust/injections.scm b/after/queries/rust/injections.scm new file mode 100644 index 0000000..9930853 --- /dev/null +++ b/after/queries/rust/injections.scm @@ -0,0 +1,15 @@ +;extends +(macro_invocation +(scoped_identifier +path: (identifier) @path (#eq? @path "sqlx") +name: (identifier) @name (#match? @name "^query.*") +) + +(token_tree +(raw_string_literal) @injection.content +(#set! injection.language "sql") +(#set! injection.include-children) +) +(#offset! @injection.content 0 3 0 -2) +) + diff --git a/ftplugin/c.lua b/ftplugin/c.lua new file mode 100644 index 0000000..995f5b7 --- /dev/null +++ b/ftplugin/c.lua @@ -0,0 +1,196 @@ +-- Indentation settings +vim.bo.expandtab = true -- Use spaces instead of tabs +vim.bo.tabstop = 4 -- Number of spaces per tab +vim.bo.shiftwidth = 4 -- Number of spaces for autoindent +vim.bo.softtabstop = 4 -- Number of spaces for editing +vim.bo.formatoptions = vim.bo.formatoptions:gsub('o', '') -- Remove 'o' + +-- Format on save using Conform +local augroup = vim.api.nvim_create_augroup('CFormat', { clear = true }) +vim.api.nvim_create_autocmd('BufWritePre', { + group = augroup, + pattern = '*.c', + callback = function() + if vim.fn.exists(':ConformFormat') == 2 then + vim.cmd('ConformFormat') + end + end, +}) + +local function cmake_build_dir(name) + local dir = vim.fn.getcwd() .. '/' .. (name or 'build') + if vim.fn.isdirectory(dir) == 0 then + vim.fn.mkdir(dir, 'p') + end + return dir +end + +local function cmake_configure(build_dir, extra_args) + local args = extra_args or '' + local bin_dir = vim.fn.getcwd() .. '/bin' + vim.fn.mkdir(bin_dir, 'p') + local out_args = + ' -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=' .. vim.fn.shellescape(bin_dir) .. ' -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + .. vim.fn.shellescape(bin_dir) + .. ' -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=' + .. vim.fn.shellescape(bin_dir) + vim.cmd('!cmake -S . -B ' .. vim.fn.fnameescape(build_dir) .. out_args .. ' ' .. args) +end + +local function cmake_build(build_dir) + vim.cmd('!cmake --build ' .. vim.fn.fnameescape(build_dir)) +end + +local function cmake_ctest(build_dir) + vim.cmd('!ctest --test-dir ' .. vim.fn.fnameescape(build_dir) .. ' --output-on-failure') +end + +local function toggle_debugger_repl() + local dbg = vim.__c_dbg_term + if dbg and dbg.is_open and dbg:is_open() then + dbg:close() + return + end + + local ok_term, term_mod = pcall(require, 'toggleterm.terminal') + if not ok_term then + return + end + + local cmd + if vim.fn.has('mac') == 1 then + cmd = 'lldb' + else + cmd = 'gdb' + end + + if not vim.__c_dbg_term then + vim.__c_dbg_term = term_mod.Terminal:new({ + cmd = cmd, + direction = 'vertical', + size = function() + return math.floor(vim.o.columns * 0.4) + end, + hidden = true, + on_open = function(term) + vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', 'close', { noremap = true, silent = true }) + vim.api.nvim_buf_set_keymap(term.bufnr, 't', '', '', { noremap = true, silent = true }) + end, + }) + end + + vim.__c_dbg_term:open() + vim.cmd('wincmd p') +end + +local opts = { buffer = true, silent = true } + +local function switch_source_header() + local bufnr = vim.api.nvim_get_current_buf() + local function edit_file(fname) + if not fname or fname == '' then + return + end + vim.cmd('edit ' .. vim.fn.fnameescape(fname)) + end + + local clangd + for _, client in ipairs(vim.lsp.get_clients({ bufnr = bufnr })) do + if client.name == 'clangd' then + clangd = client + break + end + end + + if clangd then + clangd.request('textDocument/switchSourceHeader', { uri = vim.uri_from_bufnr(bufnr) }, function(err, result) + if err then + vim.notify('clangd: switchSourceHeader failed', vim.log.levels.WARN) + return + end + if not result then + vim.notify('No corresponding header/source found', vim.log.levels.INFO) + return + end + vim.schedule(function() + edit_file(vim.uri_to_fname(result)) + end) + end, bufnr) + return + end + + local file = vim.api.nvim_buf_get_name(bufnr) + if file == '' then + return + end + + local root = file:gsub('%.[^%.]+$', '') + local ext = vim.fn.fnamemodify(file, ':e') + local candidates + + if ext == 'h' or ext == 'hpp' or ext == 'hh' or ext == 'hxx' then + candidates = { 'c', 'cpp', 'cc', 'cxx' } + elseif ext == 'c' then + candidates = { 'h' } + else + candidates = { 'h', 'hpp', 'hh', 'hxx' } + end + + for _, e in ipairs(candidates) do + local cand = root .. '.' .. e + if vim.fn.filereadable(cand) == 1 then + edit_file(cand) + return + end + end + + vim.notify('No corresponding header/source found', vim.log.levels.INFO) +end + +local function memory_profile_command(run_cmd) + if vim.fn.has('mac') == 1 then + return 'leaks --atExit -- ' .. run_cmd + end + return 'valgrind --leak-check=full --track-origins=yes ' .. run_cmd +end + +vim.keymap.set('n', 'cb', function() + local build_dir = cmake_build_dir('build') + cmake_configure(build_dir) + cmake_build(build_dir) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Build' })) + +vim.keymap.set('n', 'ct', function() + local build_dir = cmake_build_dir('build') + cmake_ctest(build_dir) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Test' })) + +vim.keymap.set('n', 'ch', switch_source_header, vim.tbl_extend('force', opts, { desc = 'C: Switch header/source' })) + +vim.keymap.set('n', 'cg', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'C: Toggle debugger REPL' })) + +vim.keymap.set('n', 'cm', function() + local cmd = vim.fn.input('Memory profile run: ', './bin/') + if cmd == '' then + return + end + vim.cmd('!' .. memory_profile_command(cmd)) +end, vim.tbl_extend('force', opts, { desc = 'C: Memory profile (leaks/valgrind)' })) + +vim.keymap.set('n', 'Rr', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Toggle' })) + +vim.keymap.set('n', 'Rf', function() + local cmd = vim.fn.input('Run: ', './bin/') + if cmd == '' then + return + end + vim.cmd('!' .. cmd) +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Run' })) + +vim.keymap.set('n', 'Ri', function() + local dbg = vim.__c_dbg_term + if dbg and dbg.send and dbg.is_open and dbg:is_open() then + dbg:send('\x03') + end +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Interrupt' })) + diff --git a/ftplugin/cpp.lua b/ftplugin/cpp.lua new file mode 100644 index 0000000..4e97f87 --- /dev/null +++ b/ftplugin/cpp.lua @@ -0,0 +1,256 @@ +vim.opt_local.cinoptions = vim.opt_local.cinoptions + '0{1' -- Places opening brace on the same line as function signature +vim.opt_local.smartindent = true -- Automatically adds indentation where needed +vim.opt_local.tabstop = 4 -- Set tab stop to 4 spaces +vim.opt_local.shiftwidth = 4 -- Set shift width to 4 spaces +vim.opt_local.softtabstop = 4 -- Use spaces instead of tabs + +vim.api.nvim_create_user_command('RunCpp', function(opts) + vim.cmd('w') -- Save the current file + local filename = vim.fn.expand('%') -- Get current file name + local output = 'bin/' .. vim.fn.expand('%:r'):match('([^/]+)$') .. '.out' -- Output file with .out extension + + -- Use provided compiler or default to 'clang++' + local compiler = opts.args ~= '' and opts.args or 'clang++' + + -- Ensure the output directory exists + vim.fn.mkdir('bin', 'p') + + -- Compile and run the C++ code + vim.cmd('!' .. compiler .. ' ' .. filename .. ' -o ' .. output .. ' && ./' .. output) +end, { nargs = '?' }) -- Accepts one optional argument for the compiler + +local function cmake_build_dir(name) + local dir = vim.fn.getcwd() .. '/' .. (name or 'build') + if vim.fn.isdirectory(dir) == 0 then + vim.fn.mkdir(dir, 'p') + end + return dir +end + +local function cmake_configure(build_dir, extra_args) + local args = extra_args or '' + local bin_dir = vim.fn.getcwd() .. '/bin' + vim.fn.mkdir(bin_dir, 'p') + local out_args = + ' -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=' .. vim.fn.shellescape(bin_dir) .. ' -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + .. vim.fn.shellescape(bin_dir) + .. ' -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY=' + .. vim.fn.shellescape(bin_dir) + vim.cmd('!cmake -S . -B ' .. vim.fn.fnameescape(build_dir) .. out_args .. ' ' .. args) +end + +local function cmake_build(build_dir, extra_args) + local args = extra_args or '' + vim.cmd('!cmake --build ' .. vim.fn.fnameescape(build_dir) .. ' ' .. args) +end + +local function cmake_ctest(build_dir) + vim.cmd('!ctest --test-dir ' .. vim.fn.fnameescape(build_dir) .. ' --output-on-failure') +end + +local function select_cmake_preset() + local lines = vim.fn.systemlist('cmake --list-presets') + local presets = {} + for _, line in ipairs(lines) do + local name = line:match('^%s*"([^"]+)"') + if name then + table.insert(presets, name) + end + end + if #presets == 0 then + return + end + vim.ui.select(presets, { prompt = 'CMake preset' }, function(choice) + if choice then + vim.g.cmake_preset = choice + end + end) +end + +local function cmake_configure_with_preset() + local preset = vim.g.cmake_preset + if preset and preset ~= '' then + vim.cmd('!cmake --preset ' .. preset) + return true + end + return false +end + +local function cmake_build_with_preset() + local preset = vim.g.cmake_preset + if preset and preset ~= '' then + vim.cmd('!cmake --build --preset ' .. preset) + return true + end + return false +end + +local function toggle_debugger_repl() + local dbg = vim.__cpp_dbg_term + if dbg and dbg.is_open and dbg:is_open() then + dbg:close() + return + end + + local ok_term, term_mod = pcall(require, 'toggleterm.terminal') + if not ok_term then + return + end + + local cmd + if vim.fn.has('mac') == 1 then + cmd = 'lldb' + else + cmd = 'gdb' + end + + if not vim.__cpp_dbg_term then + vim.__cpp_dbg_term = term_mod.Terminal:new({ + cmd = cmd, + direction = 'vertical', + size = function() + return math.floor(vim.o.columns * 0.4) + end, + hidden = true, + on_open = function(term) + vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', 'close', { noremap = true, silent = true }) + vim.api.nvim_buf_set_keymap(term.bufnr, 't', '', '', { noremap = true, silent = true }) + end, + }) + end + + vim.__cpp_dbg_term:open() + vim.cmd('wincmd p') +end + +local opts = { buffer = true, silent = true } + +local function switch_source_header() + local bufnr = vim.api.nvim_get_current_buf() + local function edit_file(fname) + if not fname or fname == '' then + return + end + vim.cmd('edit ' .. vim.fn.fnameescape(fname)) + end + + local clangd + for _, client in ipairs(vim.lsp.get_clients({ bufnr = bufnr })) do + if client.name == 'clangd' then + clangd = client + break + end + end + + if clangd then + clangd.request('textDocument/switchSourceHeader', { uri = vim.uri_from_bufnr(bufnr) }, function(err, result) + if err then + vim.notify('clangd: switchSourceHeader failed', vim.log.levels.WARN) + return + end + if not result then + vim.notify('No corresponding header/source found', vim.log.levels.INFO) + return + end + vim.schedule(function() + edit_file(vim.uri_to_fname(result)) + end) + end, bufnr) + return + end + + local file = vim.api.nvim_buf_get_name(bufnr) + if file == '' then + return + end + + local root = file:gsub('%.[^%.]+$', '') + local ext = vim.fn.fnamemodify(file, ':e') + local candidates + + if ext == 'h' or ext == 'hpp' or ext == 'hh' or ext == 'hxx' then + candidates = { 'cpp', 'cc', 'cxx', 'c' } + elseif ext == 'c' then + candidates = { 'h' } + else + candidates = { 'h', 'hpp', 'hh', 'hxx' } + end + + for _, e in ipairs(candidates) do + local cand = root .. '.' .. e + if vim.fn.filereadable(cand) == 1 then + edit_file(cand) + return + end + end + + vim.notify('No corresponding header/source found', vim.log.levels.INFO) +end + +local function memory_profile_command(run_cmd) + if vim.fn.has('mac') == 1 then + return 'leaks --atExit -- ' .. run_cmd + end + return 'valgrind --leak-check=full --track-origins=yes ' .. run_cmd +end + +vim.keymap.set('n', 'cS', select_cmake_preset, vim.tbl_extend('force', opts, { desc = 'CMake: Select preset' })) + +vim.keymap.set('n', 'cb', function() + if cmake_configure_with_preset() then + cmake_build_with_preset() + return + end + local build_dir = cmake_build_dir('build') + cmake_configure(build_dir) + cmake_build(build_dir) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Build' })) + +vim.keymap.set('n', 'ct', function() + local build_dir = cmake_build_dir('build') + cmake_ctest(build_dir) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Test' })) + +vim.keymap.set('n', 'cr', function() + local cmd = vim.fn.input('Run command: ', './') + if cmd == '' then + return + end + vim.cmd('!' .. cmd) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Run (prompt)' })) + +vim.keymap.set('n', 'ch', switch_source_header, vim.tbl_extend('force', opts, { desc = 'C++: Switch header/source' })) + +vim.keymap.set('n', 'cg', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'C++: Toggle debugger REPL' })) + +vim.keymap.set('n', 'cm', function() + local cmd = vim.fn.input('Memory profile run: ', './bin/') + if cmd == '' then + return + end + vim.cmd('!' .. memory_profile_command(cmd)) +end, vim.tbl_extend('force', opts, { desc = 'C++: Memory profile (leaks/valgrind)' })) + +vim.keymap.set('n', 'Rr', toggle_debugger_repl, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Toggle' })) + +vim.keymap.set('n', 'Rf', function() + local cmd = vim.fn.input('Run: ', './bin/') + if cmd == '' then + return + end + vim.cmd('!' .. cmd) +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Run' })) + +vim.keymap.set('n', 'Ri', function() + local dbg = vim.__cpp_dbg_term + if dbg and dbg.send and dbg.is_open and dbg:is_open() then + dbg:send('\x03') + end +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Interrupt' })) + +vim.keymap.set('n', 'ca', function() + local build_dir = cmake_build_dir('build-asan') + local flags = "-DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS='-fsanitize=address -fno-omit-frame-pointer' -DCMAKE_CXX_FLAGS='-fsanitize=address -fno-omit-frame-pointer'" + cmake_configure(build_dir, flags) + cmake_build(build_dir) +end, vim.tbl_extend('force', opts, { desc = 'CMake: Build (ASan)' })) diff --git a/ftplugin/crontab.lua b/ftplugin/crontab.lua new file mode 100644 index 0000000..d152a34 --- /dev/null +++ b/ftplugin/crontab.lua @@ -0,0 +1 @@ +vim.opt_local.fixeol = false diff --git a/ftplugin/dockerfile.lua b/ftplugin/dockerfile.lua new file mode 100644 index 0000000..7307b61 --- /dev/null +++ b/ftplugin/dockerfile.lua @@ -0,0 +1,2 @@ +vim.opt_local.expandtab = true +vim.opt_local.shiftwidth = 2 diff --git a/ftplugin/go.lua b/ftplugin/go.lua new file mode 100644 index 0000000..b393de1 --- /dev/null +++ b/ftplugin/go.lua @@ -0,0 +1,35 @@ +-- Set buffer-local options for Go files +vim.bo.expandtab = true -- Use spaces instead of tabs +vim.opt_local.shiftwidth = 4 -- Indent width +vim.opt_local.tabstop = 4 -- Tab width +vim.opt_local.softtabstop = 4 -- Soft tab width for alignment + +vim.bo.commentstring = '// %s' -- Comment format for Go +vim.opt_local.comments = 's1:/*,mb:*,ex:*/,://' + +-- Define a buffer-local key mapping to trigger Go debugging +vim.keymap.set('n', 'dd', function() + require('dap-go').debug_test() +end, { buffer = 0, desc = 'Debug Go Test' }) -- Buffer-local key mapping + +-- Load nvim-dap-go and set up configurations +require('dap-go').setup() + +-- Go DAP configuration +local dap = require('dap') + +-- Define Go DAP configurations +dap.configurations.go = { + { + type = 'go', + name = 'Launch File', + request = 'launch', + program = '${file}', + }, + { + type = 'go', + name = 'Launch Package', + request = 'launch', + program = '${workspaceFolder}', + }, +} diff --git a/ftplugin/json.lua b/ftplugin/json.lua new file mode 100644 index 0000000..c0d216b --- /dev/null +++ b/ftplugin/json.lua @@ -0,0 +1,16 @@ +-- Use 2 spaces for indentation +vim.bo.shiftwidth = 2 +vim.bo.tabstop = 2 +vim.bo.expandtab = true + +vim.bo.textwidth = 100 +vim.wo.wrap = false + +-- Enable spell-check for JSON comments +vim.wo.spell = true + +-- Enable Folding for JSON +vim.wo.foldmethod = 'syntax' + +-- Set conceal level for better JSON readability (e.g., hiding quotes) +vim.wo.conceallevel = 2 diff --git a/ftplugin/julia.lua b/ftplugin/julia.lua new file mode 100644 index 0000000..9fefebd --- /dev/null +++ b/ftplugin/julia.lua @@ -0,0 +1,163 @@ +local ok_conform, conform = pcall(require, 'conform') +if ok_conform then + conform.formatters = conform.formatters or {} + conform.formatters_by_ft = conform.formatters_by_ft or {} + + conform.formatters_by_ft.julia = { 'juliaformatter' } + + vim.api.nvim_create_autocmd('BufWritePre', { + buffer = 0, + callback = function() + if vim.api.nvim_buf_line_count(0) > 2000 then + return + end + conform.format({ timeout_ms = 2000, lsp_fallback = false }) + end, + }) +end + +if not vim.g.julia_repl_setup_done then + vim.g.julia_repl_setup_done = true + + local ok_toggleterm, toggleterm_terminal = pcall(require, 'toggleterm.terminal') + if not ok_toggleterm then + return + end + + local Terminal = toggleterm_terminal.Terminal + + local function get_startup_commands() + return [[ +ENV["JULIA_NOVERSIONS"] = "yes" +try + using Revise +catch +end +println("\033[2J\033[H") +]] + end + + local function new_julia_terminal() + return Terminal:new({ + cmd = 'julia --banner=no', + direction = 'vertical', + size = 80, + hidden = true, + on_open = function(term) + vim.defer_fn(function() + term:send(get_startup_commands()) + end, 100) + + vim.api.nvim_buf_set_keymap(term.bufnr, 'n', 'q', 'close', { noremap = true, silent = true }) + vim.api.nvim_buf_set_keymap(term.bufnr, 't', '', '', { noremap = true, silent = true }) + + local win = term.window or vim.api.nvim_get_current_win() + vim.api.nvim_set_option_value('signcolumn', 'no', { win = win }) + vim.api.nvim_set_option_value('number', false, { win = win }) + vim.api.nvim_set_option_value('relativenumber', false, { win = win }) + end, + on_exit = function(_) + vim.cmd('stopinsert') + end, + }) + end + + vim.__julia_repl_terminal = new_julia_terminal() + + vim.api.nvim_create_user_command('JuliaREPL', function() + local julia = vim.__julia_repl_terminal + if not julia then + vim.__julia_repl_terminal = new_julia_terminal() + julia = vim.__julia_repl_terminal + end + + if julia:is_open() then + julia:close() + else + julia:open() + vim.cmd('wincmd p') + end + end, {}) + + vim.keymap.set('n', 'Rr', 'JuliaREPL', { noremap = true, silent = true, desc = 'Run/REPL: Toggle' }) +end + +local function send_to_julia(code) + local julia = vim.__julia_repl_terminal + if not julia then + return + end + + if not julia:is_open() then + julia:open() + vim.defer_fn(function() + julia:send(code) + vim.cmd('wincmd p') + end, 200) + else + julia:send(code) + vim.cmd('wincmd p') + end +end + +local function get_cell_content() + local cur_line = vim.api.nvim_win_get_cursor(0)[1] + local lines = vim.api.nvim_buf_get_lines(0, 0, -1, false) + local start_line, end_line = cur_line, cur_line + + while start_line > 1 and not lines[start_line - 1]:match('^#%%') and not lines[start_line - 1]:match('^##') do + start_line = start_line - 1 + end + + while end_line < #lines and not lines[end_line + 1]:match('^#%%') and not lines[end_line + 1]:match('^##') do + end_line = end_line + 1 + end + + return table.concat(vim.api.nvim_buf_get_lines(0, start_line - 1, end_line, false), '\n') +end + +local opts = { buffer = true, silent = true } + +vim.keymap.set('n', 'Rf', function() + send_to_julia('using Revise\nincludet("' .. vim.fn.expand('%:p') .. '")\n') +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Run file' })) + +vim.keymap.set('n', 'Rl', function() + send_to_julia(vim.api.nvim_get_current_line() .. '\n') +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Send line' })) + +vim.keymap.set('n', 'Rc', function() + send_to_julia(get_cell_content() .. '\n') +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Send cell' })) + +vim.keymap.set('v', 'Rs', function() + local start_pos = vim.fn.getpos("'<")[2] + local end_pos = vim.fn.getpos("'>")[2] + local code = table.concat(vim.api.nvim_buf_get_lines(0, start_pos - 1, end_pos, false), '\n') + send_to_julia(code .. '\n') +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Send selection' })) + +vim.keymap.set('n', 'Ri', function() + send_to_julia('\x03') +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Interrupt' })) + +vim.keymap.set('n', 'jb', function() + send_to_julia('using BenchmarkTools\n@btime ' .. vim.api.nvim_get_current_line() .. '\n') +end, vim.tbl_extend('force', opts, { desc = 'Julia: Benchmark line' })) + +vim.keymap.set('n', 'Rx', function() + local julia = vim.__julia_repl_terminal + if julia then + pcall(function() + julia:shutdown() + end) + end + vim.__julia_repl_terminal = Terminal:new({ + cmd = 'julia --banner=no', + direction = 'vertical', + size = 80, + hidden = true, + }) +end, vim.tbl_extend('force', opts, { desc = 'Run/REPL: Restart' })) + +vim.g.julia_ftplugin_migrated = true diff --git a/ftplugin/lua.lua b/ftplugin/lua.lua new file mode 100644 index 0000000..653d1c7 --- /dev/null +++ b/ftplugin/lua.lua @@ -0,0 +1,9 @@ +vim.opt.expandtab = true +vim.opt.shiftwidth = 2 +vim.opt.tabstop = 2 +vim.opt.softtabstop = 2 + +vim.opt_local.formatoptions:remove('o') + +vim.opt_local.commentstring = '-- %s' +vim.opt_local.colorcolumn = '120' diff --git a/ftplugin/make.lua b/ftplugin/make.lua new file mode 100644 index 0000000..7f1e5ef --- /dev/null +++ b/ftplugin/make.lua @@ -0,0 +1,24 @@ +-- Use tabs for indentation (Makefiles require tabs in rules) +vim.bo.expandtab = false +vim.bo.shiftwidth = 4 +vim.bo.tabstop = 4 +vim.bo.softtabstop = 4 + +-- Show trailing spaces and tabs explicitly for visibility +vim.wo.list = true +vim.opt.listchars:append({ tab = '→ ', trail = '·' }) + +-- Disable automatic comment continuation +vim.opt_local.formatoptions:remove('o') + +-- Highlight spaces before tabs (helps detect incorrect indentation) +vim.cmd([[ match Error /^\s\+\t/ ]]) + +-- Enable line numbers for better navigation +vim.wo.number = true + +-- Disable spell checking for Makefiles +vim.wo.spell = false + +-- Set conceal level to 0 (Makefiles don't need concealment) +vim.wo.conceallevel = 0 diff --git a/ftplugin/markdown.lua b/ftplugin/markdown.lua new file mode 100644 index 0000000..6d05a3c --- /dev/null +++ b/ftplugin/markdown.lua @@ -0,0 +1,101 @@ +vim.opt_local.textwidth = 80 +vim.opt_local.wrap = true +vim.opt_local.linebreak = true + +-- Update PATH to include TeX binaries +vim.env.PATH = vim.env.PATH .. ':/Library/TeX/texbin' + +local function ensure_directory_exists(path) + if not vim.fn.isdirectory(path) then + vim.fn.mkdir(path, 'p') + end +end + +local function generate_pdf_paths(filename) + local base_dir = vim.fn.fnamemodify(filename, ':h') + local target_dir = base_dir:gsub('/src$', '/pdf') + local output_file = vim.fn.fnamemodify(filename, ':t:r') .. '.pdf' + local output_path = target_dir .. '/' .. output_file + + ensure_directory_exists(target_dir) + + return target_dir, output_file, output_path +end + +local function build_pdf(filename, output_path) + local command = string.format('buildnote %s %s', vim.fn.shellescape(filename), vim.fn.shellescape(output_path)) + return vim.fn.systemlist(command) +end + +local function open_pdf_in_zathura(pdf_path) + local zathura_running = vim.fn.systemlist('pgrep -f "zathura ' .. vim.fn.shellescape(pdf_path) .. '"') + + if #zathura_running == 0 then + vim.fn.jobstart({ 'zathura', pdf_path }, { detach = true, stdout = 'null', stderr = 'null' }) + print('Opening PDF in Zathura: ' .. pdf_path) + else + print('Zathura is already running for this file.') + end +end + +local function close_zathura() + local _, _, output_path = generate_pdf_paths(vim.fn.expand('%:p')) + vim.fn.system({ 'pkill', '-f', 'zathura ' .. output_path }) +end + +vim.keymap.set('n', 'Rv', function() + local filename = vim.fn.expand('%:p') + local _, _, output_path = generate_pdf_paths(filename) + + local result = build_pdf(filename, output_path) + + if #result == 0 then + print('Error: Could not generate PDF.') + return + end + + open_pdf_in_zathura(output_path) +end, { + desc = 'View output PDF in Zathura', + noremap = true, + silent = true, +}) + +vim.keymap.set('n', 'Rc', close_zathura, { + desc = 'Close Zathura instance for the current PDF', + noremap = true, + silent = true, +}) + +vim.keymap.set('n', 'Rb', function() + local filename = vim.fn.expand('%:p') + local _, _, output_path = generate_pdf_paths(filename) + + local result = build_pdf(filename, output_path) + + if #result == 0 then + print('Error: Could not generate PDF.') + return + end + + print('PDF generated at: ' .. output_path) +end, { + desc = 'Build PDF', + noremap = true, + silent = true, +}) + +vim.api.nvim_create_autocmd('BufWritePost', { + pattern = '*note-*.md', + callback = function() + local filename = vim.fn.expand('%:p') + local _, _, output_path = generate_pdf_paths(filename) + + if vim.fn.filereadable(filename) == 1 then + local command = string.format('buildnote %s %s', vim.fn.shellescape(filename), vim.fn.shellescape(output_path)) + vim.cmd('silent !' .. command) + else + print('Error: File ' .. filename .. ' does not exist.') + end + end, +}) diff --git a/ftplugin/ocaml.lua b/ftplugin/ocaml.lua new file mode 100644 index 0000000..90bbd0a --- /dev/null +++ b/ftplugin/ocaml.lua @@ -0,0 +1,4 @@ +vim.opt.shiftwidth = 2 + +-- vim.keymap.set('n', 'cp', require('ocaml.mappings').dune_promote_file, { buffer = 0 }) +-- diff --git a/ftplugin/python.lua b/ftplugin/python.lua new file mode 100644 index 0000000..ca6b810 --- /dev/null +++ b/ftplugin/python.lua @@ -0,0 +1,145 @@ +-- Set buffer-local options for Python files +vim.opt_local.expandtab = true -- Use spaces instead of tabs +vim.opt_local.commentstring = '# %s' -- Python comment format +vim.opt_local.shiftwidth = 4 -- Indent width +vim.opt_local.tabstop = 4 -- Tab width +vim.opt_local.softtabstop = 4 -- Soft tab width for alignment +vim.opt_local.fileformat = 'unix' -- Use Unix line endings +vim.opt_local.textwidth = 79 -- Maximum text width +vim.opt_local.colorcolumn = '80' -- Highlight column 80 + +local dap = require('dap') + +local function get_python_path() + local cwd = vim.fn.getcwd() + if vim.env.VIRTUAL_ENV then + return vim.env.VIRTUAL_ENV .. '/bin/python' -- Use virtual environment if set + elseif vim.fn.executable(cwd .. '/venv/bin/python') == 1 then + return cwd .. '/venv/bin/python' -- Use project-specific venv + elseif vim.fn.executable(cwd .. '/.venv/bin/python') == 1 then + return cwd .. '/.venv/bin/python' -- Use hidden project venv + else + return 'python' -- Fallback to system Python + end +end + +local function get_ipython_cmd() + local cwd = vim.fn.getcwd() + local venv = vim.env.VIRTUAL_ENV + if not venv then + if vim.fn.isdirectory(cwd .. '/.venv') == 1 then + venv = cwd .. '/.venv' + elseif vim.fn.isdirectory(cwd .. '/venv') == 1 then + venv = cwd .. '/venv' + end + end + + if venv and vim.fn.executable(venv .. '/bin/ipython') == 1 then + return vim.fn.shellescape(venv .. '/bin/ipython') + end + + local py = get_python_path() + if py:find('/') then + py = vim.fn.shellescape(py) + end + return py .. ' -m IPython' +end + +local function get_ipython_argv() + local cwd = vim.fn.getcwd() + local venv = vim.env.VIRTUAL_ENV + if not venv then + if vim.fn.isdirectory(cwd .. '/.venv') == 1 then + venv = cwd .. '/.venv' + elseif vim.fn.isdirectory(cwd .. '/venv') == 1 then + venv = cwd .. '/venv' + end + end + + if venv and vim.fn.executable(venv .. '/bin/ipython') == 1 then + return { venv .. '/bin/ipython' } + end + + return { get_python_path(), '-m', 'IPython' } +end + +vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('lsp_attach_disable_ruff_hover', { clear = true }), + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client and client.name == 'ruff' then + client.server_capabilities.hoverProvider = false + end + end, + desc = 'Disable hover capability from Ruff', +}) + +require('dap-python').setup(get_python_path()) + +dap.configurations.python = { + { + type = 'python', + request = 'launch', + name = 'Launch Program', + program = '${file}', + console = 'integratedTerminal', + }, + { + type = 'python', + request = 'launch', + name = 'Profile Memory', + program = '${file}', + args = { '--profile-memory' }, + }, +} + +vim.keymap.set('n', 'Rr', function() + local screen_width_percentage = 25 + local function find_terminal_buffer() + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(bufnr) and vim.bo[bufnr].buftype == 'terminal' and vim.b[bufnr].python_repl == true then + return bufnr + end + end + return nil + end + + local function toggle_terminal_window(bufnr) + for _, win in ipairs(vim.api.nvim_list_wins()) do + if vim.api.nvim_win_get_buf(win) == bufnr then + vim.api.nvim_win_hide(win) + return + end + end + + vim.cmd('vsplit') + vim.api.nvim_win_set_buf(0, bufnr) + vim.cmd.wincmd('L') + vim.api.nvim_win_set_width(0, math.floor(vim.o.columns / (100 / screen_width_percentage))) + end + + local function create_terminal() + vim.cmd('vnew') + vim.cmd.wincmd('L') + + vim.api.nvim_win_set_width(0, math.floor(vim.o.columns / (100 / screen_width_percentage))) + + vim.b.python_repl = true + local job_id = vim.fn.termopen(get_ipython_argv()) + if job_id and job_id > 0 then + vim.g.slime_default_config = { jobid = job_id } + end + vim.cmd('startinsert') + end + + local term_bufnr = find_terminal_buffer() + if term_bufnr then + local job_id = vim.b[term_bufnr].terminal_job_id + if job_id and job_id > 0 then + vim.g.slime_default_config = { jobid = job_id } + end + toggle_terminal_window(term_bufnr) + else + create_terminal() + end +end, { desc = 'Run/REPL: Toggle Python REPL' }) diff --git a/ftplugin/rust.lua b/ftplugin/rust.lua new file mode 100644 index 0000000..1ef2425 --- /dev/null +++ b/ftplugin/rust.lua @@ -0,0 +1,60 @@ +-- Set buffer-local options for Rust files +vim.opt_local.expandtab = true -- Use spaces instead of tabs +vim.opt_local.autoindent = true -- Maintain indentation levels +vim.opt_local.smartindent = true -- Smart indentation +vim.opt_local.shiftwidth = 4 -- Indent width +vim.opt_local.tabstop = 4 -- Tab width +vim.opt_local.softtabstop = 4 -- Soft tab width for alignment +vim.opt_local.textwidth = 80 -- Maximum text width +vim.opt_local.colorcolumn = '80' -- Highlight column 80 + +vim.keymap.set('n', 'Rf', 'Cargo run', { desc = 'Run/REPL: Run (Cargo)', noremap = true, buffer = 0 }) +vim.keymap.set('n', 'Rb', 'Cargo check', { desc = 'Run/REPL: Build/Check (Cargo)', noremap = true, buffer = 0 }) + +local dap = require('dap') +local rust_tools = require('rust-tools') +local mason_registry = require('mason-registry') + +local function get_codelldb_paths() + local codelldb_package = mason_registry.get_package('codelldb') + if not codelldb_package:is_installed() then + vim.notify('codelldb is not installed. Please install it via mason.nvim.', vim.log.levels.ERROR) + return nil, nil + end + local codelldb_path = codelldb_package:get_install_path() + local adapter = codelldb_path .. '/extension/adapter/codelldb' + local lib = codelldb_path .. '/extension/lldb/lib/liblldb.so' + return adapter, lib +end + +local codelldb_adapter, codelldb_lib = get_codelldb_paths() +if codelldb_adapter and codelldb_lib then + rust_tools.setup({ + tools = { + autosethints = true, + inlay_hints = { + show_parameter_hints = true, + parameter_hints_prefix = '<- ', + other_hints_prefix = '=> ', + }, + }, + server = { + on_attach = function(_, bufnr) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'dR', 'RustDebuggables', { noremap = true, silent = true }) + vim.api.nvim_buf_set_keymap(bufnr, 'n', 'K', 'RustHoverActions', { noremap = true, silent = true }) + end, + }, + dap = { + adapter = require('rust-tools.dap').get_codelldb_adapter(codelldb_adapter, codelldb_lib), + }, + }) +end + +dap.configurations.rust = { + { + type = 'rust', + request = 'launch', + name = 'Launch Program', + program = '${workspaceFolder}/target/debug/${workspaceFolderBasename}', + }, +} diff --git a/ftplugin/sh.lua b/ftplugin/sh.lua new file mode 100644 index 0000000..04795e5 --- /dev/null +++ b/ftplugin/sh.lua @@ -0,0 +1,5 @@ +vim.opt_local.expandtab = false +vim.opt_local.shiftwidth = 2 +vim.opt_local.softtabstop = 2 +vim.opt_local.tabstop = 2 +vim.opt_local.textwidth = 80 diff --git a/ftplugin/sql.lua b/ftplugin/sql.lua new file mode 100644 index 0000000..ad59a73 --- /dev/null +++ b/ftplugin/sql.lua @@ -0,0 +1 @@ +vim.opt_local.commentstring = '-- %s' diff --git a/ftplugin/yaml.lua b/ftplugin/yaml.lua new file mode 100644 index 0000000..f57a731 --- /dev/null +++ b/ftplugin/yaml.lua @@ -0,0 +1,17 @@ +-- Set indentation for YAML files +vim.bo.shiftwidth = 2 +vim.bo.tabstop = 2 +vim.bo.expandtab = true + +-- Set text width to 80 to auto-break lines when formatting +vim.bo.textwidth = 80 + +vim.api.nvim_create_autocmd('BufWritePre', { + buffer = 0, + callback = function() + local first_line = vim.api.nvim_buf_get_lines(0, 0, 1, false)[1] or '' + if first_line ~= '---' then + vim.api.nvim_buf_set_lines(0, 0, 0, false, { '---' }) + end + end, +}) diff --git a/init.lua b/init.lua new file mode 100755 index 0000000..4ae37dc --- /dev/null +++ b/init.lua @@ -0,0 +1,26 @@ +-- Install package manager +-- https://github.com/folke/lazy.nvim +local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + 'git', + 'clone', + '--filter=blob:none', + 'https://github.com/folke/lazy.nvim.git', + '--branch=stable', -- latest stable release + lazypath, + }) +end +vim.opt.rtp:prepend(vim.env.LAZY or lazypath) + +vim.cmd('filetype plugin on') + +vim.g.mapleader = ' ' +vim.g.maplocalleader = ' ' + +require('config.lazy') +require('config.autocmds') +require('config.options') +require('config.mappings') +require('config.utils') +require('config.themes') diff --git a/lua/config/autocmds.lua b/lua/config/autocmds.lua new file mode 100755 index 0000000..36211f2 --- /dev/null +++ b/lua/config/autocmds.lua @@ -0,0 +1,67 @@ +-- Auto-format on save +vim.cmd([[ + augroup FormatOnSave + autocmd! + autocmd BufWritePre * lua vim.lsp.buf.format({ async = false }) + augroup END +]]) + +-- Augroup for Highlight on yank +vim.cmd([[ + augroup YankHighlight + autocmd! + autocmd TextYankPost * lua vim.hl.on_yank() + augroup END +]]) + +-- Remove trailing whitespace +vim.cmd([[ + augroup RemoveTrailingWhitespace + autocmd! + autocmd BufWritePre * %s/\s\+$//e + augroup END +]]) + +-- Append newline at EOF, excluding YAML files +vim.cmd([[ + augroup append_newline_at_eof + autocmd! + autocmd BufWritePre * silent! if !empty(getline('$')) && getline('$') !~# '\n$' && &filetype != 'yaml' | call append(line('$'), '') | endif + augroup END +]]) + +local function augroup(name) + return vim.api.nvim_create_augroup('lazyvim_' .. name, { clear = true }) +end + +vim.api.nvim_create_autocmd('VimEnter', { + group = augroup('autoupdate'), + callback = function() + if require('lazy.status').has_updates() then + require('lazy').update({ show = false }) + end + end, +}) + +vim.api.nvim_create_autocmd('User', { + pattern = 'LazyCheck', + -- pattern = "LazyVimStarted", + desc = 'Update lazy.nvim plugins', + callback = function() + vim.schedule(function() + require('lazy').sync({ wait = false, show = false }) + end) + end, +}) + +vim.api.nvim_create_autocmd('TermOpen', { + pattern = '*', + group = vim.api.nvim_create_augroup('custom-term-open', { clear = true }), + desc = 'Automatically start insert mode in terminal buffers', + callback = function() + vim.cmd('startinsert') + vim.cmd('setlocal nonumber norelativenumber') + vim.cmd('normal! G') + vim.cmd('tnoremap ') + end, +}) diff --git a/lua/config/lazy.lua b/lua/config/lazy.lua new file mode 100755 index 0000000..2f59f1d --- /dev/null +++ b/lua/config/lazy.lua @@ -0,0 +1,37 @@ +local opts = { + git = { log = { '--since=3 days ago' } }, + ui = { custom_keys = { false } }, + colors = { themes = { 'monokai' } }, + checker = { + enabled = true, + -- notify = false, + }, + performance = { + rtp = { + disabled_plugins = { + 'gzip', + 'tarPlugin', + 'tohtml', + 'tutor', + 'zipPlugin', + }, + }, + }, + change_detection = { + notify = false, + }, + rocks = { enabled=false }, +} + +require('lazy').setup({ + -- Git related plugins + 'tpope/vim-fugitive', + 'tpope/vim-rhubarb', + + -- Detect tabstop and shiftwidth automatically + -- 'tpope/vim-sleuth', + { + import = 'custom.plugins', + exclude = 'custom.plugins.mason', + }, +}, opts) diff --git a/lua/config/mappings.lua b/lua/config/mappings.lua new file mode 100755 index 0000000..3a2d428 --- /dev/null +++ b/lua/config/mappings.lua @@ -0,0 +1,162 @@ +local utils = require('config.utils') + +-- Basic Keymaps +-- Keymaps for better default experience +-- See `:help vim.keymap.set()` +vim.keymap.set({ 'n', 'v' }, '', '', { silent = true }) +-- vim.keymap.set('n', 'pv', vim.cmd.Ex, { desc = "[P]roject [V]iew" }) +vim.keymap.set('n', '', '', { silent = true }) +vim.keymap.set('n', '', 'nohlsearch', { silent = true }) + +-- Remap for dealing with word wrap +vim.keymap.set('n', 'k', "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) +vim.keymap.set('n', 'j', "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) + +-- Move lines +vim.keymap.set('v', 'J', ":m '>+1gv=gv", { silent = true }) +vim.keymap.set('v', 'K', ":m '<-2gv=gv", { silent = true }) +vim.keymap.set('n', '>', '>gv', { silent = true }) +vim.keymap.set('n', '<', '', 'zz', { desc = 'Half Page Jumping Up' }) +vim.keymap.set('n', '', 'zz', { desc = 'Half Page Jumping Down' }) +vim.keymap.set('n', 'n', 'nzzzv', { noremap = true, silent = true }) +vim.keymap.set('n', 'N', 'Nzzzv', { noremap = true, silent = true }) + +vim.keymap.set('n', 'n', function() + vim.o.hlsearch = not vim.o.hlsearch +end, { desc = 'Toggle Search Highlight' }) + +vim.api.nvim_set_keymap('n', 'gp', '', { noremap = true, silent = true }) +vim.keymap.set('n', '', 'cnextzz') +vim.keymap.set('n', '', 'cprevzz') +vim.keymap.set('n', ']q', 'lnextzz', { desc = 'Location List Next' }) +vim.keymap.set('n', '[q', 'lprevzz', { desc = 'Location List Prev' }) + +-- Buffer Navigation Keymaps +vim.keymap.set('n', 'bn', 'bnextzz', { desc = 'Quick Nav Buf Next' }) +vim.keymap.set('n', 'bp', 'bprevzz', { desc = 'Quick Nav Buf Prev' }) +vim.keymap.set('n', 'bd', 'bdelete', { desc = 'Quick Nav Buf Delete' }) +vim.keymap.set('n', 'bs', 'split', { desc = 'Openn Buf Horizontal Split' }) +vim.keymap.set('n', 'bv', 'vsp', { desc = 'Open Buf Vertical Split' }) +-- vim.keymap.set('n', 'bt', function() +-- vim.cmd.vnew() +-- vim.cmd.term() +-- vim.cmd.wincmd('J') +-- vim.api.nvim_win_set_height(0, 15) +-- end, { desc = 'Open Buf in Terminal' }) + +-- -- open terminal for R and Python +-- vim.keymap.set('n', 'bR', 'terminal R', { desc = 'Open R Terminal' }) +-- vim.keymap.set('n', 'bP', 'terminal python', { desc = 'Open Python Terminal' }) + +-- Editing Keymaps +vim.keymap.set('x', 'p', [["_dP"]], { desc = 'Paste without register' }) +vim.keymap.set({ 'n', 'v' }, 'D', [["_d"]], { desc = 'Delete without register' }) +vim.keymap.set({ 'n', 'v' }, 'y', '"+y', { desc = 'Copy to + register' }) +vim.keymap.set('n', 'Y', '"+Y') +-- replace current word in current scope +vim.keymap.set( + 'n', + 'rw', + ':%s/\\<\\>//gI', + { desc = '[R]eplace Current [W]ord in Current Scope' } +) +-- replace current word in file scope +vim.keymap.set( + 'n', + 'rW', + ':%s/\\<\\>//gI', + { desc = '[R]eplace Current [W]ord in File Scope' } +) + +-- Open vertical split pane + +-- File Management Keymaps +vim.keymap.set('n', '', 'silent !tmux neww $HOME/.local/bin/tmux_sessionizer', { desc = 'Open Session' }) +vim.keymap.set('n', 'fx', '!chmod +x %', { desc = 'Set Current File to Executable', silent = true }) + +-- Telescope Keymaps +-- See `:help telescope.builtin` +local builtin = require('telescope.builtin') +vim.keymap.set('n', '?', builtin.oldfiles, { desc = '[?] Find recently opened files' }) +vim.keymap.set('n', '', builtin.buffers, { desc = '[ ] Find existing buffers' }) +vim.keymap.set('n', '/', function() + builtin.current_buffer_fuzzy_find(require('telescope.themes').get_dropdown({ winblend = 10, previewer = false })) +end, { desc = '[/] Fuzzily search in current buffer' }) +vim.keymap.set('n', 'gf', builtin.git_files, { desc = 'Search [G]it [F]iles' }) +vim.keymap.set('n', 'gb', builtin.git_branches, { desc = 'Search [G]it [B]ranches' }) +vim.keymap.set('n', 'gc', builtin.git_commits, { desc = 'Search [G]it [C]ommits' }) +vim.keymap.set('n', 'sf', builtin.find_files, { desc = '[S]earch [F]iles' }) + +vim.keymap.set({ 'n', 'v' }, 'sh', function() + local query = utils.get_search_query() + builtin.help_tags({ search = query, initial_mode = 'insert', default_text = query }) +end, { desc = '[S]earch [H]elp' }) +vim.keymap.set({ 'n', 'v' }, 'sw', function() + local query = utils.get_search_query() + builtin.grep_string({ search = query, initial_mode = 'insert', default_text = query }) +end, { desc = '[S]earch current [W]ord' }) +vim.keymap.set('n', 'sp', function() + builtin.grep_string({ search = vim.fn.input('Grep Search > ') }) +end, { desc = '[S]earch [P]roject' }) +vim.keymap.set('n', 'sG', builtin.live_grep, { desc = '[S]earch by [G]rep' }) +vim.keymap.set('n', 'sg', ':LiveGrepGitRoot', { desc = '[S]earch by [G]rep on Git Root' }) +vim.keymap.set('n', 'sd', builtin.diagnostics, { desc = '[S]earch [D]iagnostics' }) +vim.keymap.set('n', 'sr', builtin.resume, { desc = '[S]earch [R]esume' }) + +-- Telescope config files +vim.keymap.set('n', 'fc', function() + builtin.find_files({ + cwd = vim.fn.stdpath('config'), + find_command = { + 'fd', + '--type', + 'f', + '--exclude', + 'README.md', + '--exclude', + 'LICENSE.md', + '--exclude', + 'init.lua', + }, + }) +end, { desc = '[F]ind [C]onfig Files' }) + +vim.keymap.set('n', 'fp', function() + builtin.find_files({ + cwd = vim.fs.joinpath(vim.fn.stdpath('data'), 'lazy'), + }) +end, { desc = '[F]ind [P]lugin Files' }) + +-- local runner = require("quarto.runner") +-- vim.keymap.set('n', 'qrc', runner.run_cell, { desc = '[R]un [C]ell', silent = true }) +-- vim.keymap.set('n', 'qra', runner.run_above, { desc = '[R]un cell and [A]bove', silent = true }) +-- vim.keymap.set('n', "qrA", runner.run_all, { desc = "run all cells", silent = true }) +-- vim.keymap.set('n', "qrl", runner.run_line, { desc = "run line", silent = true }) +-- vim.keymap.set('v', 'qr', runner.run_range, { desc = "run visual range", silent = true }) +-- vim.keymap.set('n', 'qRA', function() +-- runner.run_all(true) +-- end, { desc = "run all cells of all languages", silent = true }) + +-- Refactoring Keymaps +-- vim.keymap.set({ "x" }, "re", [[lua require('refactoring').refactor('Extract Function')]], +-- { noremap = true, silent = true, expr = false, desc = "Extract Function" }) +-- vim.keymap.set( +-- { "x" }, +-- "rf", [[lua require('refactoring').refactor('Extract Function To File')]], +-- { noremap = true, silent = true, expr = false, desc = "Extract Function To File" } +--) +-- vim.keymap.set({ "x" }, "rv", [[lua require('refactoring').refactor('Extract Variable')]], +-- { noremap = true, silent = true, expr = false, desc = "Extract Variable" }) +-- vim.keymap.set({ "n" }, "rI", [[lua require('refactoring').refactor('Inline Variable')]], +-- { noremap = true, silent = true, expr = false, desc = "Inline Variable" }) +-- vim.keymap.set({ "n", "x" }, "ri", [[lua require('refactoring').refactor('Inline Variable')]], +-- { noremap = true, silent = true, expr = false, desc = "Inline Variable" }) +-- +-- vim.keymap.set({ "n" }, "rb", [[lua require('refactoring').refactor('Extract Block')]], +-- { noremap = true, silent = true, expr = false, desc = "Extract Block" }) +-- vim.keymap.set({ "n" }, "rbf", [[lua require('refactoring').refactor('Extract Block To File')]], +-- { noremap = true, silent = true, expr = false, desc = "Extract Block To File" }) diff --git a/lua/config/options.lua b/lua/config/options.lua new file mode 100755 index 0000000..54e1f78 --- /dev/null +++ b/lua/config/options.lua @@ -0,0 +1,141 @@ +local opt = vim.opt +local g = vim.g + +g.ftplugin_migrated_all = true + +local opts = { + -- Change cursor in insert mode + guicursor = '', + + -- set terminal tab title + title = true, + titlelen = 0, + -- titlestring = 'nvim %t (%-15.25F)%a%r%m', + titlestring = 'nvim %t (%-15.25f)%a%r%m', + + -- Make line numbers default + relativenumber = true, + + --Enable noshowmode + showmode = false, + + -- Enable lazy redraw + lazyredraw = true, + + -- Enable break indent + breakindent = true, + + -- Indent Configuration + tabstop = 4, + softtabstop = 4, + shiftwidth = 4, + expandtab = true, + + -- Disable line wrap + wrap = false, + + -- Save undo history + swapfile = false, + backup = false, + undodir = vim.fn.stdpath('data') .. '/site/undodir', + undofile = true, + + -- Searching Configuration + hlsearch = false, + incsearch = true, + + -- Case-insensitive searching UNLESS \C or capital in search + ignorecase = true, + smartcase = true, + + -- Decrease update time + updatetime = 50, + timeoutlen = 300, + + -- Set completeopt to have a better completion experience + completeopt = 'menuone,noselect', + + -- NOTE: You should make sure your terminal supports this + termguicolors = true, + scrolloff = 10, + pyxversion = 3, + + -- Sets how neovim will display certain whitespace characters in the editor. + -- See :help 'list' + -- and :help 'listchars' + list = true, + listchars = { tab = ' ', trail = '·', nbsp = '␣' }, + + -- Preview substitutions live, as you type! + inccommand = 'split', +} + +for k, v in pairs(opts) do + opt[k] = v +end + +local win_local = { + signcolumn = 'yes', + number = true, +} + +for k, v in pairs(win_local) do + vim.wo[k] = v +end + +-- Disable built-in plugins +local disabled_built_ins = { + '2html_plugin', + 'getscript', + 'getscriptPlugin', + 'gzip', + 'logipat', + 'matchit', + -- "netrw", + 'netrwFileHandlers', + 'loaded_remote_plugins', + 'loaded_tutor_mode_plugin', + -- "netrwPlugin", + -- "netrwSettings", + 'rrhelper', + 'spellfile_plugin', + 'tar', + 'tarPlugin', + 'vimball', + 'vimballPlugin', + 'zip', + 'zipPlugin', + 'matchparen', +} + +for _, plugin in pairs(disabled_built_ins) do + g['loaded_' .. plugin] = 1 +end + +-- Improve Neovim startup +local global_let_opts = { + loaded_python_provider = 0, + loaded_python3_provider = 0, + python_host_skip_check = 1, + python3_host_skip_check = 1, + python3_host_prog = '/usr/local/bin/python3', + EditorConfig_core_mode = 'external_command', + matchparen_timeout = 20, + matchparen_insert_timeout = 20, +} + +for k, v in pairs(global_let_opts) do + g[k] = v +end + +opt.formatoptions = 'l' +opt.formatoptions = opt.formatoptions + - 'a' -- Auto formatting is BAD. + - 't' -- Don't auto format my code. I got linters for that. + + 'c' -- In general, I like it when comments respect textwidth + - 'o' -- O and o, don't continue comments + + 'r' -- But do continue when pressing enter. + + 'n' -- Indent past the formatlistpat, not underneath it. + + 'j' -- Auto-remove comments if possible. + - '2' -- I'm not in gradeschool anymore +opt.iskeyword:append('_') diff --git a/lua/config/themes.lua b/lua/config/themes.lua new file mode 100755 index 0000000..e69de29 diff --git a/lua/config/utils.lua b/lua/config/utils.lua new file mode 100755 index 0000000..8203d27 --- /dev/null +++ b/lua/config/utils.lua @@ -0,0 +1,23 @@ +-- Function to get the current visual selection +local function get_visual_selection() + vim.cmd('noau normal! "vy"') + local text = vim.fn.getreg('v') + -- optional: clear register v + -- vim.fn.setreg('v', '') + + text = text:gsub('\n$', '') -- remove trailing newline + return text +end + +-- Function to get the current search query +local function get_search_query() + local word_under_cursor = vim.fn.expand('') + local visual_selection = get_visual_selection() -- call local function directly + + return visual_selection ~= '' and visual_selection or word_under_cursor +end + +return { + get_visual_selection = get_visual_selection, + get_search_query = get_search_query, +} diff --git a/lua/custom/plugins/atac.lua b/lua/custom/plugins/atac.lua new file mode 100755 index 0000000..b62c29b --- /dev/null +++ b/lua/custom/plugins/atac.lua @@ -0,0 +1,17 @@ +return { + 'NachoNievaG/atac.nvim', + dependencies = { 'akinsho/toggleterm.nvim' }, + config = function() + require('toggleterm').setup({ + open_mapping = [[]], + hide_numbers = true, + shade_terminals = true, + start_in_insert = true, + persist_size = true, + close_on_exit = true, + }) + require('atac').setup({ + dir = '~/Documents/projects/', -- By default, the dir will be set as /tmp/atac + }) + end, +} diff --git a/lua/custom/plugins/auto-dark-mode.lua b/lua/custom/plugins/auto-dark-mode.lua new file mode 100755 index 0000000..c733b7f --- /dev/null +++ b/lua/custom/plugins/auto-dark-mode.lua @@ -0,0 +1,66 @@ +-- Define the function to set custom highlights for both themes +local function set_lsp_highlights(mode) + local hl = vim.api.nvim_set_hl + + -- Shared diagnostic groups + local diagnostic_groups = { + 'DiagnosticVirtualTextError', + 'DiagnosticVirtualTextWarn', + 'DiagnosticVirtualTextInfo', + 'DiagnosticVirtualTextHint', + 'DiagnosticUnderlineError', + 'DiagnosticUnderlineWarn', + 'DiagnosticUnderlineInfo', + 'DiagnosticUnderlineHint', + } + + if mode == 'dark' then + local fg = 'NONE' + local bg = '#272822' + local comment = '#75715e' + local line_nr = '#90908a' + + for _, group in ipairs(diagnostic_groups) do + hl(0, group, { fg = fg, bg = bg, underline = false }) + end + + hl(0, 'TreesitterContext', { bg = bg }) + hl(0, 'TreesitterContextLineNumber', { fg = line_nr }) + hl(0, 'TreesitterContextSeparator', { fg = comment }) + elseif mode == 'light' then + local bg = '#fdf6e3' + local fg = '#657b83' + local comment = '#586e75' + + hl(0, 'LspDocumentHighlight', { bg = bg, fg = fg, underline = false }) + hl(0, 'DiagnosticVirtualTextError', { fg = '#dc322f' }) + hl(0, 'DiagnosticVirtualTextWarn', { fg = '#b58900' }) + hl(0, 'DiagnosticVirtualTextInfo', { fg = '#268bd2' }) + hl(0, 'DiagnosticVirtualTextHint', { fg = '#2aa198' }) + + hl(0, 'TreesitterContext', { bg = 'NONE' }) + hl(0, 'TreesitterContextLineNumber', { fg = comment }) + hl(0, 'TreesitterContextSeparator', { fg = comment }) + end +end + +return { + 'f-person/auto-dark-mode.nvim', + opts = { + update_interval = 2000, + set_dark_mode = function() + vim.api.nvim_set_option_value('background', 'dark', {}) + vim.cmd('colorscheme monokai_soda') + + -- Apply custom highlight settings for Monokai + set_lsp_highlights('dark') + end, + set_light_mode = function() + vim.api.nvim_set_option_value('background', 'light', {}) + vim.cmd('colorscheme solarized') + + -- Apply custom highlight settings for Solarized + set_lsp_highlights('light') + end, + }, +} diff --git a/lua/custom/plugins/autopairs.lua b/lua/custom/plugins/autopairs.lua new file mode 100755 index 0000000..997e534 --- /dev/null +++ b/lua/custom/plugins/autopairs.lua @@ -0,0 +1,12 @@ +return { + 'windwp/nvim-autopairs', + dependencies = { 'hrsh7th/nvim-cmp' }, + event = 'InsertEnter', + config = function() + require('nvim-autopairs').setup({}) + -- If you want to automatically add `(` after selecting a function or method + local cmp_autopairs = require('nvim-autopairs.completion.cmp') + local cmp = require('cmp') + cmp.event:on('confirm_done', cmp_autopairs.on_confirm_done()) + end, +} diff --git a/lua/custom/plugins/cmp.lua b/lua/custom/plugins/cmp.lua new file mode 100755 index 0000000..0b1c417 --- /dev/null +++ b/lua/custom/plugins/cmp.lua @@ -0,0 +1,143 @@ +return { + 'hrsh7th/nvim-cmp', + event = { 'InsertEnter', 'CmdlineEnter' }, + dependencies = { + -- Core snippet engine and integration + { 'L3MON4D3/LuaSnip' }, + { 'saadparwaiz1/cmp_luasnip' }, + + -- LSP support + { 'hrsh7th/cmp-nvim-lsp' }, + + -- Predefined snippets, lazy-loaded + { 'rafamadriz/friendly-snippets' }, + + -- Additional sources + { 'hrsh7th/cmp-path' }, + { 'hrsh7th/cmp-buffer' }, -- Will be filtered later + + -- Cmdline & git + { 'hrsh7th/cmp-cmdline' }, + { 'petertriho/cmp-git', ft = { 'gitcommit' } }, + + -- UI enhancement + { 'onsails/lspkind-nvim' }, + }, + config = function() + local cmp = require('cmp') + local luasnip = require('luasnip') + local lspkind = require('lspkind') + + -- Load only selected snippets + require('luasnip.loaders.from_vscode').lazy_load({}) + + vim.opt.completeopt = { 'menu', 'menuone', 'noselect' } + + luasnip.config.setup({ + history = true, + updateevents = 'TextChanged,TextChangedI', + }) + + -- Precompute mappings for insert and command mode + local insert_mappings = { + [''] = cmp.mapping.select_next_item(), + [''] = cmp.mapping.select_prev_item(), + [''] = cmp.mapping.scroll_docs(-4), + [''] = cmp.mapping.scroll_docs(4), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_next_item() + elseif luasnip.expand_or_jumpable() then + luasnip.expand_or_jump() + else + fallback() + end + end, { 'i', 's', 'c' }), + [''] = cmp.mapping(function(fallback) + if cmp.visible() then + cmp.select_prev_item() + elseif luasnip.jumpable(-1) then + luasnip.jump(-1) + else + fallback() + end + end, { 'i', 's', 'c' }), + [''] = cmp.mapping.confirm({ select = false }), + } + + local insert_sources = { + { name = 'nvim_lsp', priority = 1000 }, + { name = 'luasnip', priority = 900 }, + { name = 'path', priority = 750 }, + { name = 'buffer', priority = 500, keyword_length = 5, max_item_count = 50 }, + } + + cmp.setup({ + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + mapping = insert_mappings, + sources = insert_sources, + formatting = { + fields = { 'abbr', 'kind', 'menu' }, + expandable_indicator = false, + format = function(entry, vim_item) + if entry.source.name == 'nvim_lsp' then + return lspkind.cmp_format({ + mode = 'symbol_text', + maxwidth = 50, + ellipsis_char = '...', + menu = { + nvim_lsp = '[LSP]', + luasnip = '[Snip]', + buffer = '[Buffer]', + path = '[Path]', + }, + })(entry, vim_item) + end + return vim_item + end, + }, + window = { + completion = cmp.config.window.bordered(), + documentation = cmp.config.window.bordered(), + }, + performance = { + filtering_context_budget = 5, + async_budget = 5, + confirm_resolve_timeout = 100, + max_view_entries = 20, + debounce = 60, + throttle = 30, + fetching_timeout = 200, + }, + }) + + -- Cmdline setup + cmp.setup.cmdline({ '/', '?' }, { + mapping = insert_mappings, + sources = { { name = 'buffer', keyword_length = 5 } }, + }) + + cmp.setup.cmdline(':', { + mapping = insert_mappings, + sources = { { name = 'path' }, { name = 'cmdline' } }, + window = { + documentation = cmp.config.window.bordered({ + winhighlight = 'Normal:Normal,FloatBorder:FloatBorder,CursorLine:Visual,Search:None', + border = 'rounded', + }), + }, + }) + + -- Git commit + cmp.setup.filetype('gitcommit', { + sources = { + { name = 'git', priority = 900 }, + { name = 'buffer', keyword_length = 5 }, + }, + }) + end, +} diff --git a/lua/custom/plugins/comments.lua b/lua/custom/plugins/comments.lua new file mode 100755 index 0000000..c086553 --- /dev/null +++ b/lua/custom/plugins/comments.lua @@ -0,0 +1,4 @@ +return { + 'numToStr/Comment.nvim', + opts = {}, +} diff --git a/lua/custom/plugins/context.lua b/lua/custom/plugins/context.lua new file mode 100644 index 0000000..dd80393 --- /dev/null +++ b/lua/custom/plugins/context.lua @@ -0,0 +1,28 @@ +return { + 'nvim-treesitter/nvim-treesitter-context', + event = 'BufReadPost', + config = function() + require('treesitter-context').setup({ + max_lines = 3, + multiwindow = false, + multiline_threshold = 1, + trim_scope = 'inner', + mode = 'cursor', -- faster than 'cursor' + separator = '─', + on_attach = function(buf) + -- disable for large files + if vim.api.nvim_buf_line_count(buf) > 3000 then + return false + end + -- disable for specific filetypes + local disabled = { 'markdown', 'text', 'help', 'json', 'yaml' } + if vim.tbl_contains(disabled, vim.bo[buf].filetype) then + return false + end + return true + end, + }) + + vim.keymap.set('n', 'uc', 'TSContext toggle') + end, +} diff --git a/lua/custom/plugins/copilot-chat.lua b/lua/custom/plugins/copilot-chat.lua new file mode 100644 index 0000000..3e8ba25 --- /dev/null +++ b/lua/custom/plugins/copilot-chat.lua @@ -0,0 +1,43 @@ +-- Copilot Chat (UI + prompts for Copilot) +return { + 'CopilotC-Nvim/CopilotChat.nvim', + cmd = { + 'CopilotChat', + 'CopilotChatClose', + 'CopilotChatExplain', + 'CopilotChatReview', + 'CopilotChatTests', + 'CopilotChatRefactor', + 'CopilotChatOptimize', + 'CopilotChatDocs', + 'CopilotChatToggle', + }, + keys = { + { 'C', 'CopilotChat', desc = 'Open CopilotChat' }, + { 'Cq', 'CopilotChatClose', desc = 'Close CopilotChat' }, + { 'Ce', 'CopilotChatExplain', desc = 'Explain code', mode = { 'n', 'v' } }, + { 'Cr', 'CopilotChatReview', desc = 'Review code', mode = { 'n', 'v' } }, + { 'Ct', 'CopilotChatTests', desc = 'Generate tests', mode = { 'n', 'v' } }, + { 'Cf', 'CopilotChatRefactor', desc = 'Refactor code', mode = { 'n', 'v' } }, + { 'Co', 'CopilotChatOptimize', desc = 'Optimize code', mode = { 'n', 'v' } }, + { 'Cd', 'CopilotChatDocs', desc = 'Generate docs', mode = { 'n', 'v' } }, + { 'Cp', 'CopilotChatToggle', desc = 'Toggle CopilotChat' }, + }, + build = 'make tiktoken', + opts = { + debug = false, + show_help = true, + prompts = { + Explain = 'Please explain how the following code works.', + Review = 'Please review the following code and provide suggestions for improvement.', + Tests = 'Please explain how the selected code works, then generate unit tests for it.', + Refactor = 'Please refactor the following code to improve its clarity and readability.', + Optimize = 'Please optimize the following code for efficiency and performance.', + Docs = 'Please generate documentation for the following code.', + }, + }, + dependencies = { + 'nvim-lua/plenary.nvim', + 'zbirenbaum/copilot.lua', -- make sure copilot is available + }, +} diff --git a/lua/custom/plugins/copilot.lua b/lua/custom/plugins/copilot.lua new file mode 100755 index 0000000..1771de3 --- /dev/null +++ b/lua/custom/plugins/copilot.lua @@ -0,0 +1,18 @@ +-- GitHub Copilot base plugin +return { + 'zbirenbaum/copilot.lua', + cmd = 'Copilot', + event = 'InsertEnter', + opts = { + node_cmd = vim.fn.executable('node') == 1 and vim.fn.exepath('node') or 'node', + suggestion = { + auto_trigger = true, + keymap = { + accept_word = '', + accept_line = '', + -- next = '', + -- previous = '', + }, + }, + }, +} diff --git a/lua/custom/plugins/dadbod.lua b/lua/custom/plugins/dadbod.lua new file mode 100755 index 0000000..f897f9e --- /dev/null +++ b/lua/custom/plugins/dadbod.lua @@ -0,0 +1,17 @@ +return { + 'kristijanhusak/vim-dadbod-ui', + dependencies = { + { 'tpope/vim-dadbod', lazy = true }, + { 'kristijanhusak/vim-dadbod-completion', ft = { 'sql', 'mysql', 'plsql' }, lazy = true }, -- Optional + }, + cmd = { + 'DBUI', + 'DBUIToggle', + 'DBUIAddConnection', + 'DBUIFindBuffer', + }, + init = function() + -- Your DBUI configuration + vim.g.db_ui_use_nerd_fonts = 1 + end, +} diff --git a/lua/custom/plugins/dap.lua b/lua/custom/plugins/dap.lua new file mode 100755 index 0000000..da10649 --- /dev/null +++ b/lua/custom/plugins/dap.lua @@ -0,0 +1,204 @@ +return { + -- Define language plugins at top level to prevent luarocks installation + { + 'mfussenegger/nvim-dap-python', + ft = 'python', + rocks = { enabled = false }, + }, + { + 'leoluz/nvim-dap-go', + ft = 'go', + rocks = { enabled = false }, + }, + { + 'mfussenegger/nvim-dap', + dependencies = { + 'rcarriga/nvim-dap-ui', + 'nvim-neotest/nvim-nio', + { 'thehamsta/nvim-dap-virtual-text', opts = {} }, + 'mfussenegger/nvim-dap-python', + 'leoluz/nvim-dap-go', + 'jay-babu/mason-nvim-dap.nvim', + }, + config = function() + local dap = require('dap') + local dapui = require('dapui') + + dapui.setup() + + -- Mason handles debugger installation and configuration + require('mason-nvim-dap').setup({ + automatic_installation = true, + ensure_installed = { 'codelldb', 'python', 'delve' }, + handlers = {}, + }) + + -- Python setup with proper path detection + local function get_python_path() + -- Check if we're in a virtual environment + local venv = os.getenv('VIRTUAL_ENV') + if venv then + local venv_python = venv .. '/bin/python' + if vim.fn.executable(venv_python) == 1 then + return venv_python + end + end + + -- Fall back to system python3 or python + local python3 = vim.fn.exepath('python3') + if python3 ~= '' then + return python3 + end + + local python = vim.fn.exepath('python') + if python ~= '' then + return python + end + + -- Last resort - use system default + return 'python3' + end + + require('dap-python').setup(get_python_path()) + + -- Go setup (helper does the heavy lifting) + require('dap-go').setup() + + -- C/C++ configurations + dap.configurations.c = { + { + name = 'Launch', + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + }, + { + name = 'Launch with ASan', + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + env = { ASAN_OPTIONS = 'detect_leaks=1' }, + }, + } + dap.configurations.cpp = dap.configurations.c + + -- Rust configuration + dap.configurations.rust = { + { + name = 'Launch', + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/target/debug/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + }, + } + + -- Zig configuration + dap.configurations.zig = { + { + name = 'Launch', + type = 'codelldb', + request = 'launch', + program = function() + return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/zig-out/bin/', 'file') + end, + cwd = '${workspaceFolder}', + stopOnEntry = false, + }, + } + + -- Auto-open/close UI + dap.listeners.after.event_initialized['dapui_config'] = dapui.open + dap.listeners.before.event_terminated['dapui_config'] = dapui.close + dap.listeners.before.event_exited['dapui_config'] = dapui.close + + -- Breakpoint signs + vim.fn.sign_define('DapBreakpoint', { text = '🔴', texthl = '', linehl = '', numhl = '' }) + vim.fn.sign_define('DapStopped', { text = '➡️', texthl = '', linehl = 'debugPC', numhl = '' }) + end, + keys = { + { + 'dc', + function() + require('dap').continue() + end, + desc = 'Debug: Continue', + }, + { + 'ds', + function() + require('dap').step_over() + end, + desc = 'Debug: Step Over', + }, + { + 'di', + function() + require('dap').step_into() + end, + desc = 'Debug: Step Into', + }, + { + 'do', + function() + require('dap').step_out() + end, + desc = 'Debug: Step Out', + }, + { + 'db', + function() + require('dap').toggle_breakpoint() + end, + desc = 'Debug: Toggle Breakpoint', + }, + { + 'dB', + function() + require('dap').set_breakpoint(vim.fn.input('Breakpoint condition: ')) + end, + desc = 'Debug: Conditional Breakpoint', + }, + { + 'dr', + function() + require('dap').repl.toggle() + end, + desc = 'Debug: Toggle REPL', + }, + { + 'dl', + function() + require('dap').run_last() + end, + desc = 'Debug: Run Last', + }, + { + 'du', + function() + require('dapui').toggle() + end, + desc = 'Debug: Toggle UI', + }, + { + 'dt', + function() + require('dap').terminate() + end, + desc = 'Debug: Terminate', + }, + }, + ft = { 'python', 'go', 'rust', 'c', 'cpp', 'zig' }, + }, +} diff --git a/lua/custom/plugins/diffview.lua b/lua/custom/plugins/diffview.lua new file mode 100755 index 0000000..ecc2b1b --- /dev/null +++ b/lua/custom/plugins/diffview.lua @@ -0,0 +1,6 @@ +return { + 'sindrets/diffview.nvim', + dependencies = { 'nvim-lua/plenary.nvim' }, + opts = {}, +} + diff --git a/lua/custom/plugins/formatting.lua b/lua/custom/plugins/formatting.lua new file mode 100755 index 0000000..bd78726 --- /dev/null +++ b/lua/custom/plugins/formatting.lua @@ -0,0 +1,88 @@ +return { + 'stevearc/conform.nvim', + dependencies = { + 'nvim-lua/plenary.nvim', + 'WhoIsSethDaniel/mason-tool-installer.nvim', + }, + event = { 'BufReadPre', 'BufNewFile' }, + opts = { + formatters = { + juliaformatter = { + command = 'julia', + args = { + '--project=' .. vim.fn.expand('~/.julia/environments/nvim-format'), + '--startup-file=no', + '--history-file=no', + '-e', + [[ + using JuliaFormatter + print(format_text(read(stdin, String))) + ]], + }, + stdin = true, + }, + }, + formatters_by_ft = { + python = { + 'ruff_fix', + 'ruff_format', + 'ruff_organize_imports', + }, + yaml = { 'yamlfmt' }, + sh = { 'shfmt' }, + sql = { 'sqlfluff' }, + lua = { 'stylua' }, + json = { 'jq' }, + julia = { 'juliaformatter' }, -- keep JuliaFormatter here + markdown = { 'prettierd', 'prettier' }, + html = { 'prettierd' }, + css = { 'prettierd' }, + javascript = { 'prettierd' }, + typescript = { 'prettierd' }, + }, + + -- Fallback setup + format_on_save = function(bufnr) + if vim.bo[bufnr].filetype == 'julia' then + return + end + return { + timeout_ms = 2000, + lsp_fallback = true, + } + end, + + notify_on_error = true, + async = true, + }, + + config = function(_, opts) + -- Setup Conform + require('conform').setup(opts) + + -- Auto-install formatters via Mason + local all_formatters = {} + for _, formatters in pairs(opts.formatters_by_ft) do + for _, f in ipairs(formatters) do + all_formatters[f] = true + end + end + + local exclude = { + ruff_fix = true, + ruff_format = true, + ruff_organize_imports = true, + juliaformatter = true, -- exclude JuliaFormatter from Mason (install via Julia) + } + + local tools = vim.tbl_filter(function(tool) + return not exclude[tool] + end, vim.tbl_keys(all_formatters)) + + require('mason-tool-installer').setup({ + ensure_installed = tools, + auto_update = false, + run_on_start = true, + }) + end, +} diff --git a/lua/custom/plugins/fugitive.lua b/lua/custom/plugins/fugitive.lua new file mode 100755 index 0000000..c738c14 --- /dev/null +++ b/lua/custom/plugins/fugitive.lua @@ -0,0 +1,46 @@ +return { + 'tpope/vim-fugitive', + config = function() + -- General key mappings for Fugitive + vim.keymap.set('n', 'gs', vim.cmd.Git, { desc = 'Open Git status' }) + + -- Create an autocommand group for Fugitive-specific settings + local fugitive_augroup = vim.api.nvim_create_augroup('fugitive', { clear = true }) + + -- Set up autocommands for Fugitive buffers + vim.api.nvim_create_autocmd('BufWinEnter', { + group = fugitive_augroup, + pattern = '*', + callback = function() + if vim.bo.filetype ~= 'fugitive' then + return + end + + local bufnr = vim.api.nvim_get_current_buf() + local opts = { buffer = bufnr, remap = false } + + -- Key mappings specific to Fugitive buffers + vim.keymap.set('n', 'gp', function() + vim.cmd.Git('push') + end, opts) + vim.keymap.set('n', 'gP', function() + vim.cmd.Git('pull --rebase') + end, opts) + vim.keymap.set('n', 'gU', ':Git push -u origin ', opts) + end, + }) + + -- Additional key mappings for resolving Git conflicts + vim.keymap.set('n', 'gu', 'diffget //2', { desc = 'Get diff for version 2' }) + vim.keymap.set('n', 'gh', 'diffget //3', { desc = 'Get diff for version 3' }) + + -- Create a custom user command for Git operations + vim.api.nvim_create_user_command('Git', function(params) + vim.cmd('Git ' .. params.args) + end, { nargs = '*' }) + end, + cond = function() + -- Conditional loading based on the presence of a Git repository + return vim.fn.isdirectory('.git') == 1 + end, +} diff --git a/lua/custom/plugins/gitsigns.lua b/lua/custom/plugins/gitsigns.lua new file mode 100755 index 0000000..2961bfd --- /dev/null +++ b/lua/custom/plugins/gitsigns.lua @@ -0,0 +1,45 @@ +return { + 'lewis6991/gitsigns.nvim', + opts = { + -- Git sign characters + signs = { + add = { text = '+' }, + change = { text = '~' }, + delete = { text = '_' }, + topdelete = { text = '‾' }, + changedelete = { text = '~' }, + }, + -- Function called when plugin attaches to a buffer + on_attach = function(bufnr) + -- Always show the sign column + vim.opt.signcolumn = 'yes' + + -- Keybindings + local gs = require('gitsigns') + vim.keymap.set('n', 'hp', gs.preview_hunk, { + buffer = bufnr, + desc = 'Preview git hunk', + }) + + vim.keymap.set({ 'n', 'v' }, ']c', function() + if vim.wo.diff then + return ']c' + end + vim.schedule(function() + gs.next_hunk() + end) + return '' + end, { expr = true, buffer = bufnr, desc = 'Jump to next hunk' }) + + vim.keymap.set({ 'n', 'v' }, '[c', function() + if vim.wo.diff then + return '[c' + end + vim.schedule(function() + gs.prev_hunk() + end) + return '' + end, { expr = true, buffer = bufnr, desc = 'Jump to previous hunk' }) + end, + }, +} diff --git a/lua/custom/plugins/harpoon.lua b/lua/custom/plugins/harpoon.lua new file mode 100755 index 0000000..4cc2219 --- /dev/null +++ b/lua/custom/plugins/harpoon.lua @@ -0,0 +1,102 @@ +return { + 'theprimeagen/harpoon', + branch = 'harpoon2', + dependencies = { + 'nvim-lua/plenary.nvim', + -- 'nvim-telescope/telescope.nvim', + }, + config = function() + local harpoon = require('harpoon') + -- local actions = require('telescope.actions') + -- local action_state = require('telescope.actions.state') + -- local conf = require('telescope.config').values + + harpoon:setup({ + settings = { + save_on_toggle = true, + sync_on_ui_close = true, + key = function() + return vim.loop.cwd() or 'global' + end, + }, + }) + + -- Telescope integration + -- local function toggle_telescope() + -- local list = harpoon:list() + -- local file_paths = {} + -- + -- for _, item in ipairs(list.items) do + -- table.insert(file_paths, item.value) + -- end + -- + -- require('telescope.pickers') + -- .new({}, { + -- prompt_title = 'Harpoon', + -- finder = require('telescope.finders').new_table({ + -- results = file_paths, + -- }), + -- previewer = false, + -- sorter = conf.generic_sorter({}), + -- layout_config = { + -- width = 0.5, + -- height = 0.5, + -- prompt_position = 'top', + -- }, + -- layout_strategy = 'vertical', + -- attach_mappings = function(prompt_bufnr, map) + -- map('i', '', function() + -- local selection = action_state.get_selected_entry() + -- if not selection then + -- return + -- end + -- + -- local target = selection[1] + -- for _, item in ipairs(harpoon:list().items) do + -- if item.value == target then + -- harpoon:list():remove(item) + -- break + -- end + -- end + -- actions.close(prompt_bufnr) + -- toggle_telescope() + -- end) + -- return true + -- end, + -- }) + -- :find() + -- end + + vim.keymap.set('n', 'a', function() + if vim.fn.expand('%:p'):sub(1, 6) == 'oil://' then + return + end + + harpoon:list():add() + end, { desc = 'Harpoon: Add file to list (if not already present)' }) + + vim.keymap.set('n', '', function() + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, { desc = 'Harpoon: Toggle quick menu' }) + + vim.keymap.set('n', '', function() + harpoon:list():select(1) + end) + vim.keymap.set('n', '', function() + harpoon:list():select(2) + end) + vim.keymap.set('n', '', function() + harpoon:list():select(3) + end) + vim.keymap.set('n', '', function() + harpoon:list():select(4) + end) + + vim.keymap.set('n', '', function() + harpoon:list():prev() + end) + vim.keymap.set('n', '', function() + harpoon:list():next() + end) + end, +} diff --git a/lua/custom/plugins/images.lua b/lua/custom/plugins/images.lua new file mode 100644 index 0000000..e995654 --- /dev/null +++ b/lua/custom/plugins/images.lua @@ -0,0 +1,104 @@ +return { + -- { + -- 'vhyrro/luarocks.nvim', + -- priority = 1001, + -- opts = { + -- rocks = { 'magick' }, + -- }, + -- event = 'VeryLazy', -- Adjust this based on your needs + -- }, + -- { + -- 'willothy/wezterm.nvim', + -- config = true, + -- event = 'BufWinEnter', -- Or another appropriate event + -- }, + -- { + -- '3rd/image.nvim', + -- enabled = true, + -- commit = 'deb158d', + -- dev = false, + -- ft = { 'markdown', 'quarto', 'vimwiki' }, + -- config = function() + -- local image = require 'image' + -- image.setup { + -- backend = 'wezterm', + -- integrations = { + -- markdown = { + -- enabled = true, + -- only_render_image_at_cursor = true, + -- filetypes = { 'markdown', 'vimwiki', 'quarto' }, + -- }, + -- }, + -- editor_only_render_when_focused = false, + -- window_overlap_clear_enabled = true, + -- tmux_show_only_in_active_window = true, + -- window_overlap_clear_ft_ignore = { 'cmp_menu', 'cmp_docs', 'scrollview', 'scrollview_sign' }, + -- max_width = nil, + -- max_height = nil, + -- max_width_window_percentage = nil, + -- max_height_window_percentage = 30, + -- kitty_method = 'normal', + -- } + -- + -- local function clear_all_images() + -- local bufnr = vim.api.nvim_get_current_buf() + -- local images = image.get_images { buffer = bufnr } + -- for _, img in ipairs(images) do + -- img:clear() + -- end + -- end + -- + -- local function get_image_at_cursor(buf) + -- local images = image.get_images { buffer = buf } + -- local row = vim.api.nvim_win_get_cursor(0)[1] - 1 + -- for _, img in ipairs(images) do + -- if img.geometry ~= nil and img.geometry.y == row then + -- local og_max_height = img.global_state.options.max_height_window_percentage + -- img.global_state.options.max_height_window_percentage = nil + -- return img, og_max_height + -- end + -- end + -- return nil + -- end + -- + -- local create_preview_window = function(img, og_max_height) + -- local buf = vim.api.nvim_create_buf(false, true) + -- local win_width = vim.api.nvim_get_option_value('columns', {}) + -- local win_height = vim.api.nvim_get_option_value('lines', {}) + -- local win = vim.api.nvim_open_win(buf, true, { + -- relative = 'editor', + -- style = 'minimal', + -- width = win_width, + -- height = win_height, + -- row = 0, + -- col = 0, + -- zindex = 1000, + -- }) + -- vim.keymap.set('n', 'q', function() + -- vim.api.nvim_win_close(win, true) + -- img.global_state.options.max_height_window_percentage = og_max_height + -- end, { buffer = buf }) + -- return { buf = buf, win = win } + -- end + -- + -- local handle_zoom = function(bufnr) + -- local img, og_max_height = get_image_at_cursor(bufnr) + -- if img == nil then + -- return + -- end + -- + -- local preview = create_preview_window(img, og_max_height) + -- image.hijack_buffer(img.path, preview.win, preview.buf) + -- end + -- + -- vim.keymap.set('n', 'io', function() + -- local bufnr = vim.api.nvim_get_current_buf() + -- handle_zoom(bufnr) + -- end, { buffer = true, desc = 'image [o]pen' }) + -- + -- vim.keymap.set('n', 'ic', clear_all_images, { desc = 'image [c]lear' }) + -- end, + -- }, + -- +} + diff --git a/lua/custom/plugins/indent.lua b/lua/custom/plugins/indent.lua new file mode 100755 index 0000000..0beaf5a --- /dev/null +++ b/lua/custom/plugins/indent.lua @@ -0,0 +1,24 @@ +local highlight = { + 'Whitespace', + 'Function', +} + +return { + -- Add indentation guides even on blank lines + 'lukas-reineke/indent-blankline.nvim', + main = 'ibl', + opts = { + indent = { + highlight = highlight, + char = '┆', + }, + whitespace = { + highlight = highlight, + remove_blankline_trail = true, + }, + scope = { + enabled = true, + exclude = { language = { 'vim', 'lua', 'go', 'python', 'rust', 'sh', 'json', 'yaml', 'toml', 'markdown' } }, + }, + }, +} diff --git a/lua/custom/plugins/jupytext.lua b/lua/custom/plugins/jupytext.lua new file mode 100644 index 0000000..1ef74ba --- /dev/null +++ b/lua/custom/plugins/jupytext.lua @@ -0,0 +1,67 @@ +return { + 'goerz/jupytext.vim', + lazy = false, + config = function() + vim.g.jupytext_fmt = 'py:percent' + vim.g.jupytext_enable = 1 + vim.g.jupytext_pairing = 1 + + local jupytext_bin = vim.fn.exepath('jupytext') + if jupytext_bin ~= nil and jupytext_bin ~= '' then + vim.g.jupytext_command = jupytext_bin + else + local preferred_python3 = '/opt/homebrew/bin/python3' + local python3_bin = preferred_python3 + if vim.fn.executable(preferred_python3) ~= 1 then + python3_bin = vim.fn.exepath('python3') + end + + if python3_bin ~= nil and python3_bin ~= '' then + vim.g.jupytext_command = python3_bin .. ' -m jupytext' + else + vim.schedule(function() + vim.notify('Jupytext: no `jupytext` or `python3` found in PATH. Install jupytext (e.g. `/opt/homebrew/bin/python3 -m pip install --user jupytext`) or set `vim.g.jupytext_command`.', vim.log.levels.WARN) + end) + end + end + + local jupytext_group = vim.api.nvim_create_augroup('custom_jupytext', { clear = true }) + + vim.api.nvim_create_autocmd('BufWritePost', { + group = jupytext_group, + pattern = '*.py', + callback = function() + local ipynb_file = vim.fn.expand('%:r') .. '.ipynb' + if vim.fn.filereadable(ipynb_file) == 1 then + vim.cmd('silent! Jupytext --sync') + end + end, + }) + + vim.api.nvim_create_autocmd('BufWritePost', { + group = jupytext_group, + pattern = '*.ipynb', + callback = function() + local py_file = vim.fn.expand('%:r') .. '.py' + if vim.fn.filereadable(py_file) == 1 then + vim.cmd('silent! Jupytext --sync') + end + end, + }) + + vim.api.nvim_create_autocmd({ 'BufReadPost', 'BufNewFile' }, { + group = jupytext_group, + pattern = '*.ipynb', + callback = function() + local py_file = vim.fn.expand('%:r') .. '.py' + if vim.fn.filereadable(py_file) == 0 then + vim.cmd('silent! Jupytext --to py:percent') + end + + if vim.fn.filereadable(py_file) == 1 then + vim.cmd('silent! edit ' .. vim.fn.fnameescape(py_file)) + end + end, + }) + end, +} diff --git a/lua/custom/plugins/linting.lua b/lua/custom/plugins/linting.lua new file mode 100755 index 0000000..ae6f908 --- /dev/null +++ b/lua/custom/plugins/linting.lua @@ -0,0 +1,74 @@ +return { + 'mfussenegger/nvim-lint', + dependencies = { 'WhoIsSethDaniel/mason-tool-installer.nvim' }, + event = { 'BufReadPre', 'BufNewFile' }, + opts = { + linters_by_ft = { + python = { 'ruff', 'mypy' }, + go = { 'golangcilint' }, + yaml = { 'yamllint' }, + markdown = { 'markdownlint' }, + sh = { 'shellcheck' }, + lua = { 'luacheck' }, + sql = { 'sqlfluff' }, + }, + mason_to_lint = { + golangcilint = { 'golangci-lint' }, + }, + }, + config = function(_, opts) + local lint = require('lint') + lint.linters_by_ft = opts.linters_by_ft + + -- Flatten linters and map to Mason package names + local all_linters = {} + for _, ft_linters in pairs(opts.linters_by_ft) do + for _, l in ipairs(ft_linters) do + local tool = opts.mason_to_lint[l] or { l } + -- Handle both table and string values + if type(tool) == 'table' then + for _, t in ipairs(tool) do + if t ~= '' then + table.insert(all_linters, t) + end + end + elseif tool ~= '' then + table.insert(all_linters, tool) + end + end + end + + -- Deduplicate tools + local tools_set = {} + for _, t in ipairs(all_linters) do + tools_set[t] = true + end + local tools = vim.tbl_keys(tools_set) + + print('Ensuring installation of linters: ' .. table.concat(tools, ', ')) + + -- Setup mason-tool-installer safely + require('mason-tool-installer').setup({ + ensure_installed = tools, + auto_update = false, + run_on_start = true, + start_delay = 3000, + }) + + -- Autocommand group for linting + local augroup = vim.api.nvim_create_augroup('LintAutogroup', { clear = true }) + vim.api.nvim_create_autocmd({ 'BufEnter', 'BufWritePost', 'InsertLeave' }, { + group = augroup, + callback = function() + local ft = vim.bo.filetype + local available = lint.linters_by_ft[ft] + if available and #available > 0 then + local ok, err = pcall(lint.try_lint) + if not ok then + vim.notify('[nvim-lint] Error running linter: ' .. err, vim.log.levels.ERROR) + end + end + end, + }) + end, +} diff --git a/lua/custom/plugins/lsp-config.lua b/lua/custom/plugins/lsp-config.lua new file mode 100755 index 0000000..5494a9c --- /dev/null +++ b/lua/custom/plugins/lsp-config.lua @@ -0,0 +1,552 @@ +return { + 'neovim/nvim-lspconfig', + event = { 'BufReadPre', 'BufNewFile' }, + dependencies = { + -- Mason core + { 'williamboman/mason.nvim', build = ':MasonUpdate', cmd = 'Mason', config = true }, + 'williamboman/mason-lspconfig.nvim', + 'WhoIsSethDaniel/mason-tool-installer.nvim', + + -- Lua dev + { 'folke/neodev.nvim', ft = 'lua', config = true }, + + -- LSP UI & UX + { 'j-hui/fidget.nvim', opts = {}, event = 'LspAttach' }, + { 'ray-x/lsp_signature.nvim', event = 'LspAttach' }, + + -- Treesitter + { + 'nvim-treesitter/nvim-treesitter', + build = ':TSUpdate', + event = 'BufReadPost', + opts = { + highlight = { enable = true }, + indent = { enable = true }, + ensure_installed = { + 'bash', + 'c', + 'cpp', + 'go', + 'html', + 'julia', + 'lua', + 'markdown', + 'python', + 'rust', + 'sql', + 'vim', + 'vimdoc', + 'yaml', + 'zig', + }, + }, + config = true, + }, + { 'nvim-treesitter/nvim-treesitter-textobjects', event = 'BufReadPost' }, + + -- Python venv selector + { + 'linux-cultist/venv-selector.nvim', + ft = 'python', + opts = { auto_activate = true }, + config = true, + }, + + -- JSON Schema support + { 'b0o/schemastore.nvim', ft = { 'json', 'yaml' } }, + }, + + config = function() + require('neodev').setup() + + -- Enhanced capabilities for LSP servers + local capabilities = vim.lsp.protocol.make_client_capabilities() + local has_cmp, cmp_nvim_lsp = pcall(require, 'cmp_nvim_lsp') + if has_cmp then + capabilities = cmp_nvim_lsp.default_capabilities(capabilities) + end + capabilities.offsetEncoding = { 'utf-8' } + + -- Disable dynamicRegistration to silence yamlls warning + capabilities.workspace = capabilities.workspace or {} + capabilities.workspace.didChangeConfiguration = capabilities.workspace.didChangeConfiguration or {} + capabilities.workspace.didChangeConfiguration.dynamicRegistration = false + + local function path_exists(p) + return p and p ~= '' and vim.uv.fs_stat(p) ~= nil + end + + local function mason_bin(name) + local p = vim.fs.joinpath(vim.fn.stdpath('data'), 'mason', 'bin', name) + if path_exists(p) then + return p + end + return name + end + + local function julia_bin() + local home = vim.env.HOME or '' + local candidates = { + vim.fn.exepath('julia'), + vim.fs.joinpath(home, '.juliaup', 'bin', 'julia'), + '/opt/homebrew/bin/julia', + '/usr/local/bin/julia', + } + for _, p in ipairs(candidates) do + if path_exists(p) then + return p + end + end + return 'julia' + end + + -- Servers that should provide formatting + local format_enabled_servers = { + bashls = true, + clangd = true, + gopls = true, + html = true, + htmx = true, + jsonls = true, + lua_ls = true, + marksman = true, + pyright = true, + ruff = true, + rust_analyzer = true, + taplo = true, + texlab = true, + yamlls = true, + zls = true, + } + + -- Default on_attach function + local on_attach = function(client, bufnr) + -- Enable formatting only for supported servers + if not format_enabled_servers[client.name] then + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + end + + vim.bo[bufnr].omnifunc = 'v:lua.vim.lsp.omnifunc' + + -- LSP signature help + require('lsp_signature').on_attach({ + hint_enable = false, + handler_opts = { border = 'rounded' }, + }, bufnr) + + -- Keymap helper + local function map(keys, func, desc, modes) + modes = modes or 'n' + vim.keymap.set(modes, keys, func, { + buffer = bufnr, + desc = desc and 'LSP: ' .. desc or nil, + }) + end + + -- Lazy-load Telescope for LSP + local function telescope_builtin(name) + return function(...) + require('lazy').load({ plugins = { 'telescope.nvim' } }) + return require('telescope.builtin')[name](...) + end + end + + -- Navigation + map('gd', telescope_builtin('lsp_definitions'), '[G]oto [D]efinition') + map('gD', vim.lsp.buf.declaration, '[G]oto [D]eclaration') + map('gr', telescope_builtin('lsp_references'), '[G]oto [R]eferences') + map('gI', telescope_builtin('lsp_implementations'), '[G]oto [I]mplementation') + map('lT', telescope_builtin('lsp_type_definitions'), '[T]ype Definition') + map('ls', telescope_builtin('lsp_document_symbols'), '[D]ocument [S]ymbols') + map('lS', telescope_builtin('lsp_dynamic_workspace_symbols'), '[W]orkspace [S]ymbols') + + -- Diagnostics + map('ld', vim.diagnostic.open_float, 'Show line [E]rrors') + map('[d', vim.diagnostic.get_prev, 'Previous Diagnostic') + map(']d', vim.diagnostic.get_next, 'Next Diagnostic') + + -- Code Actions & Refactoring + map('lr', vim.lsp.buf.rename, 'Rename') + map('la', vim.lsp.buf.code_action, 'Code Action', { 'n', 'v' }) + + -- Documentation + map('K', vim.lsp.buf.hover, 'Hover Documentation') + map('', vim.lsp.buf.signature_help, 'Signature Documentation') + + -- Document highlight + if client.server_capabilities.documentHighlightProvider then + local highlight_group = vim.api.nvim_create_augroup('lsp_document_highlight', { clear = true }) + local visual_bg = vim.fn.synIDattr(vim.fn.hlID('Visual'), 'bg') or '#3e4452' + + vim.api.nvim_set_hl(0, 'LspReferenceText', { bg = visual_bg }) + vim.api.nvim_set_hl(0, 'LspReferenceRead', { bg = visual_bg }) + vim.api.nvim_set_hl(0, 'LspReferenceWrite', { bg = visual_bg }) + vim.o.updatetime = math.max(vim.o.updatetime, 500) + + local function toggle_lsp_highlight(enable) + if enable then + vim.api.nvim_create_autocmd('CursorHold', { + group = highlight_group, + buffer = bufnr, + callback = function() + if client and client.server_capabilities.documentHighlightProvider then + vim.lsp.buf.document_highlight() + end + end, + }) + vim.api.nvim_create_autocmd('CursorMoved', { + group = highlight_group, + buffer = bufnr, + callback = vim.lsp.buf.clear_references, + }) + else + vim.api.nvim_clear_autocmds({ group = highlight_group, buffer = bufnr }) + vim.lsp.buf.clear_references() + end + end + + vim.api.nvim_buf_create_user_command(bufnr, 'LspToggleHighlight', function() + local enabled = vim.b.lsp_highlight_enabled or false + toggle_lsp_highlight(not enabled) + vim.b.lsp_highlight_enabled = not enabled + print('LSP document highlights ' .. (enabled and 'disabled' or 'enabled')) + end, {}) + + toggle_lsp_highlight(true) + end + end + + -- Default LSP configuration + local default_config = { + capabilities = capabilities, + on_attach = on_attach, + autostart = true, + } + + local julia_cmd = julia_bin() + local julia_ls_project = vim.fn.expand('~/.julia/environments/nvim-lsp') + + -- Server-specific configurations + local servers = { + bashls = { + filetypes = { 'sh', 'bash', 'zsh' }, + }, + + html = { + filetypes = { 'html', 'htmldjango' }, + init_options = { + configurationSection = { 'html', 'css', 'javascript' }, + embeddedLanguages = { + css = true, + javascript = true, + }, + }, + }, + + htmx = { + cmd = { 'htmx-lsp' }, + filetypes = { 'html', 'htmx' }, + }, + + gopls = { + settings = { + gopls = { + gofumpt = true, + staticcheck = true, + completeUnimported = true, + usePlaceholders = true, + analyses = { unusedparams = true }, + }, + }, + }, + + clangd = { + cmd = { + 'clangd', + '--background-index', + '--clang-tidy', + '--header-insertion=iwyu', + '--completion-style=detailed', + '--header-insertion-decorators', + '--query-driver=/usr/bin/clang,/usr/bin/clang++', + '--enable-config', + }, + settings = { + formatting = true, + inlayHints = { + designators = true, + enabled = true, + parameterNames = true, + deducedTypes = true, + }, + }, + filetypes = { 'c', 'cpp', 'objc', 'objcpp', 'h', 'hpp', 'hxx' }, + }, + + marksman = { + filetypes = { 'markdown' }, + settings = { + marksman = { + extensions = { 'mdx' }, + }, + }, + }, + + jsonls = { + cmd = { 'vscode-json-language-server', '--stdio' }, + settings = { + json = { + validate = { enable = true }, + }, + }, + }, + + julials = { + filetypes = { 'julia' }, + cmd = { + julia_cmd, + '--project=' .. julia_ls_project, + '--startup-file=no', + '--history-file=no', + '-e', + [[ + using Logging + using LanguageServer + using SymbolServer + + global_logger(ConsoleLogger(stderr, Logging.Warn)) + + function project_path() + try + return LanguageServer.find_project_path(pwd()) + catch + return pwd() + end + end + + depot_path = get(ENV, "JULIA_DEPOT_PATH", "") + + server = LanguageServer.LanguageServerInstance( + stdin, + stdout, + something(project_path(), pwd()), + depot_path, + ) + + server.runlinter = true + run(server) + ]], + }, + settings = { + julia = { + lint = { + run = true, + }, + }, + }, + on_attach = function(client, bufnr) + on_attach(client, bufnr) + -- Julia must never format via LSP + client.server_capabilities.documentFormattingProvider = false + client.server_capabilities.documentRangeFormattingProvider = false + end, + }, + + pyright = { + settings = { + python = { + analysis = { + autoSearchPaths = true, + diagnosticMode = 'workspace', + useLibraryCodeForTypes = true, + typeCheckingMode = 'none', + reportGeneralTypeIssues = false, + }, + }, + }, + }, + + ruff = { + filetypes = { 'python' }, + on_init = function() + vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('lsp_attach_disable_ruff_hover', { clear = true }), + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client and client.name == 'ruff' then + -- Disable Ruff hover + client.server_capabilities.hoverProvider = false + end + end, + desc = 'LSP: Disable hover capability from Ruff', + }) + end, + }, + + rust_analyzer = { + settings = { + ['rust-analyzer'] = { + imports = { granularity = { group = 'module' }, prefix = 'self' }, + cargo = { buildScripts = { enable = true } }, + procMacro = { enable = true }, + checkOnSave = { command = 'clippy' }, + }, + }, + }, + + taplo = { + filetypes = { 'toml' }, + }, + + yamlls = { + settings = { + yaml = { + schemaStore = { enable = true }, + validate = true, + }, + }, + }, + + texlab = { + filetypes = { 'tex', 'plaintex', 'bib', 'cls', 'sty' }, + settings = { + texlab = { + build = { + onSave = false, + }, + diagnostics = { + ignoredPatterns = { + '^Overfull \\\\hbox', + '^Underfull \\\\hbox', + '^Package.*Warning', + }, + }, + auxDirectory = 'output', + }, + }, + }, + + lua_ls = { + cmd = { mason_bin('lua-language-server') }, + settings = { + Lua = { + workspace = { checkThirdParty = false }, + telemetry = { enable = false }, + diagnostics = { globals = { 'vim' } }, + }, + }, + }, + + sqls = { + filetypes = { 'sql', 'mysql', 'plsql', 'postgresql' }, + settings = { + sql = { + connections = { + { + driver = 'sqlite3', + dataSourceName = 'file::memory:?cache=shared', + }, + }, + }, + }, + on_init = function(client) + local root_dir = client.config.root_dir or vim.fn.getcwd() + local db_files = vim.fn.globpath(root_dir, '*.db', false, true) + vim.list_extend(db_files, vim.fn.globpath(root_dir, '*.sqlite', false, true)) + + if #db_files > 0 then + local connections = {} + for _, path in ipairs(db_files) do + table.insert(connections, { + driver = 'sqlite3', + dataSourceName = vim.fn.fnamemodify(path, ':p'), + }) + end + client.config.settings.sql.connections = connections + client.notify('workspace/didChangeConfiguration', { settings = client.config.settings }) + end + end, + }, + + zls = { + filetypes = { 'zig' }, + }, + } + + -- Use Mason to ensure servers are installed + local ensure_installed = vim.tbl_keys(servers) + ensure_installed = vim.tbl_filter(function(name) + return name ~= 'julials' + end, ensure_installed) + + require('mason-lspconfig').setup({ + ensure_installed = ensure_installed, + automatic_enable = false, + }) + + local function get_lspconfig_defaults(server_name) + local ok, cfg = pcall(require, 'lspconfig.configs.' .. server_name) + if ok and cfg and cfg.default_config then + local defaults = vim.deepcopy(cfg.default_config) + if type(defaults.root_dir) == 'function' then + local orig_root_dir = defaults.root_dir + defaults.root_dir = function(bufnr, on_dir) + local fname = bufnr + if type(bufnr) == 'number' then + fname = vim.api.nvim_buf_get_name(bufnr) + if fname == '' or type(fname) ~= 'string' or fname:match('^%w+://') then + fname = vim.fn.getcwd() + end + end + + local root = orig_root_dir(fname) + if type(on_dir) == 'function' then + on_dir(root) + return + end + return root + end + end + return defaults + end + return {} + end + + -- Setup and enable LSP servers + for server_name, config in pairs(servers) do + if config then + local lspconfig_defaults = get_lspconfig_defaults(server_name) + + -- Merge default config with server-specific config + local merged_config = vim.tbl_deep_extend('force', lspconfig_defaults, default_config, config) + + -- Configure and enable the server + vim.lsp.config(server_name, merged_config) + vim.lsp.enable(server_name) + end + end + + -- Diagnostics configuration + vim.diagnostic.config({ + underline = true, + severity_sort = true, + signs = { + text = { + [vim.diagnostic.severity.ERROR] = 'E', + [vim.diagnostic.severity.WARN] = 'W', + [vim.diagnostic.severity.HINT] = 'H', + [vim.diagnostic.severity.INFO] = 'I', + }, + linehl = { + [vim.diagnostic.severity.ERROR] = 'ErrorMsg', + }, + numhl = { + [vim.diagnostic.severity.WARN] = 'WarningMsg', + }, + }, + virtual_text = { spacing = 2, prefix = '●' }, + float = { source = 'if_many', border = 'rounded' }, + }) + end, +} diff --git a/lua/custom/plugins/lualine.lua b/lua/custom/plugins/lualine.lua new file mode 100755 index 0000000..23c63b8 --- /dev/null +++ b/lua/custom/plugins/lualine.lua @@ -0,0 +1,36 @@ +return { + 'nvim-lualine/lualine.nvim', + dependencies = { 'nvim-tree/nvim-web-devicons', 'tpope/vim-fugitive' }, + config = function() + require('lualine').setup({ + options = { + theme = 'auto', + icons_enabled = true, + component_separators = '|', + section_separators = '', + }, + sections = { + lualine_a = { + { + 'buffers', + show_modified_status = true, + symbols = { modified = '●', alternate_file = '#', directory = '' }, + }, + }, + lualine_b = { + { 'mode', icons_enabled = true }, + }, + lualine_c = { + 'branch', + { 'diff', colored = false, symbols = { added = ' ', modified = ' ', removed = ' ' } }, + }, + lualine_x = { + 'encoding', + { 'fileformat', symbols = { unix = ' ', mac = ' ', dos = ' ' } }, + 'filetype', + }, + }, + extensions = { 'fugitive', 'nvim-tree' }, + }) + end, +} diff --git a/lua/custom/plugins/marks.lua b/lua/custom/plugins/marks.lua new file mode 100755 index 0000000..aa2f20f --- /dev/null +++ b/lua/custom/plugins/marks.lua @@ -0,0 +1,8 @@ +return { + 'chentoast/marks.nvim', + opts = { + default_mappings = true, + default_view = 'vertical', + }, +} + diff --git a/lua/custom/plugins/monokai.lua b/lua/custom/plugins/monokai.lua new file mode 100755 index 0000000..b34be69 --- /dev/null +++ b/lua/custom/plugins/monokai.lua @@ -0,0 +1,8 @@ +return { + 'tanvirtin/monokai.nvim', + priority = 1000, + config = function() + + end, +} + diff --git a/lua/custom/plugins/neogen.lua b/lua/custom/plugins/neogen.lua new file mode 100755 index 0000000..c72bdb4 --- /dev/null +++ b/lua/custom/plugins/neogen.lua @@ -0,0 +1,36 @@ +return { + 'danymat/neogen', + dependencies = { + 'nvim-treesitter/nvim-treesitter', + 'L3MON4D3/LuaSnip', + }, + keys = { + { + 'nf', + function() + require('neogen').generate({ type = 'func' }) + end, + desc = 'Generate function documentation', + }, + { + 'nt', + function() + require('neogen').generate({ type = 'type' }) + end, + desc = 'Generate type documentation', + }, + }, + config = function() + require('neogen').setup({ + enabled = true, + snippet_engine = 'luasnip', -- Using LuaSnip as the snippet engine + }) + end, + cond = function() + -- Only load if Treesitter is installed and available + return vim.fn.exists(':TSInstall') == 2 + end, + -- Uncomment next line if you want to follow only stable versions + -- version = "*" +} + diff --git a/lua/custom/plugins/neotest.lua b/lua/custom/plugins/neotest.lua new file mode 100644 index 0000000..541cd37 --- /dev/null +++ b/lua/custom/plugins/neotest.lua @@ -0,0 +1,137 @@ +return { + { + 'nvim-neotest/neotest', + dependencies = { + 'nvim-lua/plenary.nvim', + 'nvim-neotest/nvim-nio', + 'nvim-treesitter/nvim-treesitter', + 'nvim-neotest/neotest-python', + 'alfaix/neotest-gtest', + 'andythigpen/nvim-coverage', + }, + keys = { + { + 'tn', + function() + local neotest = require('neotest') + neotest.summary.open({ enter = false }) + neotest.run.run() + end, + desc = 'Test: Run nearest', + }, + { + 'tf', + function() + local neotest = require('neotest') + neotest.summary.open({ enter = false }) + neotest.run.run(vim.fn.expand('%')) + end, + desc = 'Test: Run file', + }, + { + 'ta', + function() + local neotest = require('neotest') + neotest.summary.open({ enter = false }) + neotest.run.run(vim.fn.getcwd()) + end, + desc = 'Test: Run all (cwd)', + }, + { + 'td', + function() + local neotest = require('neotest') + neotest.summary.open({ enter = false }) + neotest.run.run({ strategy = 'dap' }) + end, + desc = 'Test: Debug nearest', + }, + { + 'tx', + function() + require('neotest').run.stop() + end, + desc = 'Test: Stop', + }, + { + 'ts', + function() + require('neotest').summary.toggle() + end, + desc = 'Test: Toggle summary', + }, + { + 'to', + function() + require('neotest').output.open({ enter = true, auto_close = false }) + end, + desc = 'Test: Show output', + }, + { + 'tw', + function() + require('neotest').watch.toggle(vim.fn.expand('%')) + end, + desc = 'Test: Watch file', + }, + { + ']t', + function() + require('neotest').jump.next({ status = 'failed' }) + end, + desc = 'Test: Next failed', + }, + { + '[t', + function() + require('neotest').jump.prev({ status = 'failed' }) + end, + desc = 'Test: Prev failed', + }, + { + 'tC', + function() + local ok, coverage = pcall(require, 'coverage') + if ok then + coverage.toggle() + end + end, + desc = 'Test: Toggle coverage', + }, + { + 'tL', + function() + local ok, coverage = pcall(require, 'coverage') + if ok then + coverage.load(true) + end + end, + desc = 'Test: Load coverage', + }, + }, + config = function() + local neotest = require('neotest') + + neotest.setup({ + adapters = { + require('neotest-python')({ + runner = 'pytest', + dap = { justMyCode = false }, + }), + require('neotest-gtest').setup({ + debug_adapter = 'codelldb', + }), + }, + output = { open_on_run = false }, + summary = { open = 'botright vsplit | vertical resize 60' }, + }) + + local ok_cov, coverage = pcall(require, 'coverage') + if ok_cov then + coverage.setup({ + auto_reload = true, + }) + end + end, + }, +} diff --git a/lua/custom/plugins/notify.lua b/lua/custom/plugins/notify.lua new file mode 100644 index 0000000..4257823 --- /dev/null +++ b/lua/custom/plugins/notify.lua @@ -0,0 +1,50 @@ +return { + 'rcarriga/nvim-notify', + opts = { + timeout = 5000, + stages = 'static', + }, + config = function(_, opts) + local notify = require('notify') + notify.setup(opts) + + -- Override vim.notify to filter out noisy LSP messages + local original_notify = vim.notify + vim.notify = function(msg, level, notify_opts) + -- Suppress Node.js warnings + if type(msg) == 'string' and msg:match('ExperimentalWarning: SQLite') then + return + end + if type(msg) == 'string' and msg:match('DeprecationWarning.*punycode') then + return + end + + -- Suppress copilot errors (harmless race conditions) + if type(msg) == 'string' and msg:match('Cannot find request.*whilst attempting to cancel') then + return + end + if type(msg) == 'string' and msg:match('AbortError: The operation was aborted') then + return + end + if type(msg) == 'string' and msg:match('rate limit exceeded') then + return + end + if type(msg) == 'string' and msg:match('Rate limited by server') then + return + end + + -- Suppress bashls parsing/formatting errors + if type(msg) == 'string' and msg:match('Error while parsing file://') then + return + end + if type(msg) == 'string' and msg:match('Error while formatting.*Shfmt') then + return + end + + original_notify(msg, level, notify_opts) + end + + -- Set as default notifier + vim.notify = notify + end, +} diff --git a/lua/custom/plugins/oil.lua b/lua/custom/plugins/oil.lua new file mode 100755 index 0000000..9ca2940 --- /dev/null +++ b/lua/custom/plugins/oil.lua @@ -0,0 +1,74 @@ +return { + 'stevearc/oil.nvim', + dependencies = { + 'nvim-tree/nvim-web-devicons', -- optional, for file icons + }, + config = function() + local oil = require('oil') + + oil.setup({ + columns = { + 'icon', + -- 'permissions', + }, + keymaps = { + ['C-h'] = false, + ['M-h'] = 'actions.select_split', + }, + view_options = { + show_hidden = true, + }, + float = { + padding = 2, + max_width = 80, + max_height = 30, + border = 'rounded', + win_options = { + winblend = 0, + }, + relative = 'editor', + }, + }) + + -- Monokai-like highlights + vim.api.nvim_set_hl(0, 'OilDir', { fg = '#A6E22E' }) + vim.api.nvim_set_hl(0, 'OilFile', { fg = '#D3D0C8' }) + vim.api.nvim_set_hl(0, 'OilHiddenFile', { fg = '#75715E' }) + vim.api.nvim_set_hl(0, 'OilProgress', { fg = '#66D9EF' }) + vim.api.nvim_set_hl(0, 'OilSymlink', { fg = '#F92672' }) + + -- Oil keymaps + vim.keymap.set('n', 'e', 'Oil', { noremap = true, silent = true, desc = 'Open parent directory' }) + vim.keymap.set('n', 'E', function() + oil.toggle_float() + end, { noremap = true, silent = true, desc = 'Toggle oil floating window' }) + + -- Add selected file in oil.nvim to Harpoon + -- same keymapping as if in a buffer + vim.keymap.set('n', 'a', function() + local ok_harpoon, harpoon = pcall(require, 'harpoon') + if not ok_harpoon then + vim.notify('Harpoon not found', vim.log.levels.WARN) + return + end + + local entry = oil.get_cursor_entry() + if not entry or not entry.name then + vim.notify('No valid entry selected in Oil', vim.log.levels.INFO) + return + end + + local full_path = oil.get_current_dir() .. entry.name + local stat = vim.loop.fs_stat(full_path) + if not stat or stat.type ~= 'file' then + vim.notify('Selected entry is not a file: ' .. full_path, vim.log.levels.INFO) + return + end + + harpoon:list():add({ + value = entry.name, + context = { filename = entry.name, cwd = oil.get_current_dir() or 'global' }, + }) + end, { desc = 'Add selected file in oil.nvim to Harpoon' }) + end, +} diff --git a/lua/custom/plugins/refactoring.lua b/lua/custom/plugins/refactoring.lua new file mode 100755 index 0000000..904feac --- /dev/null +++ b/lua/custom/plugins/refactoring.lua @@ -0,0 +1,12 @@ +return { + { + 'ThePrimeagen/refactoring.nvim', + event = 'VeryLazy', + dependencies = { + 'nvim-lua/plenary.nvim', + 'nvim-treesitter/nvim-treesitter', + }, + lazy = true, + opts = {}, + }, +} diff --git a/lua/custom/plugins/render-markdown.lua b/lua/custom/plugins/render-markdown.lua new file mode 100755 index 0000000..5f31277 --- /dev/null +++ b/lua/custom/plugins/render-markdown.lua @@ -0,0 +1,10 @@ +return { + 'MeanderingProgrammer/render-markdown.nvim', + ft = { 'markdown' }, + dependencies = { + 'nvim-tree/nvim-web-devicons', + }, + opts = { + -- Put any overrides here only if you need them. + }, +} diff --git a/lua/custom/plugins/schemastore.lua b/lua/custom/plugins/schemastore.lua new file mode 100755 index 0000000..6fae864 --- /dev/null +++ b/lua/custom/plugins/schemastore.lua @@ -0,0 +1,5 @@ +return { + 'b0o/schemastore.nvim', + ft = { 'josn', 'jsonc', 'json5', 'yaml', 'toml' }, + event = 'VeryLazy', +} diff --git a/lua/custom/plugins/slime.lua b/lua/custom/plugins/slime.lua new file mode 100755 index 0000000..1816c91 --- /dev/null +++ b/lua/custom/plugins/slime.lua @@ -0,0 +1,40 @@ +return { + 'jpalardy/vim-slime', + ft = { 'python' }, + keys = { + { 'RC', 'SlimeConfig', desc = 'Slime Config' }, + { + 'RS', + function() + if require('custom.user.ipython_utils').is_ipython_open() then + if vim.fn.mode() == 'v' then + -- Visual mode mapping + vim.cmd("'<,'>SlimeSend") + else + -- Normal mode mapping: Execute the SlimeSendCell and move to the next cell delimiter + vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('SlimeSendCell', true, true, true), 'm', true) + vim.cmd('normal! /^# %%\\') + end + else + vim.notify('No IPython REPL found. Open an IPython terminal first.', vim.log.levels.WARN) + end + end, + mode = { 'n', 'v' }, + }, + }, + init = function() + vim.g.slime_target = 'neovim' + vim.g.slime_no_mappings = true + end, + config = function() + -- Slime configuration + vim.g.slime_cell_delimiter = '# %%' + vim.g.slime_bracketed_paste = 1 + vim.g.slime_paste_file = os.getenv('HOME') .. '/.slime_paste' + vim.g.slime_input_pid = false + vim.g.slime_suggest_default = true + vim.g.slime_menu_config = false + vim.g.slime_neovim_ignore_unlisted = false + vim.g.slime_python_ipython = 0 + end, +} diff --git a/lua/custom/plugins/snippets.lua b/lua/custom/plugins/snippets.lua new file mode 100755 index 0000000..0fe99cd --- /dev/null +++ b/lua/custom/plugins/snippets.lua @@ -0,0 +1,42 @@ +return { + { + 'L3MON4D3/LuaSnip', + version = 'v2.*', -- Follows the latest major release version 2 + build = 'make install_jsregexp', -- Optional: install JavaScript-based regular expressions + + dependencies = { 'rafamadriz/friendly-snippets' }, + + config = function() + local luasnip = require('luasnip') + + -- Extend filetypes with specific snippets + luasnip.filetype_extend('javascript', { 'jsdoc' }) + luasnip.filetype_extend('python', { 'google' }) + + -- Key mappings for LuaSnip + vim.keymap.set({ 'i' }, 'e', function() + if luasnip.expand_or_jumpable() then + luasnip.expand() + end + end, { silent = true, desc = 'Expand snippet' }) + + vim.keymap.set({ 'i', 's' }, ';', function() + if luasnip.jumpable(1) then + luasnip.jump(1) + end + end, { silent = true, desc = 'Jump forward in snippet' }) + + vim.keymap.set({ 'i', 's' }, ',', function() + if luasnip.jumpable(-1) then + luasnip.jump(-1) + end + end, { silent = true, desc = 'Jump backward in snippet' }) + + vim.keymap.set({ 'i', 's' }, '', function() + if luasnip.choice_active() then + luasnip.change_choice(1) + end + end, { silent = true, desc = 'Cycle through snippet choices' }) + end, + }, +} diff --git a/lua/custom/plugins/solarized.lua b/lua/custom/plugins/solarized.lua new file mode 100755 index 0000000..b24e566 --- /dev/null +++ b/lua/custom/plugins/solarized.lua @@ -0,0 +1,7 @@ +return { + 'shaunsingh/solarized.nvim', + event = 'VeryLazy', + config = function() + vim.g.solarized_variant = 'light' + end, +} diff --git a/lua/custom/plugins/surround.lua b/lua/custom/plugins/surround.lua new file mode 100755 index 0000000..f4fac3c --- /dev/null +++ b/lua/custom/plugins/surround.lua @@ -0,0 +1,5 @@ +return { + 'tpope/vim-surround', + event = { 'BufRead', 'BufNewFile' }, + config = function() end, +} diff --git a/lua/custom/plugins/telescope-undo.lua b/lua/custom/plugins/telescope-undo.lua new file mode 100755 index 0000000..ca312bc --- /dev/null +++ b/lua/custom/plugins/telescope-undo.lua @@ -0,0 +1,44 @@ +return { + -- "debugloop/telescope-undo.nvim", + -- dependencies = { -- note how they're inverted to above example + -- { + -- "nvim-telescope/telescope.nvim", + -- dependencies = { "nvim-lua/plenary.nvim" }, + -- }, + -- }, + -- keys = { + -- { -- lazy style key map + -- "u", "Telescope undo", desc = "undo history", + -- }, + -- }, + -- opts = { + -- extensions = { + -- undo = { + -- mapping = { + -- i = { + -- [""] = require("telescope-undo.actions").yank_additions, + -- [""] = require("telescope-undo.actions").yank_deletions, + -- [""] = require("telescope-undo.actions").restore, + -- }, + -- n = { + -- ["y"] = require("telescope-undo.actions").yank_additions, + -- ["Y"] = require("telescope-undo.actions").yank_deletions, + -- ["u"] = require("telescope-undo.actions").restore, + -- }, + -- }, + -- side_by_side = true, + -- layout_strategy = "vertical", + -- layout_config = { + -- preview_height = 0.8, + -- }, + -- }, + -- }, + -- }, + -- config = function(_, opts) + -- -- Calling telescope's setup from multiple specs does not hurt, it will happily merge the + -- -- configs for us. We won't use data, as everything is in it's own namespace (telescope + -- -- defaults, as well as each extension). + -- require("telescope").setup(opts) + -- require("telescope").load_extension("undo") + -- end, +} diff --git a/lua/custom/plugins/telescope.lua b/lua/custom/plugins/telescope.lua new file mode 100755 index 0000000..57b19bc --- /dev/null +++ b/lua/custom/plugins/telescope.lua @@ -0,0 +1,96 @@ +return { + -- Fuzzy Finder (files, LSP, etc.) + 'nvim-telescope/telescope.nvim', + cmd = 'Telescope', + version = '*', + dependencies = { + 'nvim-lua/plenary.nvim', + 'debugloop/telescope-undo.nvim', -- Undo history extension + { + 'nvim-telescope/telescope-fzf-native.nvim', + build = 'make', + config = function() + require('telescope').setup({ + extensions = { + fzf = {}, + }, + }) + require('telescope').load_extension('fzf') + end, + cond = function() + return vim.fn.executable('make') == 1 + end, + }, + 'nvim-tree/nvim-web-devicons', -- Optional: Icons for UI + 'mbbill/undotree', -- Undotree dependency + -- 'b0o/schemastore.nvim', -- YAML schema support + }, + config = function() + local telescope = require('telescope') + + -- Configure Telescope + telescope.setup({ + defaults = { + -- prompt_prefix = '🔍 ', + sorting_strategy = 'descending', + layout_strategy = 'flex', + mappings = { + i = { + [''] = false, -- Disable Ctrl+u clearing input + [''] = false, -- Disable Ctrl+d clearing input + }, + }, + -- Attach the global mapping for centering the cursor on selection + attach_mappings = function(prompt_bufnr, _) + local actions = require('telescope.actions') + + -- When selecting a result, center it in the middle of the screen + actions.select_default:replace(function() + local line = actions.state.get_selected_entry().lnum + vim.api.nvim_win_set_cursor(0, { line, 0 }) + vim.cmd('normal! zz') -- This centers the line in the middle of the screen + actions.close(prompt_bufnr) -- Close the Telescope window + end) + + return true + end, + }, + extensions = { + undo = { + use_delta = true, -- Use delta for better diff visualization + }, + }, + }) + + -- Load the undo extension for Telescope + telescope.load_extension('undo') + + -- Load yaml schemas for Telescope + -- telescope.load_extension('yaml_schema') + + -- Function to find git root directory + local function find_git_root() + local current_file = vim.api.nvim_buf_get_name(0) + local current_dir = current_file ~= '' and vim.fn.fnamemodify(current_file, ':h') or vim.fn.getcwd() + local git_root = + vim.fn.systemlist('git -C ' .. vim.fn.escape(current_dir, ' ') .. ' rev-parse --show-toplevel')[1] + + if vim.v.shell_error ~= 0 then + print('Not a git repository, searching in current directory.') + return vim.fn.getcwd() + end + return git_root + end + + -- Function for live_grep within the Git root + local function live_grep_git_root() + local git_root = find_git_root() + if git_root then + require('telescope.builtin').live_grep({ search_dirs = { git_root } }) + end + end + + -- Command to trigger live_grep_git_root + vim.api.nvim_create_user_command('LiveGrepGitRoot', live_grep_git_root, { desc = 'Live grep in Git root' }) + end, +} diff --git a/lua/custom/plugins/terminal.lua b/lua/custom/plugins/terminal.lua new file mode 100755 index 0000000..48915d3 --- /dev/null +++ b/lua/custom/plugins/terminal.lua @@ -0,0 +1,11 @@ +return { + { + 'floaterminal', + dir = vim.fn.stdpath('config') .. '/lua/plugins', + config = function() + require('plugins.floaterminal').setup({ + title = 'Terminal', + }) + end, + }, +} diff --git a/lua/custom/plugins/todo-notes.lua b/lua/custom/plugins/todo-notes.lua new file mode 100755 index 0000000..73b4202 --- /dev/null +++ b/lua/custom/plugins/todo-notes.lua @@ -0,0 +1,7 @@ +-- Highlight todo, notes, etc in comments +return { + 'folke/todo-comments.nvim', + event = 'VimEnter', + dependencies = { 'nvim-lua/plenary.nvim' }, + opts = { signs = false }, +} diff --git a/lua/custom/plugins/tree-sitter.lua b/lua/custom/plugins/tree-sitter.lua new file mode 100755 index 0000000..a3f0f9c --- /dev/null +++ b/lua/custom/plugins/tree-sitter.lua @@ -0,0 +1,106 @@ +return { + 'nvim-treesitter/nvim-treesitter', + branch = 'master', + version = false, -- last release is too old and doesn't work on Windows + build = ':TSUpdate', + event = { 'VeryLazy' }, + lazy = vim.fn.argc(-1) == 0, -- load treesitter early when opening a file from the cmdline + init = function(plugin) + -- PERF: add nvim-treesitter queries to the rtp and its custom query predicates early + require('lazy.core.loader').add_to_rtp(plugin) + require('nvim-treesitter.query_predicates') + end, + cmd = { 'TSUpdateSync', 'TSUpdate', 'TSInstall' }, + keys = { + { '', desc = 'Increment Selection' }, + { '', desc = 'Decrement Selection', mode = 'x' }, + }, + opts_extend = { 'ensure_installed' }, + ---@type TSConfig + ---@diagnostic disable-next-line: missing-fields + opts = { + highlight = { enable = true }, + indent = { enable = true }, + ensure_installed = { + 'bash', + 'c', + 'html', + 'javascript', + 'jsdoc', + 'json', + 'jsonc', + 'lua', + 'luadoc', + 'markdown', + 'markdown_inline', + 'python', + 'query', + 'toml', + 'vim', + 'vimdoc', + 'yaml', + 'sql', + 'zig', + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = 'gnn', + node_incremental = 'grn', + scope_incremental = 'grc', + node_decremental = 'grm', + }, + }, + textobjects = { + select = { + enable = true, + lookahead = true, + keymaps = { + ['aa'] = '@parameter.outer', + ['ia'] = '@parameter.inner', + ['af'] = '@function.outer', + ['if'] = '@function.inner', + ['ac'] = '@class.outer', + ['ic'] = '@class.inner', + }, + }, + move = { + enable = true, + set_jumps = true, + goto_next_start = { + [']m'] = '@function.outer', + [']]'] = '@class.outer', + }, + goto_next_end = { + [']M'] = '@function.outer', + [']['] = '@class.outer', + }, + goto_previous_start = { + ['[m'] = '@function.outer', + ['[['] = '@class.outer', + }, + goto_previous_end = { + ['[M'] = '@function.outer', + ['[]'] = '@class.outer', + }, + }, + swap = { + enable = true, + swap_next = { + ['i'] = '@parameter.inner', + }, + swap_previous = { + ['I'] = '@parameter.inner', + }, + }, + }, + }, + ---@param opts TSConfig + config = function(_, opts) + -- Ensure no duplicates in the ensure_installed list + if type(opts.ensure_installed) == 'table' then + opts.ensure_installed = vim.tbl_deep_extend('force', {}, opts.ensure_installed) + end + require('nvim-treesitter.configs').setup(opts) + end, +} diff --git a/lua/custom/plugins/trouble.lua b/lua/custom/plugins/trouble.lua new file mode 100755 index 0000000..89a326a --- /dev/null +++ b/lua/custom/plugins/trouble.lua @@ -0,0 +1,39 @@ +return { + 'folke/trouble.nvim', + version = '*', + opts = {}, -- for default options, refer to the configuration section for custom setup. + cmd = 'Trouble', + event = 'LspAttach', + keys = { + { + 'xx', + 'Trouble diagnostics toggle', + desc = 'Diagnostics (Trouble)', + }, + { + 'xX', + 'Trouble diagnostics toggle filter.buf=0', + desc = 'Buffer Diagnostics (Trouble)', + }, + { + 'xs', + 'Trouble symbols toggle focus=false', + desc = 'Symbols (Trouble)', + }, + { + 'xl', + 'Trouble lsp toggle focus=false win.position=right', + desc = 'LSP Definitions / references / ... (Trouble)', + }, + { + 'xL', + 'Trouble loclist toggle', + desc = 'Location List (Trouble)', + }, + { + 'xQ', + 'Trouble qflist toggle', + desc = 'Quickfix List (Trouble)', + }, + }, +} diff --git a/lua/custom/plugins/undotree.lua b/lua/custom/plugins/undotree.lua new file mode 100755 index 0000000..a7d79b8 --- /dev/null +++ b/lua/custom/plugins/undotree.lua @@ -0,0 +1,9 @@ +return { + 'mbbill/undotree', + config = function() + -- Set Undotree to open on the right side + vim.g.undotree_WindowLayout = 4 + + vim.keymap.set('n', 'u', vim.cmd.UndotreeToggle, { desc = '[U]ndotree' }) + end, +} diff --git a/lua/custom/plugins/venv-selector.lua b/lua/custom/plugins/venv-selector.lua new file mode 100755 index 0000000..a3969c5 --- /dev/null +++ b/lua/custom/plugins/venv-selector.lua @@ -0,0 +1,29 @@ +return { + 'linux-cultist/venv-selector.nvim', + cmd = 'VenvSelect', + dependencies = { + 'neovim/nvim-lspconfig', + 'nvim-telescope/telescope.nvim', + -- Remove nvim-dap-python from here - it's defined separately + }, + opts = { + settings = { + options = { + activate_venv_in_terminal = true, + notify_user_on_venv_activation = true, + debug = true, + }, + search = { + pyenv = { + command = 'fd python$ -E "*lib*" -E "*init*" ${PYENV_ROOT}/versions/*.*.*/envs', + on_telescope_result_callback = function(filename) + return filename:gsub(os.getenv('HOME'), '~'):gsub('/bin/python', '') + end, + }, + }, + }, + }, + keys = { + { ',v', 'VenvSelect', desc = 'Select Virtual Environment' }, + }, +} diff --git a/lua/custom/plugins/vimtex.lua b/lua/custom/plugins/vimtex.lua new file mode 100755 index 0000000..94477b1 --- /dev/null +++ b/lua/custom/plugins/vimtex.lua @@ -0,0 +1,187 @@ +return { + 'lervag/vimtex', + ft = { 'tex', 'latex', 'cls', 'sty', 'bib' }, + lazy = false, + init = function() + -- macOS PDF viewer - Skim is better than Zathura on macOS + vim.g.vimtex_view_method = 'skim' + vim.g.vimtex_view_skim_sync = 1 + vim.g.vimtex_view_skim_activate = 1 + vim.g.vimtex_view_skim_reading_bar = 0 + vim.g.vimtex_view_skim_preserve_state = 1 + -- Alternative: use system default PDF viewer + -- vim.g.vimtex_view_method = 'general' + -- vim.g.vimtex_view_general_viewer = 'open' + + -- Compiler + vim.g.vimtex_compiler_method = 'latexmk' + vim.g.vimtex_compiler_latexmk_engines = { + _ = '-pdf -xelatex -synctex=1 -interaction=nonstopmode', + } + vim.g.vimtex_compiler_latexmk = { + build_dir = 'output', + aux_dir = 'output', + callback = 1, + continuous = 1, + executable = 'latexmk', + options = { + -- '-pdf', + '-verbose', + '-file-line-error', + -- '-synctex=1', + '-interaction=nonstopmode', + -- '-xelatex', + '-shell-escape', + '-outdir=output', + '-auxdir=output', + }, + } + + -- Disable problematic default mappings + vim.g.vimtex_mappings_disable = { + ['n'] = { 'K' }, + ['x'] = { 'K' }, + } + + -- Quickfix + vim.g.vimtex_quickfix_method = vim.fn.executable('pplatex') == 1 and 'pplatex' or 'latexlog' + vim.g.vimtex_quickfix_mode = 2 + vim.g.vimtex_quickfix_open_on_warning = 0 + vim.g.vimtex_quickfix_ignore_filters = { + 'Underfull \\hbox', + 'Overfull \\hbox', + 'LaTeX Warning: .+ float specifier changed to', + 'LaTeX hooks Warning', + 'Package hyperref Warning: Token not allowed in a PDF string', + 'Package fontspec Warning', + 'Package everypage Warning', + 'Package microtype Warning', + 'LaTeX Warning: Command \\. invalid in math mode', + 'Package babel Warning', + 'Package biblatex Warning', + } + + -- Folding + vim.g.vimtex_fold_enabled = 1 + vim.g.vimtex_fold_types = { + envs = { whitelist = { 'figure', 'table', 'equation', 'align' } }, + sections = { parse_levels = 1 }, + } + + -- Syntax + vim.g.vimtex_syntax_enabled = 1 + vim.g.vimtex_syntax_conceal = { + accents = 0, + ligatures = 0, + cites = 1, + fancy = 0, + spacing = 0, + greek = 1, + math_bounds = 0, + math_delimiters = 0, + math_fracs = 0, + math_super_sub = 1, + math_symbols = 1, + sections = 0, + } + end, + + config = function() + -- Auto-create output dir + vim.api.nvim_create_autocmd({ 'BufNewFile', 'BufRead' }, { + pattern = { '*.tex', '*.latex' }, + callback = function() + if vim.bo.filetype == 'tex' or vim.bo.filetype == 'latex' then + local output_dir = vim.fn.expand('%:p:h') .. '/output' + if vim.fn.isdirectory(output_dir) == 0 then + vim.fn.mkdir(output_dir, 'p') + end + end + end, + }) + + -- Toggle continuous compilation function + _G.vimtex_toggle_continuous = function() + if vim.g.vimtex_compiler_latexmk.continuous == 1 then + vim.g.vimtex_compiler_latexmk.continuous = 0 + print('Continuous compilation OFF') + vim.cmd('VimtexStop') + else + vim.g.vimtex_compiler_latexmk.continuous = 1 + print('Continuous compilation ON') + vim.cmd('VimtexCompile') + end + end + + -- Function to close Skim for current PDF + local function close_skim() + local file_name = vim.fn.expand('%:t:r') + if file_name ~= '' then + local pdf_name = file_name .. '.pdf' + -- AppleScript to close Skim window for specific PDF + local script = string.format( + [[ + tell application "System Events" + if (name of processes) contains "Skim" then + tell application "Skim" + set theWindows to every window whose name contains "%s" + repeat with theWindow in theWindows + close theWindow + end repeat + end tell + end if + end tell + ]], + pdf_name + ) + vim.fn.system({ 'osascript', '-e', script }) + end + end + + -- Auto-close Skim when LaTeX buffer is deleted or closed + vim.api.nvim_create_autocmd({ 'BufDelete', 'BufWipeout' }, { + pattern = { '*.tex', '*.latex' }, + callback = function() + close_skim() + end, + }) + + -- Also close when Neovim exits + vim.api.nvim_create_autocmd('VimLeave', { + pattern = { '*.tex', '*.latex' }, + callback = function() + close_skim() + end, + }) + end, + + -- Use VimTeX's standard keymaps - only add minimal custom ones + keys = { + -- Standard VimTeX mappings (these are the defaults, just making them explicit) + { 'll', 'VimtexCompile', desc = 'VimTeX: Compile', ft = { 'tex', 'latex' } }, + { 'lv', 'VimtexView', desc = 'VimTeX: View PDF', ft = { 'tex', 'latex' } }, + { 'ls', 'VimtexStop', desc = 'VimTeX: Stop', ft = { 'tex', 'latex' } }, + { 'lS', 'VimtexStopAll', desc = 'VimTeX: Stop All', ft = { 'tex', 'latex' } }, + { 'lc', 'VimtexClean', desc = 'VimTeX: Clean', ft = { 'tex', 'latex' } }, + { 'lC', 'VimtexClean!', desc = 'VimTeX: Clean All', ft = { 'tex', 'latex' } }, + { 'lq', 'VimtexErrors', desc = 'VimTeX: Errors', ft = { 'tex', 'latex' } }, + { 'lk', 'VimtexLog', desc = 'VimTeX: Log', ft = { 'tex', 'latex' } }, + { 'li', 'VimtexInfo', desc = 'VimTeX: Info', ft = { 'tex', 'latex' } }, + { 'lI', 'VimtexInfoFull', desc = 'VimTeX: Info Full', ft = { 'tex', 'latex' } }, + { 'lt', 'VimtexTocToggle', desc = 'VimTeX: TOC Toggle', ft = { 'tex', 'latex' } }, + { 'lT', 'VimtexTocOpen', desc = 'VimTeX: TOC Open', ft = { 'tex', 'latex' } }, + { 'lw', 'VimtexCountWords', desc = 'VimTeX: Count Words', ft = { 'tex', 'latex' } }, + { 'lW', 'VimtexCountLetters', desc = 'VimTeX: Count Letters', ft = { 'tex', 'latex' } }, + { 'lm', 'VimtexImapsList', desc = 'VimTeX: Imaps List', ft = { 'tex', 'latex' } }, + + -- Only one custom mapping for continuous compilation toggle + { + 'lR', + function() + _G.vimtex_toggle_continuous() + end, + desc = 'VimTeX: Toggle Continuous', + ft = { 'tex', 'latex' }, + }, + }, +} diff --git a/lua/custom/plugins/wezterm.lua b/lua/custom/plugins/wezterm.lua new file mode 100644 index 0000000..fffab95 --- /dev/null +++ b/lua/custom/plugins/wezterm.lua @@ -0,0 +1,6 @@ +return { + 'willothy/wezterm.nvim', + opts = { + create_commands = false + } +} diff --git a/lua/custom/plugins/which-key.lua b/lua/custom/plugins/which-key.lua new file mode 100755 index 0000000..2f1b000 --- /dev/null +++ b/lua/custom/plugins/which-key.lua @@ -0,0 +1,62 @@ +return { + 'folke/which-key.nvim', + event = 'VimEnter', -- Sets the loading event to 'VimEnter' + icons = { + mappings = true, + keys = { + Up = ' ', + Down = ' ', + Left = ' ', + Right = ' ', + C = ' ', + M = ' ', + D = ' ', + S = ' ', + CR = ' ', + Esc = ' ', + ScrollWheelDown = ' ', + ScrollWheelUp = ' ', + NL = ' ', + BS = ' ', + Space = ' ', + Tab = ' ', + F1 = '', + F2 = '', + F3 = '', + F4 = '', + F5 = '', + F6 = '', + F7 = '', + F8 = '', + F9 = '', + F10 = '', + F11 = '', + F12 = '', + }, + }, + opts = { + preset = 'helix', -- I like helix because I can still see the text, but others: modernm, classic + spec = { + { 'a', group = 'Harpoon', mode = { 'n', 'x' } }, + { 'b', group = '[B]uffer' }, + { 'c', group = '[C]ode/C++', mode = { 'n', 'x' } }, + { 'C', group = '[C]opilot', mode = {'n', 'x'} }, + { 'd', group = '[D]ebug' }, + { 'e', group = '[E]xplorer' }, + { 'f', group = '[F]iles' }, + { 'g', group = '[G]it' }, + { 'h', group = 'Git [H]unk', mode = { 'n', 'v' } }, + { 'j', group = '[J]ulia', mode = { 'n', 'x' } }, + { 'l', group = '[L]SP' }, + { 'p', group = '[P]aste', mode = { 'n', 'x' } }, + { 'R', group = '[R]un/REPL', mode = { 'n', 'x' } }, + { 'r', group = '[R]eplace' }, + { 's', group = '[S]earch' }, + { 't', group = '[T]est' }, + { 'u', group = '[U]I' }, + { 'w', group = '[W]orkspace' }, + { 'x', group = 'Trouble' }, + { 'z', group = '[Z]en' }, + }, + }, +} diff --git a/lua/custom/plugins/worktree.lua b/lua/custom/plugins/worktree.lua new file mode 100644 index 0000000..de8ebe1 --- /dev/null +++ b/lua/custom/plugins/worktree.lua @@ -0,0 +1,20 @@ +return { + 'ThePrimeagen/git-worktree.nvim', + config = function() + require('git-worktree').setup() + require('telescope').load_extension('git_worktree') + + vim.keymap.set( + 'n', + 'ga', + "lua require('telescope').extensions.git_worktree.git_worktree()", + { desc = 'Git Worktrees' } + ) + vim.keymap.set( + 'n', + 'gn', + "lua require('telescope').extensions.git_worktree.create_git_worktree()", + { desc = 'New Git Worktree' } + ) + end, +} diff --git a/lua/custom/plugins/zen-mode.lua b/lua/custom/plugins/zen-mode.lua new file mode 100755 index 0000000..1e01ae6 --- /dev/null +++ b/lua/custom/plugins/zen-mode.lua @@ -0,0 +1,42 @@ +return { + 'folke/zen-mode.nvim', + command = 'ZenMode', + opts = { + window = { + backdrop = 0.95, -- Transparency level for the zen mode window + width = 0.80, -- 80% of the total editor width + height = 1, -- Full height + options = { + signcolumn = 'no', -- Hide signcolumn in zen mode + number = true, -- Disable line numbers + relativenumber = true, -- Disable relative numbers + }, + }, + plugins = { + wezterm = { + enabled = true, + font = '+2', -- Increase font size in WezTerm by 2 + }, + }, + on_open = function() + -- Configure Neovim options when Zen Mode opens + vim.opt.ruler = false -- Hide ruler + vim.opt.showcmd = false -- Hide command feedback + vim.opt.laststatus = 0 -- Hide status line + end, + on_close = function() + -- Restore Neovim options when Zen Mode closes + vim.opt.ruler = true -- Show ruler + vim.opt.showcmd = true -- Show command feedback + vim.opt.laststatus = 2 -- Show status line + end, + }, + keys = { + { + 'zz', + 'ZenMode', + desc = 'Toggle Zen Mode', + silent = true, + }, + }, +} diff --git a/lua/custom/snippets/go.lua b/lua/custom/snippets/go.lua new file mode 100644 index 0000000..31f6dd8 --- /dev/null +++ b/lua/custom/snippets/go.lua @@ -0,0 +1,26 @@ +local ls = require('luasnip') + +local s = ls.snippet +local i = ls.insert_node +local t = ls.text_node + +local fmt = require('luasnip.extras.fmt').fmt + +ls.add_snippets('go', { + s('ee', { + t({ 'panic(' }), + i(1, 'err'), + t({ ')' }), + }), + s( + 'ei', + fmt( + [[ +if err != nil {{ + panic({}) +}} + ]], + { i(1, 'err') } + ) + ), +}) diff --git a/lua/custom/snippets/lua.lua b/lua/custom/snippets/lua.lua new file mode 100644 index 0000000..81d5d1f --- /dev/null +++ b/lua/custom/snippets/lua.lua @@ -0,0 +1,20 @@ +local ls = require 'luasnip' + +local s = ls.snippet +local i = ls.insert_node +local t = ls.text_node + +ls.add_snippets('lua', { + s('lr', { + t 'local ', + i(1, 'module'), + t ' = require("', + i(2, 'module'), + t '")', + }), + s('pr', { + t 'print(', + i(1, 'text'), + t ')', + }), +}) diff --git a/lua/custom/snippets/pyhton.lua b/lua/custom/snippets/pyhton.lua new file mode 100644 index 0000000..d0a07d1 --- /dev/null +++ b/lua/custom/snippets/pyhton.lua @@ -0,0 +1,51 @@ +local ls = require('luasnip') + +local s = ls.snippet +local i = ls.insert_node +local t = ls.text_node +local d = ls.dynamic_node +local sn = ls.snippet_node + +-- Helper to parse function args and create insert nodes +local function generate_arg_docs() + local line = vim.api.nvim_get_current_line() + local args = line:match('def%s+[%w_]+%((.*)%)') + if not args then + return sn(nil, { t('Args:') }) + end + + local nodes = { t('Args:') } + local index = 1 + + for arg in args:gmatch('[^,%s]+') do + table.insert(nodes, t({ '', ' ' .. arg .. ': ' })) + table.insert(nodes, i(index)) + index = index + 1 + end + + return sn(nil, nodes) +end + +-- Add Python snippets +ls.add_snippets('python', { + s('log', { + t({ 'LOG.' }), + i(1, 'level'), + t({ '(' }), + i(2, 'message'), + t({ ')' }), + }), + + s('#!', { + t({ '#!/usr/bin/env python' }), + }), + + -- Docstring with Args (interactive) and Returns + s('doc', { + t({ '"""' }), + d(1, generate_arg_docs, {}), + t({ '', '', 'Returns:', ' ' }), + i(2, 'return_value_description'), + t({ '', '"""' }), + }), +}) diff --git a/lua/custom/user/init.lua b/lua/custom/user/init.lua new file mode 100644 index 0000000..8a2a983 --- /dev/null +++ b/lua/custom/user/init.lua @@ -0,0 +1,2 @@ +-- Load user-created modules +require('custom.user.ipython_utils') diff --git a/lua/custom/user/ipython_utils.lua b/lua/custom/user/ipython_utils.lua new file mode 100644 index 0000000..97b8347 --- /dev/null +++ b/lua/custom/user/ipython_utils.lua @@ -0,0 +1,24 @@ +local M = {} + +-- Function to check if an IPython REPL is open in Neovim panes +function M.is_ipython_open() + for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(bufnr) and vim.api.nvim_get_option_value('buftype', { buf = bufnr }) == 'terminal' then + if vim.b[bufnr].python_repl == true then + return true + end + + -- Get first few lines to check if it's an IPython REPL + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, 10, false) + for _, line in ipairs(lines) do + -- Specific checks for IPython + if line:match('IPython') or line:match('In %[%d+%]:') or line:match('In %[') then + return true + end + end + end + end + return false +end + +return M diff --git a/lua/kickstart/plugins/autoformat.lua b/lua/kickstart/plugins/autoformat.lua new file mode 100755 index 0000000..5b41b22 --- /dev/null +++ b/lua/kickstart/plugins/autoformat.lua @@ -0,0 +1,76 @@ +-- autoformat.lua +-- +-- Use your language server to automatically format your code on save. +-- Adds additional commands as well to manage the behavior + +return { + 'neovim/nvim-lspconfig', + config = function() + -- Switch for controlling whether you want autoformatting. + -- Use :KickstartFormatToggle to toggle autoformatting on or off + local format_is_enabled = true + vim.api.nvim_create_user_command('KickstartFormatToggle', function() + format_is_enabled = not format_is_enabled + print('Setting autoformatting to: ' .. tostring(format_is_enabled)) + end, {}) + + -- Create an augroup that is used for managing our formatting autocmds. + -- We need one augroup per client to make sure that multiple clients + -- can attach to the same buffer without interfering with each other. + local _augroups = {} + local get_augroup = function(client) + if not _augroups[client.id] then + local group_name = 'kickstart-lsp-format-' .. client.name + local id = vim.api.nvim_create_augroup(group_name, { clear = true }) + _augroups[client.id] = id + end + + return _augroups[client.id] + end + + -- Whenever an LSP attaches to a buffer, we will run this function. + -- + -- See `:help LspAttach` for more information about this autocmd event. + vim.api.nvim_create_autocmd('LspAttach', { + group = vim.api.nvim_create_augroup('kickstart-lsp-attach-format', { clear = true }), + -- This is where we attach the autoformatting for reasonable clients + callback = function(args) + local client_id = args.data.client_id + local client = vim.lsp.get_client_by_id(client_id) + local bufnr = args.buf + if not client then + return + end + -- Only attach to clients that support document formatting + if not client.server_capabilities.documentFormattingProvider then + return + end + + -- Tsserver usually works poorly. Sorry you work with bad languages + -- You can remove this line if you know what you're doing :) + if client.name == 'tsserver' then + return + end + + -- Create an autocmd that will run *before* we save the buffer. + -- Run the formatting command for the LSP that has just attached. + vim.api.nvim_create_autocmd('BufWritePre', { + group = get_augroup(client), + buffer = bufnr, + callback = function() + if not format_is_enabled then + return + end + + vim.lsp.buf.format({ + async = false, + filter = function(c) + return c.id == client.id + end, + }) + end, + }) + end, + }) + end, +} diff --git a/lua/kickstart/plugins/debug.lua b/lua/kickstart/plugins/debug.lua new file mode 100755 index 0000000..be83291 --- /dev/null +++ b/lua/kickstart/plugins/debug.lua @@ -0,0 +1,88 @@ +-- debug.lua +-- +-- Shows how to use the DAP plugin to debug your code. +-- +-- Primarily focused on configuring the debugger for Go, but can +-- be extended to other languages as well. That's why it's called +-- kickstart.nvim and not kitchen-sink.nvim ;) + +return { + -- NOTE: Yes, you can install new plugins here! + 'mfussenegger/nvim-dap', + -- NOTE: And you can specify dependencies as well + dependencies = { + -- Creates a beautiful debugger UI + 'rcarriga/nvim-dap-ui', + + -- Installs the debug adapters for you + 'williamboman/mason.nvim', + 'jay-babu/mason-nvim-dap.nvim', + + -- Add your own debuggers here + 'leoluz/nvim-dap-go', + }, + config = function() + local dap = require('dap') + local dapui = require('dapui') + + require('mason-nvim-dap').setup({ + -- Makes a best effort to setup the various debuggers with + -- reasonable debug configurations + automatic_installation = true, + automatic_setup = true, + + -- You can provide additional configuration to the handlers, + -- see mason-nvim-dap README for more information + handlers = {}, + + -- You'll need to check that you have the required things installed + -- online, please don't ask me how to install them :) + ensure_installed = { + -- Update this to ensure that you have the debuggers for the langs you want + 'delve', + }, + }) + + -- Basic debugging keymaps, feel free to change to your liking! + vim.keymap.set('n', '', dap.continue, { desc = 'Debug: Start/Continue' }) + vim.keymap.set('n', '', dap.step_into, { desc = 'Debug: Step Into' }) + vim.keymap.set('n', '', dap.step_over, { desc = 'Debug: Step Over' }) + vim.keymap.set('n', '', dap.step_out, { desc = 'Debug: Step Out' }) + vim.keymap.set('n', 'b', dap.toggle_breakpoint, { desc = 'Debug: Toggle Breakpoint' }) + vim.keymap.set('n', 'B', function() + dap.set_breakpoint(vim.fn.input('Breakpoint condition: ')) + end, { desc = 'Debug: Set Breakpoint' }) + + -- Dap UI setup + -- For more information, see |:help nvim-dap-ui| + dapui.setup({ + -- Set icons to characters that are more likely to work in every terminal. + -- Feel free to remove or use ones that you like more! :) + -- Don't feel like these are good choices. + icons = { expanded = '▾', collapsed = '▸', current_frame = '*' }, + controls = { + icons = { + pause = '⏸', + play = '▶', + step_into = '⏎', + step_over = '⏭', + step_out = '⏮', + step_back = 'b', + run_last = '▶▶', + terminate = '⏹', + disconnect = '⏏', + }, + }, + }) + + -- Toggle to see last session result. Without this, you can't see session output in case of unhandled exception. + vim.keymap.set('n', '', dapui.toggle, { desc = 'Debug: See last session result.' }) + + dap.listeners.after.event_initialized['dapui_config'] = dapui.open + dap.listeners.before.event_terminated['dapui_config'] = dapui.close + dap.listeners.before.event_exited['dapui_config'] = dapui.close + + -- Install golang specific config + require('dap-go').setup() + end, +} diff --git a/lua/plugins/floaterminal.lua b/lua/plugins/floaterminal.lua new file mode 100755 index 0000000..aaab6a3 --- /dev/null +++ b/lua/plugins/floaterminal.lua @@ -0,0 +1,126 @@ +local M = {} + +-- State to manage the floating terminal +M.state = { + floating = { + buf = -1, + win = -1, + }, +} + +-- Helper function to validate a window +local function is_valid_window(win) + return win and vim.api.nvim_win_is_valid(win) +end + +-- Helper function to validate a buffer +local function is_valid_buffer(buf) + return buf and vim.api.nvim_buf_is_valid(buf) +end + +-- Create or update the floating terminal +local function create_floating_terminal(opts) + opts = opts or {} + + -- Calculate dimensions and position + local width = opts.width or math.floor(vim.o.columns * 0.8) + local height = opts.height or math.floor(vim.o.lines * 0.8) + local col = math.floor((vim.o.columns - width) / 2) + local row = math.floor((vim.o.lines - height) / 2) + + -- Ensure buffer is valid or create a new one + local buf = is_valid_buffer(opts.buf) and opts.buf or vim.api.nvim_create_buf(false, true) + + -- Window configuration + local win_config = { + relative = 'editor', + width = width, + height = height, + col = col, + row = row, + style = 'minimal', + border = opts.border or 'rounded', + title = opts.title or 'Terminal', + title_pos = 'center', + } + + -- Create the floating window + local ok, win = pcall(vim.api.nvim_open_win, buf, true, win_config) + if not ok then + vim.notify('Failed to create floating window: ' .. win, vim.log.levels.ERROR) + return nil + end + + return { buf = buf, win = win } +end + +-- Toggle the floating terminal +function M.toggle_terminal(opts) + opts = opts or {} + + if not is_valid_window(M.state.floating.win) then + -- Create and initialize the floating terminal + M.state.floating = create_floating_terminal({ + buf = M.state.floating.buf, + width = opts.width, + height = opts.height, + border = opts.border, + title = opts.title, + }) + + if M.state.floating and is_valid_buffer(M.state.floating.buf) then + -- Ensure the buffer is a terminal + local buf = M.state.floating.buf + if vim.bo[buf].buftype ~= 'terminal' then + vim.api.nvim_set_current_buf(buf) + vim.cmd('startinsert') + vim.fn.termopen(os.getenv('SHELL') or 'sh') + end + end + else + -- Close the floating window + pcall(vim.api.nvim_win_close, M.state.floating.win, true) + M.state.floating.win = nil + end +end + +-- Setup function to initialize the module +function M.setup(opts) + opts = opts or {} + + -- Create a user command for toggling the terminal + vim.api.nvim_create_user_command('FloatTermToggle', function() + M.toggle_terminal(opts) + end, {}) + + -- Apply key mappings + M.keys(opts.mapping) +end + +-- Apply key mappings +function M.keys(mapping) + local default_keymap = { + ['ft'] = { + mode = { 'n', 't' }, + cmd = 'FloatTermToggle', + options = { noremap = true, silent = true, desc = 'Toggle floating terminal' }, + }, + } + + -- Use custom or default mappings + local keymaps = mapping or default_keymap + + for key, map in pairs(keymaps) do + if map.desc then + map.options = vim.tbl_extend('force', map.options or {}, { desc = map.desc }) + end + + for _, mode in ipairs(map.mode) do + pcall(vim.keymap.del, mode, key) + end + + vim.keymap.set(map.mode, key, map.cmd, map.options) + end +end + +return M