diff --git a/nvim/lua/th b/.nojekyll similarity index 100% rename from nvim/lua/th rename to .nojekyll diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 370cde6..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 CrtlUserKnown - -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 index ab953b7..b35868f 100644 --- a/README.md +++ b/README.md @@ -161,3 +161,4 @@ Plugins will be installed automatically via `lazy.nvim` on first launch. --- Built by [CrtlUserKnown](https://github.com/CrtlUserKnown) + diff --git a/about.html b/about.html new file mode 100644 index 0000000..76dc2e8 --- /dev/null +++ b/about.html @@ -0,0 +1,229 @@ + + + + + + About | CharVim + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +

What is CharVim?

+

A hand-crafted Neovim configuration built for developers who want a fast, modern editor without having to stitch plugins together themselves.

+
+ +
+ + +
+ +

The idea

+

+ CharVim started as a personal Neovim config and grew into a shareable, opinionated distribution. The goal is simple: give you a Neovim that feels like a real IDE, with LSP, completion, debugging, AI, fuzzy-finding, and multi-cursor support, while staying lean, fast, and true to Vim's modal nature. +

+

+ Every plugin in CharVim earns its place. Nothing is included just because it's popular. The configuration is built around a daily-driver workflow: writing code, navigating large projects, debugging, and staying in flow. +

+ +

Philosophy

+ + +

Language support

+

CharVim ships with ten pre-configured LSP servers, covering the most common languages for backend, frontend, systems, and document authoring:

+ +

Inlay hints are enabled by default on all servers that support them and can be toggled with leaderih.

+ +

The themes

+

CharVim ships with three themes, all switchable at runtime via :ThemeSelect or :Theme <name>. Your selection persists across restarts.

+ +

Additional themes available inside Neovim include Tokyo Night, Catppuccin, Gruvbox, and Habamax.

+ +

AI integration

+

+ avante.nvim connects CharVim to Anthropic Claude (claude-haiku-4-5) for AI-assisted code chat and editing. Use :AvanteAsk to discuss code in context, or :AvanteEdit to apply AI suggestions directly. Add your ANTHROPIC_API_KEY to a .env file at the repo root and it gets loaded on startup automatically. +

+ +

Installation

+

Run the one-line install script. It detects your OS, installs missing system dependencies, and symlinks the config into ~/.config/nvim.

+
+ bash <(curl -s https://raw.githubusercontent.com/CrtlUserKnown/Charvim/main/setup.sh) + +
+

Or clone the repository manually and launch Neovim. Plugins install automatically via lazy.nvim on first start.

+ +

Requirements

+ + +

User customization

+

Two git-ignored files let you personalize CharVim without touching core config. They are auto-created with template comments on first startup and never overwritten by updates.

+ +

nvim/lua/user.lua

+

Loaded last, after all CharVim modules. Add keymaps, options, autocommands, or override any default:

+
-- nvim/lua/user.lua
+vim.opt.wrap = true
+vim.keymap.set('n', '<leader>x', ':!echo hello<CR>', { desc = 'My command' })
+ +

nvim/lua/user-plugins.lua

+

Return a lazy.nvim spec list to add your own plugins:

+
-- nvim/lua/user-plugins.lua
+return {
+    { 'folke/zen-mode.nvim', opts = {} },
+}
+ +
+ Read the full docs +   + See the roadmap +
+ +
+ +
+ + + + + + + diff --git a/changelog.html b/changelog.html new file mode 100644 index 0000000..6d6051f --- /dev/null +++ b/changelog.html @@ -0,0 +1,402 @@ + + + + + + Changelog | CharVim + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +

Changelog

+

Every addition, change, fix, and removal across every release of CharVim, starting from the very beginning.

+
+ +
+ + +
+ + +
+
+ v1.5.2 + Latest + June 26, 2026 +
+

Cross-Platform Setup

+

+ This release rewrites the setup script to support macOS (Homebrew), Debian/Ubuntu (apt), and Arch Linux (pacman) alongside the existing Fedora support. Each platform function checks for missing binaries before installing, keeping installs minimal. The README was also updated with detailed LSP and linter reference tables. +

+
+
+ +
    +
  • macOS support via Homebrew
  • +
  • Debian and Ubuntu support via apt
  • +
  • Arch Linux support via pacman
  • +
  • Per-platform missing-binary checks before installation
  • +
+
+
+ +
    +
  • Setup script refactored from a single Fedora-only function to a per-platform design
  • +
  • Dependency list expanded to include make and node
  • +
  • README now includes LSP server and linter tables for quick reference
  • +
  • README updated with per-distro package manager table and expanded prerequisites
  • +
+
+
+
+ + +
+
+ v1.5.1 + June 26, 2026 +
+

Find and Replace Panel, Theme Persistence, AI Switch to Claude

+

+ This release replaces the old inline rename with a full find and replace panel backed by ripgrep. The theme switcher was rewritten with live Telescope preview and cross-session persistence. avante.nvim was switched from a local Ollama model to Anthropic Claude. +

+
+
+ +
    +
  • New rename-config.lua module with a full find and replace panel using nui.nvim
  • +
  • Live ripgrep search with file preview
  • +
  • Single-occurrence and project-wide regex replace
  • +
  • LSP rename integration when the cursor is on a known symbol
  • +
  • Persistent undo via undofile = true
  • +
  • Theme selection now persists across sessions
  • +
  • Telescope-powered live-preview theme picker
  • +
  • "Auto" theme (Neovim default) added as the first option
  • +
  • Fedora dependency auto-install via dnf in setup.sh
  • +
+
+
+ +
    +
  • avante.nvim provider switched from Ollama (qwen2.5-coder:3b) to Anthropic Claude (claude-haiku-4-5)
  • +
  • Auto-suggestions disabled; accept key changed from Shift+Tab to Alt+Enter
  • +
  • Old project_rename function removed in favor of the new rename-config module
  • +
  • <leader>r which-key group label changed from "Refactor" to "Run"
  • +
  • c and cpp removed from sourcekit-lsp filetypes
  • +
  • .env at the repo root is now auto-loaded on startup
  • +
+
+
+ +
    +
  • .env, CLAUDE.md, and GEMINI.md added to .gitignore
  • +
+
+
+
+ + +
+
+ v1.5.0 + June 17, 2026 +
+

AI Assistance, Multi-Theme Support, and Integrated Linting

+

+ This release introduces local AI code completion, multiple theme support, and integrated linting. GitHub Copilot was removed in favor of a fully local AI solution. The completion stack was restored to nvim-cmp with LuaSnip, and the LSP setup was migrated from mason-lspconfig to direct lspconfig for greater control. +

+
+
+ +
    +
  • avante.nvim with Ollama and Qwen 2.5 Coder for local AI code completion
  • +
  • Tokyo Night theme
  • +
  • Catppuccin (Mocha) theme
  • +
  • Gruvbox theme
  • +
  • theme-switcher module for runtime theme cycling
  • +
  • lint-config.lua with nvim-lint for configurable linting
  • +
  • Custom noir-cat colorscheme in nvim/colors/
  • +
  • make.lua ftplugin for Makefile-specific settings
  • +
+
+
+ +
    +
  • Completion reverted from native LSP back to nvim-cmp with LuaSnip snippets
  • +
  • LSP replaced mason-lspconfig.nvim with direct lspconfig setup for greater control
  • +
  • Inlay hints added across all configured LSP clients
  • +
  • Statusline updated to dynamically reflect the active colorscheme
  • +
  • noice.nvim popupmenu backend disabled
  • +
  • lazy.nvim clone depth set to 1 and timeout increased to 120s
  • +
+
+
+ +
    +
  • GitHub Copilot (zbirenbaum/copilot.lua) and all related configuration
  • +
+
+
+
+ + +
+
+ v1.3.0 + May 6, 2026 +
+

Neovim 0.12 Compatibility and Optimization

+

+ This release focuses on compatibility and optimization for Neovim 0.12, including the transition to native completion and internal refactors to support the latest editor APIs. File runners for HTML, Python, and Java were added alongside Mason integration in the dashboard. +

+
+
+ +
    +
  • Mason integration in the alpha-nvim dashboard
  • +
  • Keybindings and user commands to run HTML, Python, and Java files directly
  • +
  • nvim-ts-autotag as a Treesitter dependency for HTML/XML tag management
  • +
+
+
+ +
    +
  • Major internal refactoring for Neovim 0.12 compatibility
  • +
  • Switched to native LSP completion; removed nvim-cmp and related plugins
  • +
  • Statusline refactored for better performance and mode-specific colors
  • +
  • Treesitter updated to use the new main branch
  • +
  • Dashboard button layout streamlined and which-key presets updated
  • +
+
+
+ +
    +
  • Updated LSP capability checks to use client:supports_method()
  • +
  • Treesitter parser retrieval updated for the new API
  • +
  • Improved reliability of setup.sh in CI and GitHub Actions environments
  • +
  • Corrected scrolloff which was set as a global Lua variable instead of a vim option
  • +
+
+
+
+ + +
+
+ v1.2.0 + March 29, 2026 +
+

Project Management and CI

+

+ Added project.nvim for smart project detection and workspace switching, integrated with Telescope. GitHub Actions CI was set up for automated Neovim configuration testing. +

+
+
+ +
    +
  • project.nvim for project detection and management via Telescope
  • +
  • GitHub Actions workflow for automated Neovim configuration testing
  • +
+
+
+ +
    +
  • README cleaned up by removing unnecessary emoji
  • +
  • .DS_Store added to .gitignore
  • +
+
+
+ +
    +
  • Old .nvim.bak version from the repository
  • +
+
+
+
+ + +
+
+ v1.1.0 + March 10, 2026 +
+

Debugger Integration and Setup Script

+

+ DAP debuggers were integrated for Java, Swift, Python, and C/C++ with customized UI layouts and session management. A setup.sh install script was added for easier environment setup, and COBOL language support was improved through specialized ftplugins. +

+
+
+ +
    +
  • DAP debuggers for Java, Swift, Python, and C/C++ with UI auto-open/close
  • +
  • setup.sh for easier installation and environment configuration
  • +
  • COBOL ftplugin with 8-space tabs, column markers at 7 and 73
  • +
  • GitHub Actions workflow for automated setup testing
  • +
+
+
+ +
    +
  • Symlink creation and repository source detection issues during installation
  • +
+
+
+
+ + +
+
+ v0.1.0 + March 5, 2026 +
+

Initial Release

+

+ The first public release of CharVim. A complete Neovim configuration with comprehensive LSP support, GitHub Copilot AI, a startup dashboard, fuzzy finding, quick file navigation, and tmux integration. The project also launched with an official GitHub Wiki. +

+
+
+ +
    +
  • GitHub Copilot for AI-assisted coding
  • +
  • Renamed entry point to charvim, allowing use of cvim command
  • +
  • Comprehensive LSP configuration via mason.nvim
  • +
  • Specialized handlers for Java (JDTLS), Typst (Tinymist), and Go (gopls)
  • +
  • alpha-nvim dashboard with ASCII art logo
  • +
  • noice.nvim for enhanced cmdline and popups
  • +
  • rose-pine colorscheme (Knew-pines variant)
  • +
  • telescope.nvim for fuzzy finding and file browsing
  • +
  • harpoon2 for quick file switching
  • +
  • vim-tmux-navigator for seamless pane navigation
  • +
  • nvim-surround for delimiter manipulation
  • +
  • vim-visual-multi for multiple cursors
  • +
  • Official project documentation via GitHub Wiki
  • +
+
+
+ +
    +
  • Initial project structure and configuration migration from personal dotfiles
  • +
+
+
+
+ +
+ View raw CHANGELOG on GitHub ↗ +
+ +
+ +
+ + + + + + + diff --git a/css/style.css b/css/style.css new file mode 100644 index 0000000..b5f515b --- /dev/null +++ b/css/style.css @@ -0,0 +1,1377 @@ +/* === FONTS === */ +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,700;1,400&family=Courier+Prime:wght@400;700&family=Open+Sans:wght@300;400;600;700&display=swap'); + +/* === THEME VARIABLES === */ +:root, +[data-theme="knew-pines"] { + --bg: #191724; + --bg-alt: #1a1823; + --surface: #1f1d2e; + --overlay: #26233a; + --muted: #6e6a86; + --subtle: #908caa; + --text: #e0def4; + --text-dim: #908caa; + + --love: #eb6f92; + --gold: #f6c177; + --rose: #ebbcba; + --pine: #31748f; + --foam: #9ccfd8; + --iris: #c4a7e7; + + --accent: #eb6f92; + --accent2: #c4a7e7; + --accent3: #9ccfd8; + + --logo-primary: #e0def4; + --logo-accent: #f6c177; + + --glow-a: rgba(235, 111, 146, 0.18); + --glow-b: rgba(196, 167, 231, 0.12); + + --border: rgba(110, 106, 134, 0.3); + --card-bg: rgba(31, 29, 46, 0.8); + --nav-bg: rgba(25, 23, 36, 0.88); +} + +[data-theme="noir-cat"] { + --bg: #1a1a1a; + --bg-alt: #181818; + --surface: #222222; + --overlay: #313244; + --muted: #585b70; + --subtle: #7f849c; + --text: #d4d4d4; + --text-dim: #a6adc8; + + --love: #f38ba8; + --gold: #fab387; + --rose: #f5c2e7; + --pine: #89b4fa; + --foam: #89dceb; + --iris: #cba6f7; + + --accent: #89b4fa; + --accent2: #cba6f7; + --accent3: #89dceb; + + --logo-primary: #d4d4d4; + --logo-accent: #cba6f7; + + --glow-a: rgba(137, 180, 250, 0.16); + --glow-b: rgba(203, 166, 247, 0.12); + + --border: rgba(88, 91, 112, 0.38); + --card-bg: rgba(34, 34, 34, 0.88); + --nav-bg: rgba(26, 26, 26, 0.92); +} + +[data-theme="dark"] { + --bg: #0d1117; + --bg-alt: #090d12; + --surface: #161b22; + --overlay: #21262d; + --muted: #484f58; + --subtle: #6e7681; + --text: #c9d1d9; + --text-dim: #8b949e; + + --love: #ff7b72; + --gold: #ffa657; + --rose: #ffdcd7; + --pine: #3fb950; + --foam: #39c5cf; + --iris: #d2a8ff; + + --accent: #58a6ff; + --accent2: #d2a8ff; + --accent3: #3fb950; + + --logo-primary: #c9d1d9; + --logo-accent: #58a6ff; + + --glow-a: rgba(88, 166, 255, 0.16); + --glow-b: rgba(210, 168, 255, 0.10); + + --border: rgba(72, 79, 88, 0.42); + --card-bg: rgba(22, 27, 34, 0.92); + --nav-bg: rgba(13, 17, 23, 0.92); +} + +/* === RESET === */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + scroll-behavior: smooth; + --gx: 50%; + --gy: 50%; +} + +body { + font-family: 'Open Sans', sans-serif; + background-color: var(--bg); + color: var(--text); + min-height: 100vh; + overflow-x: hidden; + transition: background-color 0.4s ease, color 0.4s ease; + position: relative; +} + +/* === CURSOR GRADIENT LAYER === */ +.cursor-gradient { + position: fixed; + inset: 0; + pointer-events: none; + z-index: 0; + background: radial-gradient( + ellipse 700px 500px at var(--gx) var(--gy), + var(--glow-a) 0%, + var(--glow-b) 40%, + transparent 70% + ); +} + +/* === NAVIGATION === */ +nav { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 200; + height: 64px; + background: var(--nav-bg); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-bottom: 1px solid var(--border); + padding: 0 2rem; + display: flex; + align-items: center; + justify-content: space-between; + transition: background 0.4s ease, border-color 0.4s ease; +} + +.nav-brand { + display: flex; + align-items: center; + gap: 0.55rem; + text-decoration: none; + color: var(--text); +} + +.nav-brand-logo { + width: 26px; + height: 26px; +} + +.nav-brand-name { + font-family: 'JetBrains Mono', monospace; + font-weight: 700; + font-size: 1rem; + letter-spacing: -0.02em; + color: var(--text); + transition: color 0.4s; +} + +.nav-links { + display: flex; + align-items: center; + gap: 2rem; + list-style: none; +} + +.nav-links a { + font-size: 0.875rem; + font-weight: 600; + color: var(--text-dim); + text-decoration: none; + transition: color 0.2s; + letter-spacing: 0.01em; +} + +.nav-links a:hover { color: var(--text); } +.nav-links a.active { color: var(--accent); } + +/* === THEME SWITCHER === */ +.theme-switcher { + display: flex; + align-items: center; + gap: 3px; + background: var(--overlay); + border: 1px solid var(--border); + border-radius: 22px; + padding: 4px; + transition: background 0.4s, border-color 0.4s; +} + +.theme-btn { + background: none; + border: none; + cursor: pointer; + padding: 4px 11px; + border-radius: 18px; + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + font-weight: 700; + color: var(--muted); + transition: all 0.2s; + white-space: nowrap; + letter-spacing: 0.04em; +} + +.theme-btn:hover { color: var(--text); } + +.theme-btn.active { + background: var(--accent); + color: var(--bg); +} + +/* === HAMBURGER === */ +.hamburger { + display: none; + flex-direction: column; + gap: 5px; + cursor: pointer; + background: none; + border: none; + padding: 4px; +} + +.hamburger span { + width: 22px; + height: 2px; + background: var(--text); + border-radius: 2px; + transition: all 0.3s; + display: block; +} + +/* === MAIN WRAPPER === */ +main { + position: relative; + z-index: 1; +} + +/* === HERO === */ +.hero { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: 80px 2rem 3rem; + position: relative; +} + +.hero-logo-wrap { + margin-bottom: 2rem; +} + +.hero-logo { + width: 110px; + height: 110px; + filter: drop-shadow(0 0 32px var(--glow-a)); + transition: filter 0.4s ease; +} + +.logo-primary { + fill: var(--logo-primary); + transition: fill 0.4s ease; +} + +.logo-accent { + fill: var(--logo-accent); + transition: fill 0.4s ease; +} + +.hero-name { + font-family: 'JetBrains Mono', monospace; + font-size: clamp(2.75rem, 7vw, 5rem); + font-weight: 700; + color: var(--text); + letter-spacing: -0.035em; + margin-bottom: 0.85rem; + transition: color 0.4s ease; + line-height: 1.05; +} + +.hero-tagline { + font-size: clamp(1rem, 2vw, 1.2rem); + color: var(--text-dim); + font-weight: 300; + max-width: 480px; + line-height: 1.65; + margin-bottom: 2.5rem; + letter-spacing: 0.01em; + transition: color 0.4s ease; +} + +.hero-cta { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; + margin-bottom: 5rem; +} + +/* === BUTTONS === */ +.btn { + font-family: 'Open Sans', sans-serif; + font-weight: 600; + font-size: 0.9rem; + padding: 0.75rem 1.75rem; + border-radius: 8px; + text-decoration: none; + cursor: pointer; + border: none; + transition: all 0.2s; + letter-spacing: 0.01em; + display: inline-flex; + align-items: center; + gap: 0.4rem; +} + +.btn-primary { + background: var(--accent); + color: var(--bg); +} + +.btn-primary:hover { + opacity: 0.88; + transform: translateY(-2px); + box-shadow: 0 8px 24px var(--glow-a); +} + +.btn-secondary { + background: var(--overlay); + color: var(--text); + border: 1px solid var(--border); +} + +.btn-secondary:hover { + background: var(--surface); + transform: translateY(-2px); + border-color: var(--subtle); +} + +/* === SCROLL INDICATOR === */ +.scroll-indicator { + position: absolute; + bottom: 2.5rem; + left: 50%; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; + color: var(--muted); + font-family: 'JetBrains Mono', monospace; + font-size: 0.6rem; + text-transform: uppercase; + letter-spacing: 0.15em; + animation: bobble 2.2s ease-in-out infinite; +} + +.scroll-chevron { + width: 16px; + height: 16px; + border-right: 2px solid currentColor; + border-bottom: 2px solid currentColor; + transform: rotate(45deg); +} + +@keyframes bobble { + 0%, 100% { transform: translateX(-50%) translateY(0); opacity: 0.5; } + 50% { transform: translateX(-50%) translateY(7px); opacity: 1; } +} + +/* === GRADIENT DIVIDER === */ +.gradient-divider { + height: 1px; + background: linear-gradient(to right, + transparent, + var(--accent) 25%, + var(--accent2) 75%, + transparent + ); + opacity: 0.35; +} + +/* === SECTION SCAFFOLDING === */ +.section { + padding: 6rem 2rem; +} + +.section-inner { + max-width: 1200px; + margin: 0 auto; +} + +.section-label { + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.18em; + color: var(--accent); + margin-bottom: 0.6rem; + transition: color 0.4s; +} + +.section-title { + font-family: 'Courier Prime', serif; + font-size: clamp(1.75rem, 4vw, 2.75rem); + font-weight: 700; + color: var(--text); + margin-bottom: 0.9rem; + transition: color 0.4s; +} + +.section-subtitle { + font-size: 1rem; + color: var(--text-dim); + max-width: 580px; + line-height: 1.75; + margin-bottom: 3rem; + transition: color 0.4s; +} + +/* === GRADIENT TEXT === */ +.gradient-text { + background: linear-gradient(135deg, var(--accent), var(--accent2)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* === CARDS === */ +.cards-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; +} + +.card { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 14px; + padding: 1.75rem; + transition: all 0.3s; + position: relative; + overflow: hidden; + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); +} + +.card::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient(135deg, var(--glow-a), transparent 65%); + opacity: 0; + transition: opacity 0.3s; + border-radius: 14px; + pointer-events: none; +} + +.card:hover { + border-color: var(--accent); + transform: translateY(-3px); + box-shadow: 0 12px 40px rgba(0,0,0,0.25); +} + +.card:hover::before { opacity: 1; } + +.card-icon { + font-size: 1.6rem; + margin-bottom: 1rem; + display: block; +} + +.card-title { + font-family: 'Courier Prime', serif; + font-size: 1.1rem; + font-weight: 700; + color: var(--text); + margin-bottom: 0.5rem; + transition: color 0.4s; +} + +.card-desc { + font-size: 0.875rem; + color: var(--text-dim); + line-height: 1.65; + transition: color 0.4s; +} + +/* === STATS === */ +.stats-band { + background: var(--surface); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 4rem 2rem; + transition: background 0.4s, border-color 0.4s; +} + +.stats-inner { + max-width: 900px; + margin: 0 auto; + display: flex; + gap: 3rem; + justify-content: center; + flex-wrap: wrap; +} + +.stat-item { text-align: center; } + +.stat-num { + font-family: 'JetBrains Mono', monospace; + font-size: 2.5rem; + font-weight: 700; + color: var(--accent); + display: block; + line-height: 1; + margin-bottom: 0.35rem; + transition: color 0.4s; +} + +.stat-label { + font-size: 0.8rem; + color: var(--text-dim); + text-transform: uppercase; + letter-spacing: 0.12em; + font-weight: 600; + transition: color 0.4s; +} + +/* === THEME PREVIEW SECTION === */ +.theme-previews { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1.5rem; + margin-top: 3rem; +} + +.theme-preview { + border-radius: 12px; + overflow: hidden; + border: 2px solid var(--border); + cursor: pointer; + transition: all 0.3s; + user-select: none; +} + +.theme-preview:hover { + transform: translateY(-4px); + box-shadow: 0 16px 48px rgba(0,0,0,0.3); +} + +.theme-preview.active-preview { + border-color: var(--accent); + box-shadow: 0 0 0 1px var(--accent), 0 12px 40px rgba(0,0,0,0.25); +} + +.tp-header { + padding: 0.65rem 1rem; + display: flex; + align-items: center; + gap: 0.45rem; + font-family: 'JetBrains Mono', monospace; + font-size: 0.72rem; + font-weight: 700; +} + +.dot { + width: 10px; + height: 10px; + border-radius: 50%; +} + +.tp-body { + padding: 1.1rem 1.25rem; + font-family: 'JetBrains Mono', monospace; + font-size: 0.72rem; + line-height: 2; +} + +/* knew-pines */ +.tp-kp { background: #191724; } +.tp-kp .tp-header { background: #1f1d2e; color: #e0def4; } +.tp-kp .dot-r { background: #eb6f92; } +.tp-kp .dot-y { background: #f6c177; } +.tp-kp .dot-g { background: #9ccfd8; } +.tp-kp .syn-kw { color: #c4a7e7; } +.tp-kp .syn-fn { color: #9ccfd8; } +.tp-kp .syn-str { color: #9ccfd8; } +.tp-kp .syn-cmt { color: #6e6a86; font-style: italic; } +.tp-kp .syn-var { color: #e0def4; } +.tp-kp .syn-num { color: #f6c177; } +.tp-kp .syn-op { color: #ebbcba; } + +/* noir-cat */ +.tp-nc { background: #1a1a1a; } +.tp-nc .tp-header { background: #313244; color: #d4d4d4; } +.tp-nc .dot-r { background: #f38ba8; } +.tp-nc .dot-y { background: #fab387; } +.tp-nc .dot-g { background: #a6e3a1; } +.tp-nc .syn-kw { color: #cba6f7; } +.tp-nc .syn-fn { color: #89b4fa; } +.tp-nc .syn-str { color: #a6e3a1; } +.tp-nc .syn-cmt { color: #585b70; font-style: italic; } +.tp-nc .syn-var { color: #d4d4d4; } +.tp-nc .syn-num { color: #fab387; } +.tp-nc .syn-op { color: #f38ba8; } + +/* dark */ +.tp-dk { background: #0d1117; } +.tp-dk .tp-header { background: #161b22; color: #c9d1d9; } +.tp-dk .dot-r { background: #ff7b72; } +.tp-dk .dot-y { background: #ffa657; } +.tp-dk .dot-g { background: #3fb950; } +.tp-dk .syn-kw { color: #ff7b72; } +.tp-dk .syn-fn { color: #d2a8ff; } +.tp-dk .syn-str { color: #a5d6ff; } +.tp-dk .syn-cmt { color: #8b949e; font-style: italic; } +.tp-dk .syn-var { color: #c9d1d9; } +.tp-dk .syn-num { color: #ffa657; } +.tp-dk .syn-op { color: #58a6ff; } + +/* === INSTALL BAND === */ +.install-band { + background: var(--surface); + border-top: 1px solid var(--border); + border-bottom: 1px solid var(--border); + padding: 5rem 2rem; + text-align: center; + transition: background 0.4s, border-color 0.4s; +} + +.code-block { + display: inline-flex; + align-items: center; + gap: 1rem; + background: var(--overlay); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.9rem 1.4rem; + font-family: 'JetBrains Mono', monospace; + font-size: 0.88rem; + color: var(--text); + max-width: 100%; + margin-top: 1.5rem; + transition: background 0.4s, border-color 0.4s; +} + +.code-block code { color: var(--foam); transition: color 0.4s; } + +.copy-btn { + background: none; + border: none; + cursor: pointer; + color: var(--muted); + transition: color 0.2s; + font-size: 1rem; + padding: 2px; + line-height: 1; +} + +.copy-btn:hover { color: var(--text); } + +/* === FOOTER === */ +footer { + background: var(--surface); + border-top: 1px solid var(--border); + padding: 3.5rem 2rem; + transition: background 0.4s, border-color 0.4s; +} + +.footer-inner { + max-width: 1100px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + text-align: center; +} + +.footer-brand { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.footer-logo { width: 34px; height: 34px; } + +.footer-name { + font-family: 'JetBrains Mono', monospace; + font-size: 1rem; + font-weight: 700; + color: var(--text); + transition: color 0.4s; +} + +.footer-links { + display: flex; + gap: 2rem; + list-style: none; + flex-wrap: wrap; + justify-content: center; +} + +.footer-links a { + font-size: 0.875rem; + color: var(--text-dim); + text-decoration: none; + transition: color 0.2s; +} + +.footer-links a:hover { color: var(--accent); } + +.footer-copy { + font-size: 0.78rem; + color: var(--muted); + transition: color 0.4s; +} + +/* === PAGE HERO (inner pages) === */ +.page-hero { + padding: 140px 2rem 5rem; + text-align: center; + position: relative; +} + +.page-hero .section-title { + font-size: clamp(2rem, 5vw, 3.5rem); + margin-bottom: 1rem; +} + +.page-hero .section-subtitle { + margin: 0 auto; + max-width: 560px; +} + +/* === ABOUT CONTENT === */ +.content-section { + max-width: 860px; + margin: 0 auto; + padding: 2rem 2rem 5rem; +} + +.content-section h2 { + font-family: 'Courier Prime', serif; + font-size: 1.65rem; + font-weight: 700; + color: var(--text); + margin: 3rem 0 1rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid var(--border); + transition: color 0.4s, border-color 0.4s; +} + +.content-section h2:first-of-type { margin-top: 0; } + +.content-section h3 { + font-family: 'Courier Prime', serif; + font-size: 1.2rem; + font-weight: 700; + color: var(--text); + margin: 2rem 0 0.6rem; + transition: color 0.4s; +} + +.content-section p { + color: var(--text-dim); + line-height: 1.82; + margin-bottom: 1rem; + transition: color 0.4s; +} + +.content-section ul, .content-section ol { + color: var(--text-dim); + line-height: 1.8; + padding-left: 1.6rem; + margin-bottom: 1rem; + transition: color 0.4s; +} + +.content-section li { margin-bottom: 0.4rem; } + +.content-section code { + font-family: 'JetBrains Mono', monospace; + font-size: 0.83em; + background: var(--overlay); + padding: 2px 6px; + border-radius: 4px; + color: var(--foam); + transition: background 0.4s, color 0.4s; +} + +.content-section pre { + background: var(--overlay); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.25rem; + overflow-x: auto; + margin: 1.25rem 0; + transition: background 0.4s, border-color 0.4s; +} + +.content-section pre code { + background: none; + padding: 0; + font-size: 0.85rem; + color: var(--text); +} + +/* === DOCS LAYOUT === */ +.docs-wrap { + display: grid; + grid-template-columns: 260px 1fr; + max-width: 1200px; + margin: 80px auto 0; + min-height: calc(100vh - 80px); +} + +.docs-sidebar { + position: -webkit-sticky; + position: sticky; + top: 80px; + height: calc(100vh - 80px); + overflow-y: auto; + padding: 2rem 1.25rem; + border-right: 1px solid var(--border); + scrollbar-width: thin; + scrollbar-color: var(--overlay) transparent; + transition: border-color 0.4s; +} + +.sidebar-group { margin-bottom: 2rem; } + +.sidebar-group-title { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--accent); + padding: 0 0.5rem; + margin-bottom: 0.5rem; + transition: color 0.4s; +} + +.sidebar-links { list-style: none; } + +.sidebar-links a { + display: block; + font-size: 0.845rem; + color: var(--text-dim); + text-decoration: none; + padding: 0.3rem 0.6rem; + border-radius: 5px; + transition: all 0.15s; + margin-bottom: 1px; +} + +.sidebar-links a:hover { color: var(--text); background: var(--overlay); } +.sidebar-links a.active { color: var(--accent); background: var(--overlay); } + +.docs-content { + padding: 3rem 3rem; + max-width: 820px; +} + +.docs-content h1 { + font-family: 'Courier Prime', serif; + font-size: 2.1rem; + font-weight: 700; + color: var(--text); + margin-bottom: 0.4rem; + transition: color 0.4s; +} + +.docs-lead { + font-size: 1rem; + color: var(--text-dim); + margin-bottom: 2.5rem; + line-height: 1.7; + transition: color 0.4s; +} + +.docs-content h2 { + font-family: 'Courier Prime', serif; + font-size: 1.5rem; + font-weight: 700; + color: var(--text); + margin: 2.8rem 0 0.9rem; + padding-bottom: 0.4rem; + border-bottom: 1px solid var(--border); + scroll-margin-top: 90px; + transition: color 0.4s, border-color 0.4s; +} + +.docs-content h3 { + font-family: 'Courier Prime', serif; + font-size: 1.15rem; + font-weight: 700; + color: var(--text); + margin: 2rem 0 0.7rem; + scroll-margin-top: 90px; + transition: color 0.4s; +} + +.docs-content p { + color: var(--text-dim); + line-height: 1.82; + margin-bottom: 1rem; + font-size: 0.94rem; + transition: color 0.4s; +} + +.docs-content ul, .docs-content ol { + color: var(--text-dim); + padding-left: 1.5rem; + margin-bottom: 1rem; + line-height: 1.8; + font-size: 0.94rem; + transition: color 0.4s; +} + +.docs-content li { margin-bottom: 0.3rem; } + +.docs-content code { + font-family: 'JetBrains Mono', monospace; + font-size: 0.81em; + background: var(--overlay); + padding: 2px 5px; + border-radius: 4px; + color: var(--foam); + transition: background 0.4s, color 0.4s; +} + +.docs-content pre { + background: var(--overlay); + border: 1px solid var(--border); + border-radius: 8px; + padding: 1.25rem; + overflow-x: auto; + margin: 1.25rem 0; + transition: background 0.4s, border-color 0.4s; +} + +.docs-content pre code { + background: none; + padding: 0; + font-size: 0.84rem; + color: var(--text); +} + +.docs-content table { + width: 100%; + border-collapse: collapse; + margin: 1.25rem 0; + font-size: 0.875rem; + overflow: hidden; + border-radius: 8px; +} + +.docs-content th { + background: var(--overlay); + color: var(--text); + font-family: 'JetBrains Mono', monospace; + font-size: 0.72rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; + padding: 0.65rem 1rem; + text-align: left; + border: 1px solid var(--border); + transition: background 0.4s, color 0.4s, border-color 0.4s; +} + +.docs-content td { + padding: 0.58rem 1rem; + color: var(--text-dim); + border: 1px solid var(--border); + vertical-align: top; + transition: color 0.4s, border-color 0.4s; +} + +.docs-content tr:nth-child(even) td { + background: rgba(255,255,255,0.018); +} + +.docs-content kbd { + font-family: 'JetBrains Mono', monospace; + font-size: 0.73em; + background: var(--overlay); + border: 1px solid var(--border); + border-bottom-width: 2px; + padding: 1px 6px; + border-radius: 4px; + color: var(--text); + white-space: nowrap; + transition: background 0.4s, color 0.4s, border-color 0.4s; +} + +.docs-tip { + background: var(--overlay); + border-left: 3px solid var(--foam); + border-radius: 0 8px 8px 0; + padding: 1rem 1.25rem; + margin: 1.25rem 0; + font-size: 0.875rem; + color: var(--text-dim); + line-height: 1.7; + transition: background 0.4s, border-color 0.4s, color 0.4s; +} + +.docs-warn { + background: var(--overlay); + border-left: 3px solid var(--love); + border-radius: 0 8px 8px 0; + padding: 1rem 1.25rem; + margin: 1.25rem 0; + font-size: 0.875rem; + color: var(--text-dim); + line-height: 1.7; + transition: background 0.4s, border-color 0.4s, color 0.4s; +} + +.docs-tip strong, .docs-warn strong { + font-family: 'JetBrains Mono', monospace; + font-size: 0.75em; + text-transform: uppercase; + letter-spacing: 0.1em; + display: block; + margin-bottom: 0.3rem; +} + +.docs-tip strong { color: var(--foam); } +.docs-warn strong { color: var(--love); } + +/* === ROADMAP === */ +.roadmap-wrap { + max-width: 780px; + margin: 0 auto; + padding: 0 2rem 6rem; +} + +.timeline { + position: relative; + padding: 1rem 0; +} + +.timeline::before { + content: ''; + position: absolute; + left: 22px; + top: 0; + bottom: 0; + width: 2px; + background: linear-gradient(to bottom, var(--accent), var(--accent2) 60%, transparent); + opacity: 0.5; +} + +.tl-item { + position: relative; + padding-left: 64px; + margin-bottom: 2.75rem; +} + +.tl-dot { + position: absolute; + left: 14px; + top: 4px; + width: 18px; + height: 18px; + border-radius: 50%; + background: var(--accent); + border: 3px solid var(--bg); + z-index: 1; + transition: background 0.4s, border-color 0.4s; +} + +.tl-dot.planned { + background: transparent; + border-color: var(--accent); + border-style: dashed; +} + +.tl-version { + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + font-weight: 700; + color: var(--accent); + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 0.25rem; + transition: color 0.4s; +} + +.tl-date { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + color: var(--muted); + margin-bottom: 0.5rem; + transition: color 0.4s; +} + +.tl-title { + font-family: 'Courier Prime', serif; + font-size: 1.25rem; + font-weight: 700; + color: var(--text); + margin-bottom: 0.75rem; + transition: color 0.4s; +} + +.tl-card { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 10px; + padding: 1.25rem; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + transition: background 0.4s, border-color 0.4s; +} + +.tl-desc { + font-size: 0.9rem; + color: var(--text-dim); + line-height: 1.72; + margin-bottom: 1rem; + transition: color 0.4s; +} + +.tag-list { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; +} + +.tag { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + font-weight: 700; + padding: 3px 9px; + border-radius: 20px; + border: 1px solid; + letter-spacing: 0.04em; + text-transform: uppercase; + transition: all 0.4s; +} + +.tag-added { color: var(--foam); border-color: var(--foam); background: rgba(156,207,216,0.08); } +.tag-changed { color: var(--gold); border-color: var(--gold); background: rgba(246,193,119,0.08); } +.tag-fixed { color: var(--love); border-color: var(--love); background: rgba(235,111,146,0.08); } +.tag-removed { color: var(--muted); border-color: var(--muted); background: transparent; } +.tag-planned { color: var(--iris); border-color: var(--iris); background: rgba(196,167,231,0.08); } + +/* === VERSION BADGE === */ +.version-badge { + display: inline-flex; + align-items: center; + gap: 0.55rem; + padding: 0.32rem 0.9rem; + background: var(--overlay); + border: 1px solid var(--border); + border-radius: 20px; + font-family: 'JetBrains Mono', monospace; + margin-bottom: 1.75rem; + font-size: 0.76rem; + text-decoration: none; + color: inherit; + transition: border-color 0.25s, background 0.4s; +} + +.version-badge:hover { border-color: var(--accent); } +.v-num { color: var(--accent); font-weight: 700; transition: color 0.4s; } +.v-label { color: var(--muted); transition: color 0.4s; } +.v-sep { width: 1px; height: 11px; background: var(--border); flex-shrink: 0; transition: background 0.4s; } + +/* === CHANGELOG === */ +.cl-wrap { + max-width: 880px; + margin: 0 auto; + padding: 0 2rem 6rem; +} + +.cl-entry { + margin-bottom: 3rem; + padding-bottom: 3rem; + border-bottom: 1px solid var(--border); + transition: border-color 0.4s; +} + +.cl-entry:last-child { border-bottom: none; } + +.cl-header { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.65rem; + margin-bottom: 0.6rem; +} + +.cl-version { + font-family: 'JetBrains Mono', monospace; + font-size: 1.15rem; + font-weight: 700; + color: var(--accent); + transition: color 0.4s; +} + +.cl-badge { + font-family: 'JetBrains Mono', monospace; + font-size: 0.63rem; + font-weight: 700; + padding: 2px 8px; + border-radius: 20px; + text-transform: uppercase; + letter-spacing: 0.08em; +} + +.cl-badge.latest { background: var(--accent); color: var(--bg); } + +.cl-date { + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + color: var(--muted); + margin-left: auto; + transition: color 0.4s; +} + +.cl-title { + font-family: 'Courier Prime', serif; + font-size: 1.35rem; + font-weight: 700; + color: var(--text); + margin-bottom: 0.65rem; + transition: color 0.4s; +} + +.cl-summary { + font-size: 0.92rem; + color: var(--text-dim); + line-height: 1.78; + margin-bottom: 1.4rem; + transition: color 0.4s; +} + +.cl-sections { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1rem; +} + +.cl-section { + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 8px; + padding: 0.9rem 1rem; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + transition: background 0.4s, border-color 0.4s; +} + +.cl-section-label { + font-family: 'JetBrains Mono', monospace; + font-size: 0.63rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.12em; + margin-bottom: 0.55rem; + display: block; + transition: color 0.4s; +} + +.cl-section-label.added { color: var(--foam); } +.cl-section-label.changed { color: var(--gold); } +.cl-section-label.fixed { color: var(--love); } +.cl-section-label.removed { color: var(--muted); } + +.cl-list { + list-style: none; + padding: 0; + margin: 0; +} + +.cl-list li { + font-size: 0.82rem; + color: var(--text-dim); + padding: 0.15rem 0 0.15rem 1rem; + position: relative; + line-height: 1.55; + transition: color 0.4s; +} + +.cl-list li::before { + content: '▸'; + position: absolute; + left: 0; + font-size: 0.58rem; + top: 0.33rem; +} + +.cl-list.added li::before { color: var(--foam); } +.cl-list.changed li::before { color: var(--gold); } +.cl-list.fixed li::before { color: var(--love); } +.cl-list.removed li::before { color: var(--muted); } + +/* === RESPONSIVE === */ +@media (max-width: 900px) { + .docs-wrap { grid-template-columns: 1fr; } + + .docs-sidebar { + position: relative; + top: 0; + height: auto; + border-right: none; + border-bottom: 1px solid var(--border); + padding: 1.5rem; + } + + .docs-content { padding: 2rem 1.5rem; } +} + +@media (max-width: 640px) { + .hamburger { display: flex; } + + .nav-links { + position: fixed; + top: 64px; + left: 0; + right: 0; + background: var(--nav-bg); + backdrop-filter: blur(24px); + -webkit-backdrop-filter: blur(24px); + border-bottom: 1px solid var(--border); + flex-direction: column; + padding: 1.5rem 2rem; + gap: 1.25rem; + transform: translateY(-110%); + transition: transform 0.3s cubic-bezier(0.4,0,0.2,1); + align-items: flex-start; + z-index: 190; + } + + .nav-links.open { transform: translateY(0); } + .theme-switcher { display: none; } + + .hero-name { font-size: clamp(2.2rem, 12vw, 3.5rem); } + + .stats-inner { gap: 2rem; } + + .timeline::before { left: 14px; } + .tl-item { padding-left: 48px; } + .tl-dot { left: 6px; width: 16px; height: 16px; } + + .footer-links { gap: 1.25rem; } +} diff --git a/docs.html b/docs.html new file mode 100644 index 0000000..bc2fd9f --- /dev/null +++ b/docs.html @@ -0,0 +1,703 @@ + + + + + + Documentation | CharVim + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ +

Documentation

+

Complete reference for CharVim, a custom Neovim 0.12+ configuration managed by lazy.nvim. The entry point is nvim/init.lua and all modules live in nvim/lua/.

+ + +

Installation

+

Run the install script. It auto-detects your OS and installs missing system dependencies before symlinking the config into ~/.config/nvim.

+
+ bash <(curl -s https://raw.githubusercontent.com/CrtlUserKnown/Charvim/main/setup.sh) + +
+ +

Supported package managers:

+ + + + + + + + +
OS / DistroPackage manager
macOSbrew
Fedora / RHELdnf
Debian / Ubuntuapt
Arch Linuxpacman
+ +

Manual install

+
    +
  1. Back up your existing config: mv ~/.config/nvim ~/.config/nvim.bak
  2. +
  3. Clone the repository: git clone https://github.com/CrtlUserKnown/Charvim.git ~/.config/nvim
  4. +
  5. Launch Neovim: nvim
  6. +
+

Plugins install automatically via lazy.nvim on first launch.

+ + +

Requirements

+
    +
  • Neovim 0.12+
  • +
  • Git
  • +
  • A Nerd Font for icons
  • +
  • ripgrep for Telescope live grep and Find and Replace
  • +
  • fd for Telescope file finding
  • +
  • python3 for DAP Python support
  • +
  • make for building avante.nvim
  • +
  • node for Mason-managed LSP servers
  • +
  • ANTHROPIC_API_KEY (optional) for AI features via avante.nvim
  • +
+ + +

How Vim Works

+

Vim (and Neovim) is a modal editor. Keys do different things depending on the active mode. Press Esc to return to Normal mode from anywhere.

+ + + + + + + + +
ModeHow to enterPurpose
NormalEscNavigation and commands. This is the default mode.
Inserti / a / oTyping text
Visualv / V / Ctrl+vSelecting blocks of text
Command-line: / /Saving, quitting, searching
+ +
+ Tip + Efficiency in Vim comes from staying in Normal mode and combining operators with motions: dw deletes a word, yy yanks a line, ci" changes text inside quotes. +
+ + +

Leader Keys

+ + + + + + +
KeyValue
<leader>Space
<localleader>\
+

Leader key timeout: 500 ms.

+ + +

General

+ + + + + + + + + + +
KeysModeAction
jkInsertEscape to Normal mode
leader wNormalQuick save (:w)
leader WNormalSave popup (Save / Save As / Format & Save / Save All)
leader qNormalSave and quit (:wq)
leader qqNormalQuit without saving (:q!)
;NormalShell command shortcut (:!)
+ + + + + + + + + + + + + + +
KeysModeAction
[[NormalGo to beginning of file (gg)
]]NormalGo to end of file (G)
HNormal / VisualGo to beginning of line (0)
LNormal / VisualGo to end of line ($)
Alt+[Normal / VisualPrevious paragraph ({)
Alt+]Normal / VisualNext paragraph (})
Ctrl+h/j/k/lNormalNavigate splits (also integrates with tmux panes)
+ + +

Line Manipulation

+ + + + + + + + + +
KeysModeAction
Alt+k / Alt+jNormalMove line up / down
Alt+k / Alt+jVisualMove selection up / down
Ctrl+k / Ctrl+jInsertMove line up / down
Ctrl+Shift+k/jNormalDuplicate line up / down
Ctrl+Shift+k/jVisualDuplicate selection up / down
+ + +

File Explorer

+ + + + + + + + +
KeysModeAction
leader eNormalOpen file browser at current file's directory
leader ENormalOpen file browser at project root
leader tNormalOpen new tab with file browser
:E / :TreeCommandOpen file browser
+ + +

Telescope

+ + + + + + + +
KeysContextAction
leader gNormalLive grep
leader pNormalProjects picker
Ctrl+.Telescope insertToggle hidden files
+

Hidden files are shown by default. .git/ directories are always ignored.

+ + +

Harpoon 2

+ + + + + + + + + + +
KeysModeAction
leader aNormalAdd current file to Harpoon list
leader arNormalRemove current file from Harpoon list
leader acNormalClear all Harpoon marks
leader hNormalToggle Harpoon quick menu
Alt+15NormalJump to Harpoon file 1–5
Alt+n / Alt+pNormalNext / previous Harpoon file
+ + +

Multi-Cursor

+ + + + + + + + + +
KeysModeAction
Ctrl+nNormalEnter multi-cursor mode
Ctrl+Normal / VisualAdd cursor above
Ctrl+Normal / VisualAdd cursor below
j / kMulti-cursor activeMove down/up and extend cursors
EscMulti-cursor activeExit multi-cursor mode
+

In multi-cursor mode, entering Insert types at all cursor positions simultaneously.

+ + +

Find & Replace / Rename

+ + + + + + + +
KeysModeAction
leader SNormalOpen find & replace panel
leader SVisualOpen panel pre-filled with selection
:FindCommandSame as leader S
+ +

Panel navigation:

+ + + + + + + + + + +
KeysContextAction
TabAny panelCycle focus: Find → Replace → Results
j / kResults listNavigate matches
yResults listAccept replacement for highlighted match
YAny panelAccept replacement for all matches
EnterResults listJump to match in file
Esc / qAny panelClose
+

When the find term matches an identifier with an active LSP client, Y uses LSP rename instead of regex substitution.

+ + +

LSP

+ + + + + + + + + + + + + + + + + +
KeysModeAction
KNormalHover documentation
gdNormalGo to definition
gDNormalGo to declaration
giNormalGo to implementation
goNormalGo to type definition
grNormalShow references
gsNormalSignature help
leader lrNormalRename symbol (LSP)
leader caNormalCode action
leader leNormalOpen diagnostic float
[d / ]dNormalPrevious / next diagnostic
leader ihNormalToggle inlay hints
leader igNormalAdd word to dictionary (ltex/typos)
+ +

Configured LSP Servers

+ + + + + + + + + + + + + + +
ServerLanguagesNotes
lua_lsLuaAuto-installed via Mason
pyrightPythonAuto-installed via Mason
ts_lsTypeScript / JavaScript / VueAuto-installed via Mason
jdtlsJavaPer-project workspace, GoogleStyle format
kotlin_language_serverKotlinJVM target 17, type & param inlay hints
goplsGogofumpt, full inlay hints, shadow analysis
clangdC / C++ / Obj-CAuto-installed via Mason
tinymistTypstExports PDF on save
sourcekit-lspSwift / Obj-CSystem binary; lazy-enabled on filetype
lemminxXMLAuto-installed via Mason
+ + +

Debugging (DAP)

+ + + + + + + + + + + + + + + +
KeysModeAction
leader dbNormalToggle breakpoint
leader dBNormalSet conditional breakpoint
leader dcNormalContinue
leader dnNormalStep over
leader diNormalStep into
leader doNormalStep out
leader drNormalOpen REPL
leader dlNormalRun last configuration
leader dtNormalTerminate session
leader duNormalToggle DAP UI
leader dpNormalDebug Python method under cursor
+ + + + + + + + + + +
LanguageAdapter
Pythondap-python
Javajdtls built-in
C / C++codelldb
Swiftcodelldb
Kotlinkotlin-debug-adapter
+

The DAP UI opens automatically when a session starts and closes when it ends.

+ + +

AI (avante.nvim)

+ + + + + + + +
KeysModeAction
Alt+EnterInsertAccept inline suggestion
Alt+]InsertNext suggestion
Ctrl+]InsertDismiss suggestion
+

Requires ANTHROPIC_API_KEY set in a .env file at the repo root. The file is auto-loaded on startup.

+ + +

Themes

+ + + + + + + +
Keys / CommandAction
:ThemeSelectOpen interactive theme picker with Telescope live-preview
:Theme <name>Switch directly to a named theme
Dashboard tOpen theme picker from the dashboard
+

Available theme names: Auto, Knew-pines, Noir-cat, Tokyo Night, Catppuccin, Gruvbox, Habamax. Selection persists to ~/.local/share/nvim/charvim_theme.

+ + +

UI Plugins

+
    +
  • noice.nvim: Replaces the command line with a centered popup.
  • +
  • alpha-nvim: Dashboard with ASCII art logo and a two-column quick-access grid.
  • +
  • which-key.nvim: Displays available keybindings in a popup using the modern preset.
  • +
+ + +

Navigation and File Management

+
    +
  • telescope.nvim and telescope-file-browser.nvim: Fuzzy finder and file browser that replaces netrw. Shows hidden files by default.
  • +
  • project.nvim: Project detection and management via Telescope.
  • +
  • harpoon (v2): Quick file bookmarking and navigation.
  • +
  • vim-tmux-navigator: Seamless navigation between Neovim splits and tmux panes.
  • +
+ + +

Editing Tools

+
    +
  • nvim-surround: Add, change, and delete surrounding pairs.
  • +
  • multicursor (custom): Native multi-cursor implementation.
  • +
  • autoclose (custom): Auto-closes brackets, quotes, and backticks.
  • +
  • rename-config (custom): Find and Replace panel backed by rg.
  • +
  • save-popup (custom): Context-aware save options via leaderW.
  • +
+ + +

LSP and Diagnostics

+
    +
  • mason.nvim: Installs and manages LSP servers, linters, and DAP adapters.
  • +
  • LSP servers registered directly with vim.lsp.config() and vim.lsp.enable().
  • +
  • Inlay hints enabled by default where supported. Toggle with leaderih.
  • +
  • Diagnostic noise from ltex (sentence and paragraph length) is filtered globally.
  • +
+ + +

Completion

+
    +
  • nvim-cmp: Completion engine with LSP and buffer sources.
  • +
  • LuaSnip: Snippet engine.
  • +
+ + + + + + +
KeysModeAction
Tab / Shift+TabInsertCycle through completion items
EnterInsertConfirm selected completion
+ + +

Linting

+

nvim-lint runs automatically on BufWritePost and BufEnter.

+ + + + + + + + + + +
LanguageLinter
Lualuacheck
Gostaticcheck
Pythonruff
JavaScript / TypeScripteslint_d
Javacheckstyle
Kotlinktlint
+ + +

Debugging

+
    +
  • nvim-dap and nvim-dap-ui: Debug Adapter Protocol with an auto-managed UI.
  • +
  • mason-nvim-dap: Ensures DAP adapters are installed via Mason.
  • +
+ + +

Syntax and Parsing

+
    +
  • nvim-treesitter: Syntax highlighting, indentation, and incremental selection.
  • +
  • nvim-ts-autotag: Auto-closes and auto-renames HTML and JSX tags.
  • +
  • typst-preview.nvim: Live browser preview for Typst files via :TP, :TS, :TU.
  • +
+ + +

Editor Options

+ + + + + + + + + + + + + + + + + +
OptionValue
Line numbersOn (absolute)
Relative numbersOn
Tab width4 spaces (expandtab)
Smart indentOn
Swap filesOff
Backup filesOff
ClipboardSystem clipboard (unnamedplus)
Scroll offset10 lines
EncodingUTF-8
Cursor lineHighlighted (number only, gold color)
Command line height0 (hidden)
UndoPersistent (survives restarts)
Incremental substituteSplit preview (inccommand = split)
+ + +

Statusline

+

Custom-built statusline (no plugin). Displays:

+
    +
  • Left: Mode indicator letter + filename + modified flag [+]
  • +
  • Right: Save flash (written, shown 2s after save) + mode name + percentage + column
  • +
+ + + + + + + + + +
ModeColour
NormalGold #f6c177
InsertFoam #9ccfd8
VisualIris #c4a7e7
CommandLove #eb6f92
ReplaceLove #eb6f92
+ + +

Filetype-Specific Settings

+

COBOL

+

Tab width: 8 spaces. Colour columns at 7 and 73. Text width: 72.

+

Crystal

+

.cr files fall back to Ruby syntax highlighting.

+

Java

+

Build system auto-detected (Maven / Gradle / Ant). JDTLS workspaces stored in ~/.cache/nvim/jdtls/<project_name>.

+

Kotlin

+

Build system auto-detected (Gradle or Maven).

+ + +

User Customization

+

Two git-ignored files allow personal settings that survive git pull updates. Auto-created with template comments on first startup.

+ +

nvim/lua/user.lua

+

Loaded last, after all CharVim config. Add keymaps, options, autocommands, or override any default:

+
-- nvim/lua/user.lua
+vim.opt.wrap = true
+vim.keymap.set('n', '<leader>x', ':!echo hello<CR>', { desc = 'My command' })
+ +

nvim/lua/user-plugins.lua

+

Return a lazy.nvim spec list to add personal plugins:

+
-- nvim/lua/user-plugins.lua
+return {
+    { 'folke/zen-mode.nvim', opts = {} },
+}
+ + +

Update Checker

+

CharVim checks for new GitHub releases on startup. A Telescope popup appears when an update is found.

+ + + + + + + + +
OptionEffect
Update nowRuns git pull asynchronously
Remind me laterDismisses; prompts again next startup
Skip vX.Y.ZSuppresses this release permanently
Disable update checksTurns off all future checks
+ + + + + + + +
CommandAction
:CharvimUpdateCheckForce a check now (also re-enables checks)
:CharvimUpdateEnableEnable automatic update checks
:CharvimUpdateDisableDisable automatic update checks
+

Settings persist to ~/.local/share/nvim/charvim_settings.json.

+ + +

Uninstall / Reinstall

+

Open from the dashboard (u) or with :CharvimUninstall.

+ + + + + + + +
OptionWhat it removes
ReinstallRe-runs setup.sh; falls back to recreating the symlink
Normal uninstall~/.config/nvim symlink + CharVim data files
Complete uninstallEverything above + lazy/, mason/, and repo directory
+
+ Warning + Complete uninstall is irreversible. Type YES at the confirmation prompt to proceed. +
+ + +

Custom Commands

+ + + + + + + + + + + + + + +
CommandAction
:E / :TreeOpen Telescope file browser
:FindOpen find & replace panel
:TP / :TS / :TUStart / stop / update Typst preview
:ThemeSelectOpen interactive theme picker
:Theme <name>Switch to a specific theme
:OpenInBrowserOpen current HTML file in browser
:RunPythonRun current Python file in split terminal
:RunJavaCompile and run current Java file
:CharvimUpdateCheckCheck for CharVim updates
:CharvimUninstallOpen uninstall / reinstall menu
+ + +

Dashboard

+

Open with :AlphaRedraw or leaderd.

+ + + + + + + + + + + + + + + + +
KeyAction
fFind file
rRecent files
pProjects
nNew file
gFind text
cOpen config (init.lua)
lLazy plugin manager
mMason installer
tTheme picker
dOpen docs in browser
uUninstall / reinstall menu
qQuit
+ +
+
+
+ + + + + + + diff --git a/img/favicon/charvim-atl.svg b/img/favicon/charvim-atl.svg new file mode 100644 index 0000000..b6f5169 --- /dev/null +++ b/img/favicon/charvim-atl.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/img/favicon/charvim.svg b/img/favicon/charvim.svg new file mode 100644 index 0000000..0409acf --- /dev/null +++ b/img/favicon/charvim.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/img/logo/charvim-logo.svg b/img/logo/charvim-logo.svg new file mode 100644 index 0000000..bc20118 --- /dev/null +++ b/img/logo/charvim-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..b1a4b72 --- /dev/null +++ b/index.html @@ -0,0 +1,396 @@ + + + + + + CharVim: Neovim, crafted for flow. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+ +
+ +

CharVim

+

Neovim, crafted for flow.

+ + + v1.5.2 + + Latest release + + + + + +
+ + +
+
+
+ 10+ + LSP Servers +
+
+ 3 + Color Themes +
+
+ 6 + Linters +
+
+ 5 + DAP Adapters +
+
+ 4 + Platforms +
+
+
+ +
+ + +
+
+ +

Everything you need.
Nothing you don't.

+

CharVim bundles a carefully chosen set of plugins and custom modules. Opinionated enough to feel cohesive, flexible enough to make your own.

+ +
+
+ +

Lazy-loaded plugins

+

Built on lazy.nvim with per-filetype loading. Your editor starts fast and stays fast, no matter how many plugins are installed.

+
+ +
+ 🧠 +

Full LSP suite

+

Ten language servers pre-configured for Lua, Python, TypeScript, Java, Kotlin, Go, C/C++, Swift, Typst, and XML. All installed and managed through Mason.

+
+ +
+ 🤖 +

AI assistance

+

avante.nvim powered by Anthropic Claude for inline code chat and AI-assisted editing. Just set ANTHROPIC_API_KEY and go.

+
+ +
+ 🐛 +

Debug Adapter Protocol

+

DAP configured for Python, Java, C/C++, Swift, and Kotlin. The debug UI opens automatically when a session starts and closes when it ends.

+
+ +
+ 🎨 +

Three themes

+

Knew-pines, Noir-cat, and Dark. Switch between them at runtime with a live Telescope preview, and your choice carries over between sessions.

+
+ +
+ 🔎 +

Find and Replace panel

+

A custom nui.nvim panel backed by ripgrep. Live search with file preview, project-wide regex replace, and LSP rename when available.

+
+ +
+ 📌 +

Harpoon 2

+

Quick file bookmarks with Alt+1 through Alt+5 jumps. Navigate your five most important files in a single keystroke.

+
+ +
+ ✏️ +

Multi-cursor

+

Native multi-cursor implementation with no plugin dependency. Add cursors above or below with Ctrl+↑ and Ctrl+↓, then type at all positions at once.

+
+ +
+ 🖥️ +

Cross-platform setup

+

One install script, four platforms: macOS via Homebrew, Fedora, Debian/Ubuntu, and Arch Linux. Missing dependencies get installed automatically.

+
+
+
+
+ +
+ + +
+
+ +

Three themes.
Click to switch.

+

All themes are available inside Neovim via :ThemeSelect. The site reflects whichever theme you pick.

+ +
+ + +
+
+
+
+
+ knew-pines +
+
+
local M = {}
+
function M.setup(opts)
+
  local theme = "knew-pines"
+
  -- warm rose-pine palette
+
  vim.cmd("colorscheme " .. theme)
+
end
+
return M
+
+
+ + +
+
+
+
+
+ noir-cat +
+
+
local M = {}
+
function M.setup(opts)
+
  local theme = "noir-cat"
+
  -- dark catppuccin-inspired
+
  vim.cmd("colorscheme " .. theme)
+
end
+
return M
+
+
+ + +
+
+
+
+
+ dark +
+
+
local M = {}
+
function M.setup(opts)
+
  local theme = "dark"
+
  -- minimal github-dark palette
+
  vim.cmd("colorscheme " .. theme)
+
end
+
return M
+
+
+ +
+
+
+ +
+ + +
+ +

One command to get started.

+

+ The install script detects your OS and installs all required dependencies before symlinking the config into place. +

+
+
+ bash <(curl -s https://raw.githubusercontent.com/CrtlUserKnown/Charvim/main/setup.sh) + +
+
+
+ Manual install guide +
+
+ +
+ + + + + + + diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..f81cc90 --- /dev/null +++ b/js/main.js @@ -0,0 +1,119 @@ +/* === THEME === */ +const DEFAULT_THEME = 'knew-pines'; +const STORAGE_KEY = 'charvim-theme'; + +function getTheme() { + return localStorage.getItem(STORAGE_KEY) || DEFAULT_THEME; +} + +function applyTheme(theme) { + document.documentElement.setAttribute('data-theme', theme); + localStorage.setItem(STORAGE_KEY, theme); + + document.querySelectorAll('.theme-btn').forEach(btn => { + btn.classList.toggle('active', btn.dataset.theme === theme); + }); + + document.querySelectorAll('.theme-preview').forEach(prev => { + prev.classList.toggle('active-preview', prev.dataset.theme === theme); + }); +} + +/* === CURSOR GRADIENT === */ +function initCursorGradient() { + let tx = 50, ty = 50; + let cx = 50, cy = 50; + + document.addEventListener('mousemove', e => { + tx = (e.clientX / window.innerWidth) * 100; + ty = (e.clientY / window.innerHeight) * 100; + }); + + (function tick() { + cx += (tx - cx) * 0.055; + cy += (ty - cy) * 0.055; + document.documentElement.style.setProperty('--gx', cx.toFixed(2) + '%'); + document.documentElement.style.setProperty('--gy', cy.toFixed(2) + '%'); + requestAnimationFrame(tick); + })(); +} + +/* === COPY BUTTONS === */ +function initCopyBtns() { + document.querySelectorAll('.copy-btn').forEach(btn => { + btn.addEventListener('click', () => { + const text = btn.closest('.code-block').querySelector('code').textContent; + navigator.clipboard.writeText(text).then(() => { + const orig = btn.textContent; + btn.textContent = '✓'; + btn.style.color = 'var(--foam)'; + setTimeout(() => { btn.textContent = orig; btn.style.color = ''; }, 2000); + }); + }); + }); +} + +/* === NAVIGATION === */ +function initNav() { + const page = window.location.pathname.split('/').pop() || 'index.html'; + + document.querySelectorAll('.nav-links a').forEach(a => { + const href = a.getAttribute('href') || ''; + const match = href === page + || (page === '' && href === 'index.html') + || (page === 'index.html' && href === 'index.html'); + a.classList.toggle('active', match); + }); + + const burger = document.querySelector('.hamburger'); + const links = document.querySelector('.nav-links'); + if (burger && links) { + burger.addEventListener('click', () => links.classList.toggle('open')); + document.addEventListener('click', e => { + if (!burger.contains(e.target) && !links.contains(e.target)) { + links.classList.remove('open'); + } + }); + } +} + +/* === DOCS SIDEBAR SCROLL HIGHLIGHT === */ +function initDocsSidebar() { + const headings = document.querySelectorAll('.docs-content h2[id], .docs-content h3[id]'); + const sideLinks = document.querySelectorAll('.sidebar-links a[href^="#"]'); + if (!headings.length) return; + + const obs = new IntersectionObserver(entries => { + entries.forEach(e => { + if (e.isIntersecting) { + sideLinks.forEach(l => { + l.classList.toggle('active', l.getAttribute('href') === '#' + e.target.id); + }); + } + }); + }, { rootMargin: '-64px 0px -55% 0px', threshold: 0 }); + + headings.forEach(h => obs.observe(h)); +} + +/* === THEME PREVIEW CLICK === */ +function initThemePreviews() { + document.querySelectorAll('.theme-preview[data-theme]').forEach(el => { + el.addEventListener('click', () => applyTheme(el.dataset.theme)); + }); +} + +/* === BOOT === */ +document.addEventListener('DOMContentLoaded', () => { + applyTheme(getTheme()); + + document.querySelectorAll('.theme-btn[data-theme]').forEach(btn => { + btn.addEventListener('click', () => applyTheme(btn.dataset.theme)); + }); + + initCursorGradient(); + initCopyBtns(); + initNav(); + initDocsSidebar(); + initThemePreviews(); +}); diff --git a/nvim/.gitignore b/nvim/.gitignore deleted file mode 100644 index 8d7af86..0000000 --- a/nvim/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -# Neovim -lazy-lock.json -.DS_Store - -# Backup files -*.backup -*~ diff --git a/nvim/after/ftplugin/cobol.lua b/nvim/after/ftplugin/cobol.lua deleted file mode 100644 index 73ec5b2..0000000 --- a/nvim/after/ftplugin/cobol.lua +++ /dev/null @@ -1,14 +0,0 @@ --- COBOL-specific settings - --- Tab settings -vim.opt_local.tabstop = 8 -- Number of spaces a tab counts for -vim.opt_local.shiftwidth = 8 -- Number of spaces for indentation -vim.opt_local.expandtab = true -- Convert tabs to spaces - -vim.opt_local.colorcolumn = "7,73" -- Mark columns 7 and 73 - -vim.opt_local.textwidth = 72 - --- Line numbering (helpful for debugging) -vim.opt_local.number = true -vim.opt_local.relativenumber = false diff --git a/nvim/after/ftplugin/make.lua b/nvim/after/ftplugin/make.lua deleted file mode 100644 index 15c6583..0000000 --- a/nvim/after/ftplugin/make.lua +++ /dev/null @@ -1,7 +0,0 @@ --- Makefile-specific settings - --- Tabs are required for Makefile recipes -vim.opt_local.tabstop = 8 -vim.opt_local.shiftwidth = 8 -vim.opt_local.softtabstop = 8 -vim.opt_local.expandtab = false diff --git a/nvim/init.lua b/nvim/init.lua deleted file mode 100644 index 183650b..0000000 --- a/nvim/init.lua +++ /dev/null @@ -1,58 +0,0 @@ -vim.g.mapleader = " " -vim.g.maplocalleader = "\\" - -local env_file = vim.fn.stdpath("config") .. "/../.env" -if vim.fn.filereadable(env_file) == 1 then - for line in io.lines(env_file) do - local key, value = line:match("^([%w_]+)=(.*)$") - if key and value ~= "" then - vim.fn.setenv(key, value) - end - end -end - -vim.api.nvim_set_hl(0, 'CursorLineNr', { - fg = '#f6c177', - bold = true -}) - -require('statusline') -require('options') -require('keymaps') -require('theme-switcher') -require('multicursor').setup() - -require('autoclose').setup() - -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", - lazypath }) -end -vim.opt.rtp:prepend(lazypath) - -require('lazy').setup('plugins', { - change_detection = { enabled = true, notify = false }, - timeout = 120000, - git = { - clone_params = { depth = 1 }, - }, -}) - -require('lsp-config') -require('treesitter-config') -require('completion-config') -require('rename-config') - -vim.api.nvim_set_hl(0, "Normal", { bg = "none" }) -vim.api.nvim_set_hl(0, "NormalFloat", { bg = "none" }) - -vim.api.nvim_create_autocmd("FileType", { - pattern = "crystal", - callback = function() - if vim.bo.syntax == 'on' or vim.bo.syntax == 'crystal' then - vim.cmd("set syntax=ruby") - end - end -}) - diff --git a/nvim/lazy-lock.json b/nvim/lazy-lock.json new file mode 100644 index 0000000..ce59345 --- /dev/null +++ b/nvim/lazy-lock.json @@ -0,0 +1,36 @@ +{ + "LuaSnip": { "branch": "master", "commit": "0abc8f390b278c3b4aabc4c004ac8a088b65cf24" }, + "alpha-nvim": { "branch": "main", "commit": "6c6a89d5b068b5251c8bdf0dd57bb921bcfeeb09" }, + "avante.nvim": { "branch": "main", "commit": "dee2402ed0b7c1d3710c0c8eed3d26aa055e118e" }, + "catppuccin": { "branch": "main", "commit": "49a926655a2f5579e9c276470fc300baaa49e524" }, + "cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" }, + "cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" }, + "dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" }, + "gruvbox.nvim": { "branch": "main", "commit": "154eb5ff5b96d0641307113fa385eaf0d36d9796" }, + "harpoon": { "branch": "harpoon2", "commit": "87b1a3506211538f460786c23f98ec63ad9af4e5" }, + "img-clip.nvim": { "branch": "main", "commit": "b6ddfb97b5600d99afe3452d707444afda658aca" }, + "lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" }, + "mason-nvim-dap.nvim": { "branch": "main", "commit": "9a10e096703966335bd5c46c8c875d5b0690dade" }, + "mason.nvim": { "branch": "main", "commit": "2a6940af80375532e5e9e7c1f2fc6319a1b7a69d" }, + "noice.nvim": { "branch": "main", "commit": "7bfd942445fb63089b59f97ca487d605e715f155" }, + "nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" }, + "nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" }, + "nvim-dap": { "branch": "master", "commit": "531771530d4f82ad2d21e436e3cc052d68d7aebb" }, + "nvim-dap-python": { "branch": "master", "commit": "1808458eba2b18f178f990e01376941a42c7f93b" }, + "nvim-dap-ui": { "branch": "master", "commit": "1a66cabaa4a4da0be107d5eda6d57242f0fe7e49" }, + "nvim-lint": { "branch": "master", "commit": "99cbc3ca8a76845fca50e496be7212bebf907dd3" }, + "nvim-nio": { "branch": "master", "commit": "21f5324bfac14e22ba26553caf69ec76ae8a7662" }, + "nvim-surround": { "branch": "main", "commit": "2e93e154de9ff326def6480a4358bfc149d5da2c" }, + "nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" }, + "nvim-ts-autotag": { "branch": "main", "commit": "88c1453db4ba7dd24131086fe51fdf74e587d275" }, + "nvim-web-devicons": { "branch": "master", "commit": "dfbfaa967a6f7ec50789bead7ef87e336c1fa63c" }, + "plenary.nvim": { "branch": "master", "commit": "74b06c6c75e4eeb3108ec01852001636d85a932b" }, + "project.nvim": { "branch": "main", "commit": "8c6bad7d22eef1b71144b401c9f74ed01526a4fb" }, + "rose-pine": { "branch": "main", "commit": "ff483051a47e27d84bdef47703538df1ed9f4a47" }, + "telescope-file-browser.nvim": { "branch": "master", "commit": "3610dc7dc91f06aa98b11dca5cc30dfa98626b7e" }, + "telescope.nvim": { "branch": "master", "commit": "7d324792b7943e4aa16ad007212e6acc6f9fe335" }, + "tokyonight.nvim": { "branch": "main", "commit": "cdc07ac78467a233fd62c493de29a17e0cf2b2b6" }, + "typst-preview.nvim": { "branch": "master", "commit": "325036ee145ca51d9efb145c09ac16bce3bc8b7d" }, + "vim-tmux-navigator": { "branch": "master", "commit": "e41c431a0c7b7388ae7ba341f01a0d217eb3a432" }, + "which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" } +} diff --git a/nvim/lua/alpha-config.lua b/nvim/lua/alpha-config.lua deleted file mode 100644 index 7a17034..0000000 --- a/nvim/lua/alpha-config.lua +++ /dev/null @@ -1,83 +0,0 @@ -local alpha = require('alpha') -local dashboard = require('alpha.themes.dashboard') - --- Read ASCII art from file -local function read_ascii_art() - local config_path = vim.fn.stdpath('config') - local art_path = config_path .. '/lua/img/charVim.txt' - local file = io.open(art_path, 'r') - - if file then - local lines = {} - for line in file:lines() do - if line:sub(1,1) == '[' and line:sub(-1,-1) == ']' then - table.insert(lines, line:sub(2, -2)) - else - table.insert(lines, line) - end - end - file:close() - return lines - else - return { - " ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 \xe2\x96\x88\xe2\x96\x88\xe2\x96\x88\xe2\x96\x88 ", - " ", - } - end -end - -dashboard.section.header.val = read_ascii_art() - -dashboard.section.buttons.val = { - dashboard.button("f", " Find file", ":Telescope find_files "), - dashboard.button("p", " Projects", ":Telescope projects "), - dashboard.button("n", " New file", ":ene startinsert "), - dashboard.button("r", " Recent files", ":Telescope oldfiles "), - dashboard.button("g", " Find text", ":Telescope live_grep "), - dashboard.button("c", " Config", ":e $MYVIMRC "), - dashboard.button("l", "󰒲 Lazy", ":Lazy"), - dashboard.button("m", " Mason", ":Mason"), - dashboard.button("t", " Themes", ":ThemeSelect"), - dashboard.button("q", " Quit", ":qa"), -} - -vim.keymap.set('n', 'd', ':Alpha', { desc = 'Go to Dashboard' }) -vim.keymap.set('n', 'p', ':Telescope projects', { desc = 'Projects' }) - -local function footer() - local datetime = os.date(" %d-%m-%Y %H:%M:%S") - local version = vim.version() - local nvim_version_info = " v" .. version.major .. "." .. version.minor .. "." .. version.patch - return datetime .. " " .. nvim_version_info -end - -dashboard.section.footer.val = footer() - -dashboard.config.layout = { - { type = "padding", val = 2 }, - dashboard.section.header, - { type = "padding", val = 2 }, - dashboard.section.buttons, - { type = "padding", val = 1 }, - dashboard.section.footer, -} - -dashboard.config.opts.noautocmd = true - -alpha.setup(dashboard.config) diff --git a/nvim/lua/autoclose.lua b/nvim/lua/autoclose.lua deleted file mode 100644 index 701872a..0000000 --- a/nvim/lua/autoclose.lua +++ /dev/null @@ -1,176 +0,0 @@ -local autoclose = {} - -local config = { - keys = { - ["("] = { escape = false, close = true, pair = "()" }, - ["["] = { escape = false, close = true, pair = "[]" }, - ["{"] = { escape = false, close = true, pair = "{}" }, - - [">"] = { escape = true, close = false, pair = "<>" }, - [")"] = { escape = true, close = false, pair = "()" }, - ["]"] = { escape = true, close = false, pair = "[]" }, - ["}"] = { escape = true, close = false, pair = "{}" }, - - ['"'] = { escape = true, close = true, pair = '""' }, - ["'"] = { escape = true, close = true, pair = "''" }, - ["`"] = { escape = true, close = true, pair = "``" }, - - [" "] = { escape = false, close = true, pair = " " }, - - [""] = {}, - [""] = {}, - [""] = {}, - [""] = { disable_command_mode = true }, - [""] = { disable_command_mode = true }, - }, - options = { - disabled_filetypes = { "text" }, - disable_when_touch = false, - touch_regex = "[%w(%[{]", - pair_spaces = false, - auto_indent = true, - disable_command_mode = false, - }, - disabled = false, -} - -local function insert_get_pair() - local line = "_" .. vim.api.nvim_get_current_line() - local col = vim.api.nvim_win_get_cursor(0)[2] + 1 - return line:sub(col, col + 1) -end - -local function command_get_pair() - local line = "_" .. vim.fn.getcmdline() - local col = vim.fn.getcmdpos() - return line:sub(col, col + 1) -end - -local function is_pair(pair) - if pair == " " then return false end - for _, info in pairs(config.keys) do - if pair == info.pair then return true end - end - return false -end - -local function is_disabled(info) - if config.disabled then return true end - - -- use vim.bo instead of deprecated nvim_buf_get_option - local current_filetype = vim.bo.filetype - - for _, filetype in pairs(config.options.disabled_filetypes) do - if filetype == current_filetype then return true end - end - - if info["enabled_filetypes"] ~= nil then - for _, filetype in pairs(info.enabled_filetypes) do - if filetype == current_filetype then return false end - end - return true - end - - if info["disabled_filetypes"] ~= nil then - for _, filetype in pairs(info.disabled_filetypes) do - if filetype == current_filetype then return true end - end - end - return false -end - -local function handler(key, info, mode) - if is_disabled(info) then return key end - - local pair = mode == "insert" and insert_get_pair() or command_get_pair() - - if (key == "" or key == "" or key == "") and is_pair(pair) then - return "" - elseif - mode == "insert" - and (key == "" or key == "") - and is_pair(pair) - then - return "O" .. (config.options.auto_indent and "" or "") - elseif info.escape and pair:sub(2, 2) == key then - return mode == "insert" and "U" or "" - elseif info.close then - if - config.options.disable_when_touch - and (pair .. "_"):sub(2, 2):match(config.options.touch_regex) - then - return key - end - - if - key == " " - and ( - not config.options.pair_spaces - or (config.options.pair_spaces and not is_pair(pair)) - or pair:sub(1, 1) == pair:sub(2, 2) - ) - then - return key - end - - return info.pair .. (mode == "insert" and "U" or "") - else - return key - end -end - -local function visual_handler(key, info) - if is_disabled(info) then return key end - - if info.close and info.pair then - local open_char = info.pair:sub(1, 1) - local close_char = info.pair:sub(2, 2) - return string.format('`>a%s`', close_char, open_char) - else - return key - end -end - -function autoclose.setup(user_config) - user_config = user_config or {} - - if user_config.keys ~= nil then - for key, info in pairs(user_config.keys) do - config.keys[key] = info - end - end - - if user_config.options ~= nil then - for key, info in pairs(user_config.options) do - config.options[key] = info - end - end - - for key, info in pairs(config.keys) do - vim.keymap.set("i", key, function() - return (key == " " and "" or "") .. handler(key, info, "insert") - end, { noremap = true, expr = true }) - - if - not config.options.disable_command_mode - and not info.disable_command_mode - then - vim.keymap.set("c", key, function() - return (key == " " and "" or "") - .. handler(key, info, "command") - end, { noremap = true, expr = true }) - end - - if info.close and info.pair then - vim.keymap.set("x", key, function() - return visual_handler(key, info) - end, { noremap = true, expr = true }) - end - end -end - -function autoclose.toggle() - config.disabled = not config.disabled -end - -return autoclose diff --git a/nvim/lua/completion-config.lua b/nvim/lua/completion-config.lua deleted file mode 100644 index cade18b..0000000 --- a/nvim/lua/completion-config.lua +++ /dev/null @@ -1,34 +0,0 @@ -local cmp = require("cmp") - -cmp.setup({ - snippet = { - expand = function(args) - require("luasnip").lsp_expand(args.body) - end, - }, - mapping = cmp.mapping.preset.insert({ - [""] = cmp.mapping.confirm({ select = false }), - [""] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_next_item() - else - fallback() - end - end, { "i", "s" }), - [""] = cmp.mapping(function(fallback) - if cmp.visible() then - cmp.select_prev_item() - else - fallback() - end - end, { "i", "s" }), - }), - sources = cmp.config.sources({ - { name = "nvim_lsp" }, - }, { - { name = "buffer" }, - }), -}) - -vim.opt.completeopt = "menu,menuone,noselect" -vim.opt.shortmess:append("c") diff --git a/nvim/lua/dap-config.lua b/nvim/lua/dap-config.lua deleted file mode 100644 index d3a9e07..0000000 --- a/nvim/lua/dap-config.lua +++ /dev/null @@ -1,133 +0,0 @@ -local dap = require('dap') -local dapui = require('dapui') - --- ui setup -dapui.setup({ - icons = { expanded = "▾", collapsed = "▸", current_frame = "▸" }, - layouts = { - { - elements = { - { id = "scopes", size = 0.35 }, - { id = "breakpoints", size = 0.15 }, - { id = "stacks", size = 0.35 }, - { id = "watches", size = 0.15 }, - }, - size = 40, - position = "left", - }, - { - elements = { - { id = "repl", size = 0.5 }, - { id = "console", size = 0.5 }, - }, - size = 10, - position = "bottom", - }, - }, -}) - --- open/close ui with session -dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end -dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end -dap.listeners.before.event_exited["dapui_config"] = function() dapui.close() end - --- python -local dap_python = require('dap-python') -dap_python.setup('python3') -dap_python.test_runner = 'pytest' - --- java (jdtls built-in dap) -dap.configurations.java = { - { - type = 'java', - request = 'attach', - name = 'Attach to process', - processId = require('dap.utils').pick_process, - }, - { - type = 'java', - request = 'launch', - name = 'Launch Java', - mainClass = function() - return vim.fn.input('Main class: ', '', 'file') - end, - }, -} - --- c / c++ / swift adapter -dap.adapters.codelldb = { - type = 'server', - port = '${port}', - executable = { - command = vim.fn.stdpath('data') .. '/mason/bin/codelldb', - args = { '--port', '${port}' }, - }, -} - -dap.configurations.c = { - { - name = 'Launch', - type = 'codelldb', - request = 'launch', - program = function() - return vim.fn.input('Executable: ', vim.fn.getcwd() .. '/', 'file') - end, - cwd = '${workspaceFolder}', - stopOnEntry = false, - }, -} -dap.configurations.cpp = dap.configurations.c - --- swift uses codelldb, points to .build/debug by default -dap.configurations.swift = { - { - name = 'Launch Swift', - type = 'codelldb', - request = 'launch', - program = function() - return vim.fn.input('Executable: ', vim.fn.getcwd() .. '/.build/debug/', 'file') - end, - cwd = '${workspaceFolder}', - stopOnEntry = false, - }, -} - --- kotlin -dap.adapters.kotlin = { - type = 'executable', - command = vim.fn.stdpath('data') .. '/mason/bin/kotlin-debug-adapter', - args = {}, -} - -dap.configurations.kotlin = { - { - type = 'kotlin', - request = 'launch', - name = 'Launch Kotlin', - mainClass = function() - return vim.fn.input('Main class (e.g. com.example.MainKt): ') - end, - projectRoot = '${workspaceFolder}', - }, - { - type = 'kotlin', - request = 'attach', - name = 'Attach to Kotlin', - port = 5005, - projectRoot = '${workspaceFolder}', - }, -} - --- keymaps -local map = vim.keymap.set -map('n', 'db', dap.toggle_breakpoint, { desc = 'Toggle breakpoint' }) -map('n', 'dB', function() dap.set_breakpoint(vim.fn.input('Condition: ')) end, { desc = 'Conditional breakpoint' }) -map('n', 'dc', dap.continue, { desc = 'Continue' }) -map('n', 'dn', dap.step_over, { desc = 'Step over' }) -map('n', 'di', dap.step_into, { desc = 'Step into' }) -map('n', 'do', dap.step_out, { desc = 'Step out' }) -map('n', 'dr', dap.repl.open, { desc = 'Open REPL' }) -map('n', 'dl', dap.run_last, { desc = 'Run last' }) -map('n', 'dt', dap.terminate, { desc = 'Terminate' }) -map('n', 'du', dapui.toggle, { desc = 'Toggle DAP UI' }) -map('n', 'dp', function() require('dap-python').test_method() end, { desc = 'Debug Python method' }) diff --git a/nvim/lua/harpoon-config.lua b/nvim/lua/harpoon-config.lua deleted file mode 100644 index 662f80d..0000000 --- a/nvim/lua/harpoon-config.lua +++ /dev/null @@ -1,40 +0,0 @@ -local harpoon = require("harpoon") -harpoon:setup({ - settings = { - save_on_toggle = true, - sync_on_ui_close = true, - }, - -- UI customization - menu = { - width = vim.api.nvim_win_get_width(0) - 4, - height = 10, - borderchars = { "─", "│", "─", "│", "╭", "╮", "╯", "╰" }, -- Rounded corners - -- Other border options: - -- borderchars = { "━", "┃", "━", "┃", "┏", "┓", "┛", "┗" }, -- Thick borders - -- borderchars = { "─", "│", "─", "│", "┌", "┐", "┘", "└" }, -- Simple corners - -- borderchars = { " ", " ", " ", " ", " ", " ", " ", " " }, -- No border - }, -}) - --- Add file to harpoon -vim.keymap.set("n", "a", function() harpoon:list():add() end, { desc = "Add file to Harpoon" }) - --- Remove current file from harpoon -vim.keymap.set("n", "ar", function() harpoon:list():remove() end, { desc = "Remove from Harpoon" }) - --- Clear all harpoon marks -vim.keymap.set("n", "ac", function() harpoon:list():clear() end, { desc = "Clear all Harpoon marks" }) - --- Toggle harpoon menu -vim.keymap.set("n", "h", function() harpoon.ui:toggle_quick_menu(harpoon:list()) end, { desc = "Toggle Harpoon menu" }) - --- Navigate with Option (Alt) + number -vim.keymap.set("n", "", function() harpoon:list():select(1) end, { desc = "Harpoon file 1" }) -vim.keymap.set("n", "", function() harpoon:list():select(2) end, { desc = "Harpoon file 2" }) -vim.keymap.set("n", "", function() harpoon:list():select(3) end, { desc = "Harpoon file 3" }) -vim.keymap.set("n", "", function() harpoon:list():select(4) end, { desc = "Harpoon file 4" }) -vim.keymap.set("n", "", function() harpoon:list():select(5) end, { desc = "Harpoon file 5" }) - --- Navigate to next/previous harpoon file -vim.keymap.set("n", "", function() harpoon:list():next() end, { desc = "Next Harpoon file" }) -vim.keymap.set("n", "", function() harpoon:list():prev() end, { desc = "Previous Harpoon file" }) diff --git a/nvim/lua/img/charVim.txt b/nvim/lua/img/charVim.txt deleted file mode 100644 index 5430a4c..0000000 --- a/nvim/lua/img/charVim.txt +++ /dev/null @@ -1,18 +0,0 @@ -[ ] -[ █████████████ █████████ ] -[ ████████ █████ ] -[ ████████ ████ ] -[ ███████ ███ ] -[ ███████ ███ ] -[ ███████ ████ ] -[ ███████ ███ ] -[ ███████ ███ ] -[ ███████ ████ ] -[ ███████ ███ ██ ] -[ ██████████ ██████ ] -[ █████████ ████████ ] -[ ███████ █████████ ] -[ ██████ ██████████ ] -[ █████ ████████ ] -[ ███ ████ ] -[ ] diff --git a/nvim/lua/keymaps.lua b/nvim/lua/keymaps.lua deleted file mode 100644 index 0ed2e7b..0000000 --- a/nvim/lua/keymaps.lua +++ /dev/null @@ -1,98 +0,0 @@ --- force Escape key to always work -vim.keymap.set('i', '', '', { noremap = true, silent = true }) -vim.keymap.set('i', 'jk', '', { noremap = true, silent = true }) - --- save and quit with leader keys -vim.keymap.set('n', 'w', ':w', { noremap = true, silent = true, desc = 'Save file' }) -vim.keymap.set('n', 'q', ':wq', { noremap = true, silent = true, desc = 'Save and quit' }) -vim.keymap.set('n', 'qq', ':q!', { noremap = true, silent = true, desc = 'Quit without saving' }) -vim.keymap.set('n', 't', ':tabnew | Telescope file_browser path=%:p:h select_buffer=true hidden=true', { desc = 'New tab with file explorer' }) - --- go to beginning and end of file -vim.keymap.set('n', '[[', 'gg', { noremap = true, silent = true, desc = 'Go to beginning of file' }) -vim.keymap.set('n', ']]', 'G', { noremap = true, silent = true, desc = 'Go to end of file' }) - --- go to beginning and end of line -vim.keymap.set('n', 'H', '0', { noremap = true, silent = true, desc = 'Go to beginning of line' }) -vim.keymap.set('n', 'L', '$', { noremap = true, silent = true, desc = 'Go to end of line' }) -vim.keymap.set('v', 'H', '0', { noremap = true, silent = true, desc = 'Go to beginning of line' }) -vim.keymap.set('v', 'L', '$', { noremap = true, silent = true, desc = 'Go to end of line' }) - --- paragraph navigation with Option + [ and ] -vim.keymap.set('n', '', '{', { noremap = true, silent = true, desc = 'Previous paragraph' }) -vim.keymap.set('n', '', '}', { noremap = true, silent = true, desc = 'Next paragraph' }) -vim.keymap.set('v', '', '{', { noremap = true, silent = true, desc = 'Previous paragraph' }) -vim.keymap.set('v', '', '}', { noremap = true, silent = true, desc = 'Next paragraph' }) - --- Move line up and down with option/alt + j/k -vim.keymap.set('n', '', ':m .-2==', { desc = 'Move line up' }) -vim.keymap.set('n', '', ':m .+1==', { desc = 'Move line down' }) -vim.keymap.set('v', '', ":m '<-2gv=gv", { desc = 'Move selection up' }) -vim.keymap.set('v', '', ":m '>+1gv=gv", { desc = 'Move selection down' }) -vim.keymap.set('i', '', ':m .-2==gi', { desc = 'Move line up' }) -vim.keymap.set('i', '', ':m .+1==gi', { desc = 'Move line down' }) - --- Copy line up and down with ctrl + shift + j/k -vim.keymap.set('n', '', ':t .-1==', { desc = 'Copy line up' }) -vim.keymap.set('n', '', ':t .==', { desc = 'Copy line down' }) -vim.keymap.set('v', '', ":t '<-1gv=gv", { desc = 'Copy selection up' }) -vim.keymap.set('v', '', ":t '>gv=gv", { desc = 'Copy selection down' }) - --- split navigation handled by vim-tmux-navigator -vim.g.tmux_navigator_no_mappings = 1 - --- terminal shortcut to run command without having to press :! -vim.keymap.set('n', ';', ':!', { noremap = true }) - --- runners --- open current HTML file in browser -local function open_in_browser() - local file = vim.fn.expand('%:p') - local ext = vim.fn.expand('%:e') - if ext ~= 'html' and ext ~= 'htm' then - vim.notify('Not an HTML file', vim.log.levels.WARN) - return - end - local opener - if vim.fn.has('mac') == 1 then - opener = 'open' - elseif vim.fn.has('win32') == 1 or vim.fn.has('win64') == 1 then - opener = 'start' - else - opener = 'xdg-open' - end - local ok = vim.fn.jobstart({ opener, file }, { detach = true }) - if ok == -1 or ok == 0 then - vim.notify('Failed to open file in browser', vim.log.levels.ERROR) - end -end - -vim.keymap.set('n', 'ob', open_in_browser, { desc = 'Open HTML in browser' }) -vim.api.nvim_create_user_command('OpenInBrowser', open_in_browser, { desc = 'Open current HTML file in browser' }) - --- run current Python file in a terminal split -local function run_python() - if vim.fn.expand('%:e') ~= 'py' then - vim.notify('Not a Python file', vim.log.levels.WARN) - return - end - vim.cmd('split | terminal python3 ' .. vim.fn.shellescape(vim.fn.expand('%:p'))) -end - -vim.keymap.set('n', 'rp', run_python, { desc = 'Run Python file' }) -vim.api.nvim_create_user_command('RunPython', run_python, { desc = 'Run current Python file' }) - --- run current Java file in a terminal split (compiles then runs) -local function run_java() - if vim.fn.expand('%:e') ~= 'java' then - vim.notify('Not a Java file', vim.log.levels.WARN) - return - end - local file = vim.fn.shellescape(vim.fn.expand('%:p')) - local dir = vim.fn.shellescape(vim.fn.expand('%:p:h')) - vim.cmd('split | terminal cd ' .. dir .. ' && javac ' .. file .. ' && java ' .. vim.fn.expand('%:t:r')) -end - -vim.keymap.set('n', 'rj', run_java, { desc = 'Run Java file' }) -vim.api.nvim_create_user_command('RunJava', run_java, { desc = 'Run current Java file' }) - diff --git a/nvim/lua/lint-config.lua b/nvim/lua/lint-config.lua deleted file mode 100644 index 50f343a..0000000 --- a/nvim/lua/lint-config.lua +++ /dev/null @@ -1,19 +0,0 @@ -local lint = require("lint") - -lint.linters_by_ft = { - lua = { "luacheck" }, - go = { "staticcheck" }, - python = { "ruff" }, - javascript = { "eslint_d" }, - typescript = { "eslint_d" }, - javascriptreact = { "eslint_d" }, - typescriptreact = { "eslint_d" }, - java = { "checkstyle" }, - kotlin = { "ktlint" }, -} - -vim.api.nvim_create_autocmd({ "BufWritePost", "BufEnter" }, { - callback = function() - pcall(lint.try_lint) - end, -}) diff --git a/nvim/lua/lsp-config.lua b/nvim/lua/lsp-config.lua deleted file mode 100644 index 0f2583c..0000000 --- a/nvim/lua/lsp-config.lua +++ /dev/null @@ -1,410 +0,0 @@ -local function add_word_to_dictionary(word, client_name) - if client_name == "ltex" then - local params = { - command = "ltex.addToDictionary", - arguments = { - { - words = { - ["en-US"] = { word } - } - } - } - } - vim.lsp.buf.execute_command(params) - end - - if client_name == "typos_lsp" then - local config_path = vim.fn.expand("~/.config/typos.toml") - vim.notify("Add custom typos config at " .. config_path, vim.log.levels.INFO) - end -end - -local on_attach = function(client, bufnr) - local opts = { noremap = true, silent = true, buffer = bufnr } - - if client:supports_method('textDocument/inlayHint') then - vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - end - - vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts) - vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) - vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts) - vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts) - vim.keymap.set('n', 'go', vim.lsp.buf.type_definition, opts) - vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) - vim.keymap.set('n', 'gs', vim.lsp.buf.signature_help, opts) - vim.keymap.set('n', 'lr', vim.lsp.buf.rename, opts) - vim.keymap.set('n', 'ca', vim.lsp.buf.code_action, opts) - vim.keymap.set('n', 'le', vim.diagnostic.open_float, opts) - vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts) - vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts) - - vim.keymap.set('n', 'ih', function() - vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr }) - end, { buffer = bufnr, desc = 'Toggle inlay hints' }) - - vim.keymap.set('n', 'ig', function() - local word = vim.fn.expand('') - add_word_to_dictionary(word, client.name) - vim.notify("Added '" .. word .. "' to dictionary", vim.log.levels.INFO) - end, vim.tbl_extend('force', opts, { desc = 'Ignore word (add to dictionary)' })) - - vim.keymap.set('n', 'di', function() - local diag = vim.diagnostic.get(0, { lnum = vim.fn.line('.') - 1 }) - print(vim.inspect(diag)) - end, vim.tbl_extend('force', opts, { desc = 'Inspect diagnostics at cursor' })) -end - -local function filter_diagnostics(diagnostics) - return vim.tbl_filter(function(diagnostic) - if diagnostic.message:match("sentence is %d+ words long") then return false end - if diagnostic.message:match("paragraph is %d+ words long") then return false end - if diagnostic.message:match("This sentence") then return false end - return true - end, diagnostics) -end - -local original_set = vim.diagnostic.set -vim.diagnostic.set = function(namespace, bufnr, diagnostics, opts) - diagnostics = filter_diagnostics(diagnostics) - original_set(namespace, bufnr, diagnostics, opts) -end - -vim.diagnostic.config({ - virtual_text = { source = "if_many" }, - signs = true, - underline = true, - update_in_insert = true, - severity_sort = true, - float = { - border = "rounded", - source = "if_many", - header = "", - prefix = "", - }, -}) - -local capabilities = require("cmp_nvim_lsp").default_capabilities() - -local function mason_cmd(name) - local path = vim.fn.stdpath('data') .. '/mason/bin/' .. name - if vim.fn.executable(path) == 1 then - return { path } - end - return { name } -end - -vim.lsp.config('lua_ls', { - cmd = mason_cmd('lua-language-server'), - filetypes = { 'lua' }, - root_markers = { '.luarc.json', '.luarc.jsonc', '.luacheckrc', '.stylua.toml', 'selene.toml', 'selene.yml', '.git' }, - capabilities = capabilities, - on_attach = on_attach, - settings = { - Lua = { - runtime = { version = 'LuaJIT' }, - workspace = { - checkThirdParty = false, - library = vim.api.nvim_get_runtime_file("", true), - }, - diagnostics = { globals = { 'vim' } }, - telemetry = { enable = false }, - }, - }, -}) -vim.lsp.enable('lua_ls') - -vim.lsp.config('gopls', { - cmd = mason_cmd('gopls'), - filetypes = { 'go', 'gomod' }, - root_markers = { 'go.mod', '.git' }, - capabilities = capabilities, - on_attach = on_attach, - settings = { - gopls = { - analyses = { unusedparams = true, shadow = true }, - staticcheck = true, - gofumpt = true, - hints = { - assignVariableTypes = true, - compositeLiteralFields = true, - compositeLiteralTypes = true, - constantValues = true, - functionTypeParameters = true, - parameterNames = true, - rangeVariableTypes = true, - }, - }, - }, -}) -vim.lsp.enable('gopls') - -vim.lsp.config('tinymist', { - cmd = mason_cmd('tinymist'), - filetypes = { 'typst' }, - root_markers = { '.git' }, - capabilities = capabilities, - on_attach = on_attach, - settings = { - exportPdf = "onSave", - outputPath = "$root/$dir/$name", - }, -}) -vim.lsp.enable('tinymist') - -vim.lsp.config('clangd', { - cmd = mason_cmd('clangd'), - filetypes = { 'c', 'cpp', 'objc', 'objcpp' }, - root_markers = { '.clangd', 'compile_commands.json', '.git' }, - capabilities = capabilities, - on_attach = on_attach, -}) -vim.lsp.enable('clangd') - -vim.lsp.config('sourcekit-lsp', { - cmd = { 'sourcekit-lsp' }, - filetypes = { 'swift', 'objective-c', 'objective-cpp' }, - root_markers = { 'Package.swift', 'compile_commands.json', '.git' }, - capabilities = capabilities, - on_attach = on_attach, -}) - -vim.api.nvim_create_autocmd('FileType', { - pattern = { 'swift', 'objective-c', 'objective-cpp' }, - callback = function() - vim.lsp.enable('sourcekit-lsp') - end, -}) - -local function jdtls_workspace(root_dir) - local workspace = vim.fn.stdpath('cache') .. '/jdtls' - if root_dir then - workspace = workspace .. '/' .. vim.fn.fnamemodify(root_dir, ':t') - else - workspace = workspace .. '/default' - end - return workspace -end - -vim.lsp.config('jdtls', { - cmd = function(dispatchers, config) - local path = vim.fn.stdpath('data') .. '/mason/bin/jdtls' - local exe = vim.fn.executable(path) == 1 and path or 'jdtls' - local data_dir = jdtls_workspace(config.root_dir) - return vim.lsp.rpc.start({ exe, '-data', data_dir }, dispatchers, { - cwd = config.cmd_cwd, - env = config.cmd_env, - detached = config.detached, - }) - end, - filetypes = { 'java' }, - root_markers = { 'build.xml', 'pom.xml', 'build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts', '.git' }, - capabilities = capabilities, - on_attach = on_attach, - settings = { - java = { - eclipse = { downloadSources = true }, - configuration = { updateBuildConfiguration = "interactive" }, - maven = { downloadSources = true, updateSnapshots = false }, - project = { - referencedLibraries = { - "lib/**/*.jar", "**/lib/*.jar", - vim.fn.expand("~/.m2/repository/**/*.jar"), - vim.fn.expand("~/.gradle/caches/**/*.jar"), - "target/**/*.jar", "build/libs/**/*.jar", "dist/**/*.jar", - }, - }, - inlayHints = { parameterNames = { enabled = "all" } }, - implementationsCodeLens = { enabled = true }, - referencesCodeLens = { enabled = true }, - sources = { organizeImports = { starThreshold = 9999, staticStarThreshold = 9999 } }, - codeGeneration = { - toString = { template = "${object.className}{${member.name()}=${member.value}, ${otherMembers}}" }, - hashCodeEquals = { useJava7Objects = true }, - useBlocks = true, - }, - completion = { - favoriteStaticMembers = { - "org.junit.Assert.*", "org.junit.Assume.*", - "org.junit.jupiter.api.Assertions.*", "org.junit.jupiter.api.Assumptions.*", - "org.junit.jupiter.api.DynamicContainer.*", "org.junit.jupiter.api.DynamicTest.*", - "org.mockito.Mockito.*", "org.mockito.ArgumentMatchers.*", "org.mockito.Answers.*", - }, - filteredTypes = { "com.sun.*", "io.micrometer.shaded.*", "java.awt.*", "jdk.*", "sun.*" }, - importOrder = { "java", "javax", "com", "org" }, - }, - format = { - enabled = true, - settings = { - profile = "GoogleStyle", - }, - }, - }, - }, -}) -vim.lsp.enable('jdtls') - -vim.lsp.config('kotlin_language_server', { - cmd = mason_cmd('kotlin-language-server'), - filetypes = { 'kotlin' }, - root_markers = { 'settings.gradle', 'settings.gradle.kts', 'build.gradle', 'build.gradle.kts', 'pom.xml', '.git' }, - capabilities = capabilities, - on_attach = on_attach, - settings = { - kotlin = { - compiler = { jvm = { target = "17" } }, - inlayHints = { - typeHints = { enable = true }, - parameterHints = { enable = true }, - }, - }, - }, -}) -vim.lsp.enable('kotlin_language_server') - -vim.api.nvim_create_autocmd("FileType", { - pattern = "kotlin", - callback = function(args) - local bufnr = args.buf - local opts = { buffer = bufnr, noremap = true, silent = true } - - local function run_build_cmd(cmd) - if cmd then vim.cmd('split | terminal ' .. cmd) end - end - - local function get_kotlin_cmd(gradle_cmd, maven_cmd) - local root = vim.fs.root(0, { - 'settings.gradle', 'settings.gradle.kts', 'build.gradle', 'build.gradle.kts', 'pom.xml', - }) - if not root then - vim.notify("No Kotlin project root detected", vim.log.levels.WARN) - return nil - end - if vim.fn.filereadable(root .. '/pom.xml') == 1 then - return 'cd ' .. root .. ' && ' .. maven_cmd - end - return 'cd ' .. root .. ' && ' .. gradle_cmd - end - - vim.keymap.set('n', 'kb', function() - run_build_cmd(get_kotlin_cmd('./gradlew build', 'mvn compile')) - end, vim.tbl_extend('force', opts, { desc = 'Build Kotlin project' })) - - vim.keymap.set('n', 'kt', function() - run_build_cmd(get_kotlin_cmd('./gradlew test', 'mvn test')) - end, vim.tbl_extend('force', opts, { desc = 'Run Kotlin tests' })) - - vim.keymap.set('n', 'kc', function() - run_build_cmd(get_kotlin_cmd('./gradlew clean', 'mvn clean')) - end, vim.tbl_extend('force', opts, { desc = 'Clean Kotlin project' })) - - vim.keymap.set('n', 'kr', function() - run_build_cmd(get_kotlin_cmd('./gradlew run', 'mvn exec:java')) - end, vim.tbl_extend('force', opts, { desc = 'Run Kotlin project' })) - end, -}) - -vim.lsp.config('pyright', { - cmd = mason_cmd('pyright'), - filetypes = { 'python' }, - root_markers = { 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', '.git' }, - capabilities = capabilities, - on_attach = on_attach, -}) -vim.lsp.enable('pyright') - -vim.lsp.config('ts_ls', { - cmd = mason_cmd('typescript-language-server'), - filetypes = { 'javascript', 'typescript', 'javascriptreact', 'typescriptreact', 'vue' }, - root_markers = { 'package.json', 'tsconfig.json', 'jsconfig.json', '.git' }, - capabilities = capabilities, - on_attach = on_attach, -}) -vim.lsp.enable('ts_ls') - -vim.lsp.config('lemminx', { - cmd = mason_cmd('lemminx'), - filetypes = { 'xml' }, - root_markers = { '.git' }, - capabilities = capabilities, - on_attach = on_attach, -}) -vim.lsp.enable('lemminx') - -vim.api.nvim_create_autocmd("FileType", { - pattern = "java", - callback = function() - local root = vim.fs.root(0, { - 'build.xml', 'pom.xml', 'build.gradle', 'build.gradle.kts', - 'settings.gradle', 'settings.gradle.kts', - }) - if not root then return end - - if vim.fn.filereadable(root .. '/pom.xml') == 1 then - vim.b.java_build_system = 'Maven' - vim.b.java_build_file = 'pom.xml' - elseif vim.fn.filereadable(root .. '/build.gradle') == 1 or - vim.fn.filereadable(root .. '/build.gradle.kts') == 1 then - vim.b.java_build_system = 'Gradle' - vim.b.java_build_file = vim.fn.filereadable(root .. '/build.gradle') == 1 - and 'build.gradle' or 'build.gradle.kts' - elseif vim.fn.filereadable(root .. '/build.xml') == 1 then - vim.b.java_build_system = 'Ant' - vim.b.java_build_file = 'build.xml' - end - vim.b.java_project_root = root - end -}) - -vim.api.nvim_create_autocmd("FileType", { - pattern = "java", - callback = function(args) - local bufnr = args.buf - local opts = { buffer = bufnr, noremap = true, silent = true } - - local function run_build_cmd(cmd) - if cmd then vim.cmd('split | terminal ' .. cmd) end - end - - local function get_cmd(maven_cmd, gradle_cmd, ant_cmd) - local bs = vim.b.java_build_system - local root = vim.b.java_project_root - if not bs or not root then - vim.notify("No build system detected", vim.log.levels.WARN) - return nil - end - if bs == 'Maven' then return 'cd ' .. root .. ' && ' .. maven_cmd end - if bs == 'Gradle' then return 'cd ' .. root .. ' && ' .. gradle_cmd end - if bs == 'Ant' then return 'cd ' .. root .. ' && ' .. ant_cmd end - end - - vim.keymap.set('n', 'jb', function() - run_build_cmd(get_cmd('mvn compile', './gradlew build', 'ant compile')) - end, vim.tbl_extend('force', opts, { desc = 'Build Java project' })) - - vim.keymap.set('n', 'jt', function() - run_build_cmd(get_cmd('mvn test', './gradlew test', 'ant test')) - end, vim.tbl_extend('force', opts, { desc = 'Run Java tests' })) - - vim.keymap.set('n', 'jc', function() - run_build_cmd(get_cmd('mvn clean', './gradlew clean', 'ant clean')) - end, vim.tbl_extend('force', opts, { desc = 'Clean Java project' })) - - vim.keymap.set('n', 'jw', function() - local root = vim.b.java_project_root - if not root then - vim.notify("No Java project detected", vim.log.levels.WARN) - return - end - local workspace_dir = jdtls_workspace(root) - vim.lsp.stop_client(vim.lsp.get_clients({ name = 'jdtls' })) - vim.fn.system('rm -rf ' .. workspace_dir) - vim.notify("Cleaned JDTLS workspace for " .. vim.fn.fnamemodify(root, ':t'), vim.log.levels.INFO) - vim.notify("Restart Neovim or run :LspStart to reinitialize", vim.log.levels.INFO) - end, vim.tbl_extend('force', opts, { desc = 'Clean JDTLS workspace cache' })) - end -}) - -return { - on_attach = on_attach, -} diff --git a/nvim/lua/multicursor.lua b/nvim/lua/multicursor.lua deleted file mode 100644 index 1afb772..0000000 --- a/nvim/lua/multicursor.lua +++ /dev/null @@ -1,368 +0,0 @@ -local M = {} - -local ns = vim.api.nvim_create_namespace('multicursor') -local on_key_ns = vim.api.nvim_create_namespace('multicursor_on_key') - -local cursors = nil -local last_real_pos = nil -local active_idx = nil -local in_tracking = false -local in_broadcast = false -local insert_start = nil -local skip_cursor_move = false -local multi_mode = false - -local attached_buffers = {} - -local function gc() - return vim.api.nvim_win_get_cursor(0) -end - -local function sc(pos) - vim.api.nvim_win_set_cursor(0, pos) -end - -local function lc(lnum) - local lines = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false) - return lines[1] or '' -end - -local function blc() - return vim.api.nvim_buf_line_count(0) -end - -local function cc(lnum, col) - if col < 0 then return 0 end - local line = lc(lnum) - return math.min(col, #line) -end - -local function sort_cursors() - table.sort(cursors, function(a, b) - if a[1] == b[1] then return a[2] < b[2] end - return a[1] < b[1] - end) -end - -local function render() - vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) - if not cursors then return end - for _, c in ipairs(cursors) do - pcall(vim.api.nvim_buf_set_extmark, 0, ns, c[1] - 1, cc(c[1], c[2]), { - sign_text = '▎', - sign_hl_group = 'Cursor', - priority = 250, - }) - end -end - -local function refresh_active_idx() - if not cursors then return end - local pos = gc() - for i, c in ipairs(cursors) do - if c[1] == pos[1] and c[2] == pos[2] then - active_idx = i - return - end - end - active_idx = nil -end - -function M.is_active() - return cursors ~= nil -end - -function M.enter() - if cursors then return end - local pos = gc() - cursors = {{pos[1], pos[2]}} - last_real_pos = {pos[1], pos[2]} - active_idx = 1 - multi_mode = true - render() -end - -function M.add_above() - local pos = gc() - if not cursors then - cursors = {{pos[1], pos[2]}} - last_real_pos = {pos[1], pos[2]} - active_idx = 1 - multi_mode = true - end - if pos[1] > 1 then - local ok = true - for _, c in ipairs(cursors) do - if c[1] == pos[1] - 1 and c[2] == pos[2] then ok = false; break end - end - if ok then - table.insert(cursors, {pos[1] - 1, pos[2]}) - sort_cursors() - refresh_active_idx() - render() - end - end -end - -function M.add_below() - local pos = gc() - if not cursors then - cursors = {{pos[1], pos[2]}} - last_real_pos = {pos[1], pos[2]} - active_idx = 1 - multi_mode = true - end - if pos[1] < blc() then - local ok = true - for _, c in ipairs(cursors) do - if c[1] == pos[1] + 1 and c[2] == pos[2] then ok = false; break end - end - if ok then - table.insert(cursors, {pos[1] + 1, pos[2]}) - sort_cursors() - refresh_active_idx() - render() - end - end -end - -function M.exit() - cursors = nil - last_real_pos = nil - active_idx = nil - insert_start = nil - multi_mode = false - vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) -end - -local function do_normal_jk(key, count) - vim.cmd('normal! ' .. (count > 0 and count or 1) .. key) -end - -local function j_handler() - if not multi_mode then - do_normal_jk('j', vim.v.count) - return - end - local count = vim.v.count > 0 and vim.v.count or 1 - for _ = 1, count do - local pos = gc() - if pos[1] >= blc() then break end - M.add_below() - last_real_pos = {pos[1] + 1, pos[2]} - sc({pos[1] + 1, pos[2]}) - end - refresh_active_idx() - render() -end - -local function k_handler() - if not multi_mode then - do_normal_jk('k', vim.v.count) - return - end - local count = vim.v.count > 0 and vim.v.count or 1 - for _ = 1, count do - local pos = gc() - if pos[1] <= 1 then break end - M.add_above() - last_real_pos = {pos[1] - 1, pos[2]} - sc({pos[1] - 1, pos[2]}) - end - refresh_active_idx() - render() -end - -local function move_all_cursors(dlnum, dcol) - if not cursors or in_tracking then return end - in_tracking = true - for _, c in ipairs(cursors) do - c[1] = math.max(1, math.min(blc(), c[1] + dlnum)) - c[2] = cc(c[1], c[2] + dcol) - end - in_tracking = false - render() -end - -local function broadcast_text(inserted, start_lnum, start_col) - if not cursors or #inserted == 0 then return end - local buf = vim.api.nvim_get_current_buf() - local work = {} - for i, c in ipairs(cursors) do - work[#work + 1] = {i, c[1], c[2]} - end - table.sort(work, function(a, b) - if a[2] == b[2] then return a[3] > b[3] end - return a[2] > b[2] - end) - for _, item in ipairs(work) do - local idx, lnum, col = item[1], item[2], item[3] - if lnum ~= start_lnum or col ~= start_col then - local line = lc(lnum) - local new_line = line:sub(1, col) .. inserted .. line:sub(col + 1) - vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, {new_line}) - cursors[idx][2] = col + #inserted - else - cursors[idx][2] = cursors[idx][2] + #inserted - end - end - render() -end - -local function insert_handler() - if not cursors then return end - local pos = gc() - insert_start = {lnum = pos[1], col = pos[2], line = lc(pos[1])} - last_real_pos = nil -end - -local function insert_leave_handler() - local state = insert_start - insert_start = nil - if not cursors or not state then return end - local end_pos = gc() - local final_lnum, final_col = end_pos[1], end_pos[2] - if final_lnum == state.lnum then - local new_line = lc(final_lnum) - local inserted = new_line:sub(state.col + 1, final_col) - broadcast_text(inserted, state.lnum, state.col) - last_real_pos = {gc()[1], gc()[2]} - return - end - last_real_pos = {gc()[1], gc()[2]} -end - -local function cursor_handler() - if not cursors or in_tracking then return end - local new_pos = gc() - - if skip_cursor_move then - skip_cursor_move = false - last_real_pos = {new_pos[1], new_pos[2]} - if active_idx and cursors[active_idx] then - cursors[active_idx][1] = new_pos[1] - cursors[active_idx][2] = new_pos[2] - end - render() - return - end - - if last_real_pos then - local dlnum = new_pos[1] - last_real_pos[1] - local dcol = new_pos[2] - last_real_pos[2] - if dlnum ~= 0 or dcol ~= 0 then - move_all_cursors(dlnum, dcol) - end - end - last_real_pos = {new_pos[1], new_pos[2]} - refresh_active_idx() -end - -local function text_changed_handler() - if not cursors or in_broadcast then return end - in_broadcast = true - local work = {} - for _, c in ipairs(cursors) do - work[#work + 1] = {c[1], c[2]} - end - table.sort(work, function(a, b) - if a[1] == b[1] then return a[2] > b[2] end - return a[1] > b[1] - end) - local feed_dot = vim.api.nvim_replace_termcodes('.', true, false, true) - local origin_idx = active_idx - if not origin_idx then - local current = gc() - for i, c in ipairs(cursors) do - if c[1] == current[1] and c[2] == current[2] then - origin_idx = i - break - end - end - end - for _, pos in ipairs(work) do - if not (origin_idx and cursors[origin_idx] - and cursors[origin_idx][1] == pos[1] - and cursors[origin_idx][2] == pos[2]) then - sc(pos) - vim.api.nvim_feedkeys(feed_dot, 'nx', false) - local np = gc() - for _, c in ipairs(cursors) do - if c[1] == pos[1] and c[2] == pos[2] then - c[1] = np[1] - c[2] = np[2] - break - end - end - end - end - sort_cursors() - render() - in_broadcast = false -end - -local function on_buf_lines(_, _, firstline, lastline, new_lastline) - if not cursors then return end - if new_lastline > lastline then - local shift = new_lastline - lastline - for _, c in ipairs(cursors) do - if c[1] > firstline then - c[1] = c[1] + shift - end - end - elseif new_lastline < lastline then - local deleted = lastline - new_lastline - for _, c in ipairs(cursors) do - if c[1] > lastline then - c[1] = c[1] - deleted - elseif c[1] > firstline then - c[1] = firstline + 1 - end - end - end - skip_cursor_move = true - render() -end - -local function ensure_attached() - local buf = vim.api.nvim_get_current_buf() - if attached_buffers[buf] then return end - attached_buffers[buf] = true - vim.api.nvim_buf_attach(buf, false, { on_lines = on_buf_lines }) -end - -local function setup_autocmds() - local group = vim.api.nvim_create_augroup('multicursor', { clear = true }) - vim.api.nvim_create_autocmd('CursorMoved', { group = group, callback = cursor_handler }) - vim.api.nvim_create_autocmd('InsertEnter', { group = group, callback = insert_handler }) - vim.api.nvim_create_autocmd('InsertLeave', { group = group, callback = insert_leave_handler }) - vim.api.nvim_create_autocmd('TextChanged', { group = group, callback = text_changed_handler }) - vim.api.nvim_create_autocmd('BufEnter', { group = group, callback = ensure_attached }) -end - -local function setup_keymaps() - vim.keymap.set({'n', 'v'}, '', M.add_above, { desc = 'Add cursor above' }) - vim.keymap.set({'n', 'v'}, '', M.add_below, { desc = 'Add cursor below' }) - vim.keymap.set('n', '', M.enter, { desc = 'Enter multi-cursor mode' }) - vim.keymap.set('n', 'j', j_handler, { desc = 'j or add cursor below (multi-cursor)' }) - vim.keymap.set('n', 'k', k_handler, { desc = 'k or add cursor above (multi-cursor)' }) - vim.keymap.set('n', 'gj', j_handler, { desc = 'gj or add cursor below (multi-cursor)' }) - vim.keymap.set('n', 'gk', k_handler, { desc = 'gk or add cursor above (multi-cursor)' }) -end - -vim.on_key(function(key) - if cursors and key == '\27' and vim.fn.mode() == 'n' then - vim.wait(10, function() return vim.fn.getchar(0) ~= 0 end) - if vim.fn.getchar(0) == 0 then - M.exit() - end - end -end, on_key_ns) - -function M.setup() - setup_autocmds() - setup_keymaps() - ensure_attached() -end - -return M diff --git a/nvim/lua/nvim-tmux-navigator.lua b/nvim/lua/nvim-tmux-navigator.lua deleted file mode 100644 index db886ce..0000000 --- a/nvim/lua/nvim-tmux-navigator.lua +++ /dev/null @@ -1,4 +0,0 @@ -return { - "christoomey/vim-tmux-navigator", - lazy = false -} diff --git a/nvim/lua/options.lua b/nvim/lua/options.lua deleted file mode 100644 index 26f2f65..0000000 --- a/nvim/lua/options.lua +++ /dev/null @@ -1,53 +0,0 @@ -vim.wo.number = true -vim.wo.relativenumber = true - -vim.opt.timeoutlen = 500 - -vim.opt.list = true -vim.opt.listchars = { tab = '▸ ', trail = '·', extends = '»', precedes = '«', nbsp = '␣', leadmultispace = '│ ', lead = '│'} - -vim.api.nvim_create_user_command('E', 'Telescope file_browser path=%:p:h select_buffer=true hidden=true', {}) -vim.api.nvim_create_user_command('Tree', 'Telescope file_browser path=%:p:h select_buffer=true hidden=true', {}) - -vim.keymap.set('n', 'e', ':Telescope file_browser path=%:p:h select_buffer=true hidden=true') -vim.keymap.set('n', 'E', function() - local root = vim.fs.root(0, { '.git', 'package.json', 'Makefile', 'pom.xml', 'build.gradle', 'Cargo.toml' }) - local path = root or vim.fn.getcwd() - require('telescope').extensions.file_browser.file_browser({ path = path, hidden = true }) -end) - -vim.keymap.set('n', 'g', ':Telescope live_grep', { noremap = true, silent = true, desc = 'Live grep' }) - -vim.scriptencoding = "utf-8" -vim.opt.encoding = "utf-8" -vim.opt.fileencoding = "utf-8" - -vim.opt.backup = false - -vim.opt.scrolloff = 10 -- was vim.scrolloff (global Lua var, had no effect) - -vim.opt.expandtab = true -vim.opt.smarttab = true -vim.opt.smartindent = true -vim.opt.shiftwidth = 4 -vim.opt.tabstop = 4 -vim.opt.softtabstop = 4 - -vim.opt.swapfile = false - -vim.opt.clipboard = "unnamedplus" - -vim.opt.cursorline = true -vim.opt.cursorlineopt = 'number' - -vim.opt.guicursor = "n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50" - -vim.opt.cmdheight = 0 -vim.opt.showcmd = false - --- search incramental -vim.opt.inccommand = "split" - --- undo dirfor nvim -vim.opt.undodir = vim.fn.stdpath("data") .. "/undodir" -vim.opt.undofile = true diff --git a/nvim/lua/plugins.lua b/nvim/lua/plugins.lua deleted file mode 100644 index 8d19b59..0000000 --- a/nvim/lua/plugins.lua +++ /dev/null @@ -1,368 +0,0 @@ -return -{ - -- Nvim Surround - { - "kylechui/nvim-surround", - version = "*", - event = "VeryLazy", - config = function() - require("nvim-surround").setup({}) - end - }, - - -- Multi-cursor support (replaced by homebrew multicursor.lua) - -- { - -- "mg979/vim-visual-multi", - -- branch = "master", - -- lazy = false, - -- }, - - -- Vim-Tmux-Navigator - { - "christoomey/vim-tmux-navigator", - cmd = { - "TmuxNavigateLeft", - "TmuxNavigateDown", - "TmuxNavigateUp", - "TmuxNavigateRight", - "TmuxNavigatePrevious", - - }, - keys = { - { "", "TmuxNavigateLeft" }, - { "", "TmuxNavigateDown" }, - { "", "TmuxNavigateUp" }, - { "", "TmuxNavigateRight" }, - { "", "TmuxNavigatePrevious" }, - }, - }, - - -- Harpoon2 - { - 'ThePrimeagen/harpoon', - branch = 'harpoon2', - dependencies = { 'nvim-lua/plenary.nvim' }, - config = function() - require('harpoon-config') - end - }, - - -- Rosé Pine - { - 'rose-pine/neovim', - name = 'rose-pine', - priority = 1000, - config = function() - require('rose-pine').setup({ - variant = 'main', - disable_background = true, - disable_float_background = true, - disable_italics = false, - highlight_groups = { - CursorLineNr = { fg = 'gold', bold = true } - } - }) - end - }, - - -- Tokyo Night - { - 'folke/tokyonight.nvim', - lazy = false, - priority = 1000, - opts = { - style = 'night', - transparent = true, - }, - }, - - -- Catppuccin - { - 'catppuccin/nvim', - name = 'catppuccin', - lazy = false, - priority = 1000, - opts = { - flavour = 'mocha', - transparent_background = true, - }, - }, - - -- Gruvbox - { - 'ellisonleao/gruvbox.nvim', - lazy = false, - priority = 1000, - opts = { - transparent_mode = true, - }, - }, - - -- Treesitter - { - 'nvim-treesitter/nvim-treesitter', - branch = 'main', - build = ':TSUpdate', - dependencies = { - { - 'windwp/nvim-ts-autotag', - config = function() - require('nvim-ts-autotag').setup({ - opts = { - enable_close = true, - enable_rename = true, - enable_close_on_slash = true, - }, - }) - end, - }, - }, - }, - - -- Mason (LSP servers, linters, DAP installers) - { - 'williamboman/mason.nvim', - config = function() - require('mason').setup({ - ui = { - icons = { - package_installed = "✓", - package_pending = "➜", - package_uninstalled = "✗" - } - } - }) - end, - }, - - -- Project management - { - "ahmedkhalf/project.nvim", - config = function() - require("project_nvim").setup({ - detection_methods = { "pattern", "lsp" }, - patterns = { ".git", "Makefile", "package.json", "pom.xml", "build.gradle", "Cargo.toml" }, - show_hidden = false, - silent_chdir = true, - }) - end, - }, - - -- Telescope - { - 'nvim-telescope/telescope.nvim', - dependencies = { - 'nvim-lua/plenary.nvim', - 'nvim-telescope/telescope-file-browser.nvim', - 'ahmedkhalf/project.nvim', - }, - config = function() - local telescope = require('telescope') - local actions = require('telescope.actions') - local action_state = require('telescope.actions.state') - - telescope.setup({ - defaults = { - prompt_prefix = ' 🔎 ', - selection_caret = '▸', - file_ignore_patterns = { "%.git/", "%DS_Store$" }, - mappings = { - i = { - [""] = function(prompt_bufnr) - local picker = action_state.get_current_picker(prompt_bufnr) - local finder = picker.finder - finder.hidden = not finder.hidden - picker:refresh(require('telescope.finders').new_oneshot_job( - vim.tbl_flatten({ - "fd", "--type", "f", - finder.hidden and "--hidden" or "--no-hidden", - "--color", "never" - }), - picker.finder.entry_maker - )) - end, - }, - }, - }, - pickers = { - find_files = { hidden = true }, - }, - extensions = { - file_browser = { hidden = true, hijack_netrw = true }, - projects = {}, - }, - }) - - pcall(telescope.load_extension, 'file_browser') - pcall(telescope.load_extension, 'projects') - end, - }, - - -- Which-key - { - "folke/which-key.nvim", - event = "VeryLazy", - opts = { preset = "modern" }, - config = function() - local wk = require("which-key") - wk.setup({ preset = "modern" }) - wk.add({ - { "r", group = "Run" }, - { "w", desc = "Save file" }, - { "q", desc = "Save and quit" }, - { "Q", desc = "Quit without saving" }, - { "e", desc = "File explorer" }, - { "v", desc = "File explorer" }, - { "p", desc = "Projects" }, - { "g", desc = "Live grep" }, - }) - end, - }, - - -- DAP - { - 'mfussenegger/nvim-dap', - dependencies = { - 'rcarriga/nvim-dap-ui', - 'nvim-neotest/nvim-nio', - 'jay-babu/mason-nvim-dap.nvim', - 'mfussenegger/nvim-dap-python', - }, - config = function() - require('dap-config') - end, - }, - - -- Mason DAP - { - 'jay-babu/mason-nvim-dap.nvim', - dependencies = { 'williamboman/mason.nvim', 'mfussenegger/nvim-dap' }, - config = function() - require('mason-nvim-dap').setup({ - ensure_installed = { 'python', 'javadbg', 'codelldb', 'kotlin' }, - automatic_installation = true, - }) - end, - }, - - -- noice.nvim - { - "folke/noice.nvim", - event = "VeryLazy", - dependencies = { "MunifTanjim/nui.nvim" }, - opts = { - cmdline = { - enabled = true, - view = "cmdline_popup", - format = { - cmdline = { icon = ":" }, - search_down = { icon = "🔍 ⌄" }, - search_up = { icon = "🔍 ⌃" }, - filter = { icon = "$" }, - lua = { icon = "☾" }, - help = { icon = "?" }, - }, - }, - messages = { enabled = false }, - popupmenu = { enabled = false }, - lsp = { - override = { - ["vim.lsp.util.convert_input_to_markdown_lines"] = true, - ["vim.lsp.util.stylize_markdown"] = true, - }, - }, - presets = { - bottom_search = true, - command_palette = true, - long_message_to_split = true, - }, - }, - }, - - -- avante.nvim (AI code chat and editing with Anthropic Claude) - { - "yetone/avante.nvim", - event = "VeryLazy", - build = "make", - dependencies = { - "nvim-lua/plenary.nvim", - "stevearc/dressing.nvim", - "nvim-telescope/telescope.nvim", - "nvim-tree/nvim-web-devicons", - "MunifTanjim/nui.nvim", - { - "HakonHarnes/img-clip.nvim", - event = "VeryLazy", - opts = { - default = { - embed_image_as_base64 = false, - }, - }, - }, - }, - config = function() - vim.g.plenary_curl_bin_path = "/usr/bin/curl" - require("avante").setup({ - provider = "claude", - providers = { - claude = { - endpoint = "https://api.anthropic.com", - model = "claude-haiku-4-5", - timeout = 30000, - extra_request_body = { - max_tokens = 4096, - }, - }, - }, - mappings = { - suggestion = { - accept = "", - next = "", - dismiss = "", - }, - }, - behaviour = { - auto_suggestions = false, - }, - }) - end, - }, - - -- alpha-nvim dashboard - { - "goolord/alpha-nvim", - config = function() - require('alpha-config') - end - }, - - -- Typst Preview - { - 'chomosuke/typst-preview.nvim', - ft = 'typst', - version = '1.*', - build = function() require('typst-preview').update() end, - opts = {}, - config = function() - vim.api.nvim_create_user_command('TP', 'TypstPreview', { desc = 'Start Typst preview' }) - vim.api.nvim_create_user_command('TS', 'TypstPreviewStop', { desc = 'Stop Typst preview' }) - vim.api.nvim_create_user_command('TU', 'TypstPreviewUpdate', { desc = 'Update Typst preview' }) - end - }, - - -- Linting (nvim-lint) - { - "mfussenegger/nvim-lint", - config = function() - require('lint-config') - end, - }, - - -- nvim-cmp core + sources - "hrsh7th/nvim-cmp", - "hrsh7th/cmp-nvim-lsp", - "hrsh7th/cmp-buffer", - - -- Snippet engine (kept for DAP/LSP snippet expansion) - 'L3MON4D3/LuaSnip', -} diff --git a/nvim/lua/rename-config.lua b/nvim/lua/rename-config.lua deleted file mode 100644 index bafa69d..0000000 --- a/nvim/lua/rename-config.lua +++ /dev/null @@ -1,405 +0,0 @@ -local M = {} - -local function get_visual_selection() - local ls, cs = vim.fn.line("'<"), vim.fn.col("'<") - local le, ce = vim.fn.line("'>"), vim.fn.col("'>") - if ls ~= le then return nil end - local line = vim.api.nvim_buf_get_lines(0, ls - 1, ls, false)[1] or '' - return line:sub(cs, ce) -end - -local function is_identifier(s) - return s ~= '' and s:match('^[%a_][%w_]*$') ~= nil -end - -function M.open(from_visual) - local orig_win = vim.api.nvim_get_current_win() - local orig_bufnr = vim.api.nvim_get_current_buf() - local orig_pos = vim.api.nvim_win_get_cursor(0) - local init_word = from_visual and get_visual_selection() or nil - init_word = (init_word and init_word ~= '') and init_word or vim.fn.expand('') - - -- Detect if LSP rename is available for this symbol - local lsp_rename = false - if is_identifier(init_word) then - for _, c in ipairs(vim.lsp.get_clients({ bufnr = orig_bufnr })) do - if c.server_capabilities.renameProvider then - lsp_rename = true - break - end - end - end - - local Layout = require('nui.layout') - local Popup = require('nui.popup') - - local results = {} - local focused = 'find' - local stimer = nil - local alive = false - - local find_pop = Popup({ - border = { style = 'rounded', text = { top = ' 🔎 Find ', top_align = 'left' } }, - buf_options = { buftype = 'nofile', modifiable = true }, - }) - local replace_pop = Popup({ - border = { style = 'rounded', text = { top = ' Replace ', top_align = 'left' } }, - buf_options = { buftype = 'nofile', modifiable = true }, - }) - local list_pop = Popup({ - border = { style = 'rounded', text = { top = ' Results ', top_align = 'left' } }, - buf_options = { buftype = 'nofile', modifiable = false }, - win_options = { cursorline = true, wrap = false }, - }) - local hint_pop = Popup({ - border = { style = 'none' }, - buf_options = { buftype = 'nofile', modifiable = false }, - win_options = { winhighlight = 'Normal:Comment' }, - }) - local preview_pop = Popup({ - border = { style = 'rounded', text = { top = ' Preview ', top_align = 'left' } }, - buf_options = { buftype = 'nofile', modifiable = false }, - win_options = { wrap = false, cursorline = true }, - }) - - local total_h = math.max(16, math.min(30, math.floor(vim.o.lines * 0.75))) - local total_w = math.min(vim.o.columns - 4, 170) - local left_w = 48 - - local layout = Layout( - { relative = 'editor', position = '50%', size = { width = total_w, height = total_h } }, - Layout.Box({ - Layout.Box({ - Layout.Box({ - Layout.Box(find_pop, { size = 3 }), - Layout.Box(replace_pop, { size = 3 }), - Layout.Box(list_pop, { grow = 1 }), - }, { dir = 'col', size = left_w }), - Layout.Box(preview_pop, { grow = 1 }), - }, { dir = 'row', grow = 1 }), - Layout.Box(hint_pop, { size = 2 }), - }, { dir = 'col' }) - ) - - local ns = vim.api.nvim_create_namespace('charvim_rename_preview') - - local function get_find() - return vim.api.nvim_buf_get_lines(find_pop.bufnr, 0, 1, false)[1] or '' - end - local function get_replace() - return vim.api.nvim_buf_get_lines(replace_pop.bufnr, 0, 1, false)[1] or '' - end - - local function update_preview(r) - if not (alive and vim.api.nvim_win_is_valid(preview_pop.winid)) then return end - vim.api.nvim_buf_clear_namespace(preview_pop.bufnr, ns, 0, -1) - if not r then - vim.bo[preview_pop.bufnr].modifiable = true - vim.api.nvim_buf_set_lines(preview_pop.bufnr, 0, -1, false, {}) - vim.bo[preview_pop.bufnr].modifiable = false - pcall(function() vim.wo[preview_pop.winid].winbar = '' end) - return - end - - local context = 8 - local lines, hl_row - - local buf = vim.fn.bufnr(r.filename) - if buf ~= -1 and vim.api.nvim_buf_is_loaded(buf) then - local s = math.max(0, r.lnum - context - 1) - lines = vim.api.nvim_buf_get_lines(buf, s, r.lnum + context, false) - hl_row = r.lnum - 1 - s - else - local ok, all = pcall(vim.fn.readfile, r.filename) - if not ok then - vim.bo[preview_pop.bufnr].modifiable = true - vim.api.nvim_buf_set_lines(preview_pop.bufnr, 0, -1, false, { ' (cannot read file)' }) - vim.bo[preview_pop.bufnr].modifiable = false - return - end - local s = math.max(1, r.lnum - context) - local e = math.min(#all, r.lnum + context) - lines = {} - for i = s, e do lines[#lines + 1] = all[i] end - hl_row = r.lnum - s - end - - vim.bo[preview_pop.bufnr].modifiable = true - vim.api.nvim_buf_set_lines(preview_pop.bufnr, 0, -1, false, lines) - vim.bo[preview_pop.bufnr].modifiable = false - - -- Highlight the matched line and the matched text within it - vim.api.nvim_buf_add_highlight(preview_pop.bufnr, ns, 'CursorLine', hl_row, 0, -1) - local pat = get_find() - if pat ~= '' then - local col_s = (r.col or 1) - 1 - vim.api.nvim_buf_add_highlight( - preview_pop.bufnr, ns, 'IncSearch', hl_row, col_s, col_s + #pat) - end - - pcall(vim.api.nvim_win_set_cursor, preview_pop.winid, { hl_row + 1, 0 }) - pcall(vim.fn.win_execute, preview_pop.winid, 'normal! zz') - - local short = vim.fn.fnamemodify(r.filename, ':~:.') - pcall(function() vim.wo[preview_pop.winid].winbar = ' ' .. short .. ':' .. r.lnum end) - - local ft = vim.filetype.match({ filename = r.filename }) or '' - if ft ~= '' and vim.bo[preview_pop.bufnr].filetype ~= ft then - vim.bo[preview_pop.bufnr].filetype = ft - end - end - - local function set_list(lines) - vim.bo[list_pop.bufnr].modifiable = true - vim.api.nvim_buf_set_lines(list_pop.bufnr, 0, -1, false, lines) - vim.bo[list_pop.bufnr].modifiable = false - end - - local function update_results(new_results) - results = new_results - if #results == 0 then - set_list({ ' (no matches)' }) - update_preview(nil) - else - local lines = {} - for _, r in ipairs(results) do - lines[#lines + 1] = string.format(' %s:%d %s', - vim.fn.fnamemodify(r.filename, ':~:.'), r.lnum, r.text) - end - set_list(lines) - -- Show preview for the currently highlighted row (or first result) - local row = vim.api.nvim_win_is_valid(list_pop.winid) - and vim.api.nvim_win_get_cursor(list_pop.winid)[1] or 1 - update_preview(results[row] or results[1]) - end - end - - local function run_search(pat) - if pat == '' then update_results({}); return end - local cwd = vim.fn.getcwd() - vim.fn.jobstart( - { 'rg', '--line-number', '--column', '--no-heading', '--color=never', '--', pat, '.' }, - { - cwd = cwd, - stdout_buffered = true, - on_stdout = function(_, data) - local found = {} - for _, line in ipairs(data or {}) do - if line ~= '' then - local f, ln, col, txt = line:match('^(.-):(%d+):(%d+):(.*)$') - if f then - found[#found + 1] = { - filename = vim.fn.fnamemodify(cwd .. '/' .. f, ':p'), - lnum = tonumber(ln), - col = tonumber(col), - text = txt:match('^%s*(.-)%s*$') or txt, - } - end - end - end - vim.schedule(function() - if alive and vim.api.nvim_win_is_valid(list_pop.winid) then - update_results(found) - end - end) - end, - } - ) - end - - local function debounce(pat) - if stimer then vim.fn.timer_stop(stimer) end - stimer = vim.fn.timer_start(200, function() - stimer = nil - vim.schedule(function() run_search(pat) end) - end) - end - - local function focus_find() - focused = 'find' - vim.api.nvim_set_current_win(find_pop.winid) - vim.cmd('startinsert!') - end - local function focus_replace() - focused = 'replace' - vim.api.nvim_set_current_win(replace_pop.winid) - vim.cmd('startinsert!') - end - local function focus_list() - focused = 'list' - vim.api.nvim_set_current_win(list_pop.winid) - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - update_preview(results[row]) - end - local function cycle() - if focused == 'find' then focus_replace() - elseif focused == 'replace' then focus_list() - else focus_find() end - end - - local function close() - if not alive then return end - alive = false - if stimer then vim.fn.timer_stop(stimer); stimer = nil end - layout:unmount() - -- Restore normal mode in the underlying window after the popup is gone - vim.schedule(function() - if vim.api.nvim_get_mode().mode:sub(1, 1) == 'i' then - vim.cmd('stopinsert') - end - end) - end - - -- Y: accept all occurrences (LSP rename when applicable, else regex via cfdo) - local function accept_all() - local pat = get_find() - local rep = get_replace() - if pat == '' then vim.notify('Find field is empty', vim.log.levels.WARN); return end - if #results == 0 then vim.notify('No matches', vim.log.levels.WARN); return end - - -- Use LSP rename when: LSP available, find term unchanged from original symbol - if lsp_rename and pat == init_word and is_identifier(pat) then - local new_name = rep ~= '' and rep or nil - local saved_win = orig_win - local saved_pos = orig_pos - close() - -- Defer past the close() stopinsert callback so the window is fully settled - vim.schedule(function() - if vim.api.nvim_win_is_valid(saved_win) then - vim.api.nvim_set_current_win(saved_win) - vim.api.nvim_win_set_cursor(saved_win, saved_pos) - end - vim.lsp.buf.rename(new_name) - end) - return - end - - -- Regex replace across all matched files via quickfix - local qfl = vim.tbl_map(function(r) - return { filename = r.filename, lnum = r.lnum, col = r.col, text = r.text } - end, results) - close() - vim.fn.setqflist(qfl) - local ep = vim.fn.escape(pat, '/\\') - local er = vim.fn.escape(rep, '/\\&') - vim.cmd('cfdo %s/\\V' .. ep .. '/' .. er .. '/ge | update') - vim.cmd('cclose') - end - - -- y: accept the single occurrence under the list cursor - local function accept_one() - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - local r = results[row] - if not r then return end - local pat = get_find() - local rep = get_replace() - if pat == '' then return end - - local ep = '\\V' .. vim.fn.escape(pat, '\\') - local er = vim.fn.escape(rep, '\\&') - - local buf = vim.fn.bufnr(r.filename) - if buf ~= -1 and vim.api.nvim_buf_is_loaded(buf) then - local line = vim.api.nvim_buf_get_lines(buf, r.lnum - 1, r.lnum, false)[1] or '' - vim.api.nvim_buf_set_lines(buf, r.lnum - 1, r.lnum, false, - { vim.fn.substitute(line, ep, er, '') }) - else - local ok, lines = pcall(vim.fn.readfile, r.filename) - if not ok or not lines[r.lnum] then return end - lines[r.lnum] = vim.fn.substitute(lines[r.lnum], ep, er, '') - vim.fn.writefile(lines, r.filename) - end - - table.remove(results, row) - update_results(results) - end - - local function jump() - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - local r = results[row] - if not r then return end - close() - vim.cmd('edit ' .. vim.fn.fnameescape(r.filename)) - vim.api.nvim_win_set_cursor(0, { r.lnum, (r.col or 1) - 1 }) - end - - local function bmap(pop, modes, key, fn) - for _, m in ipairs(modes) do - pop:map(m, key, fn, { noremap = true, silent = true }) - end - end - - layout:mount() - alive = true - - vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, { - buffer = list_pop.bufnr, - callback = function() - if not alive then return end - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - update_preview(results[row]) - end, - }) - - local mode_label = lsp_rename and ' · LSP rename' or ' · Regex' - vim.bo[hint_pop.bufnr].modifiable = true - vim.api.nvim_buf_set_lines(hint_pop.bufnr, 0, -1, false, { - '', - ' cycle · j/k list · y accept · Y accept all · jump · close' .. mode_label, - }) - vim.bo[hint_pop.bufnr].modifiable = false - - vim.api.nvim_buf_set_lines(find_pop.bufnr, 0, -1, false, { init_word }) - - vim.api.nvim_create_autocmd({ 'TextChangedI', 'TextChanged' }, { - buffer = find_pop.bufnr, - callback = function() debounce(get_find()) end, - }) - - -- Find panel: Tab cycles, Esc closes, Y (normal) accepts all - bmap(find_pop, { 'i', 'n' }, '', cycle) - bmap(find_pop, { 'i', 'n' }, '', close) - bmap(find_pop, { 'n' }, 'Y', accept_all) - - -- Replace panel: Tab cycles, Esc closes, CR/Y accepts all - bmap(replace_pop, { 'i', 'n' }, '', cycle) - bmap(replace_pop, { 'i', 'n' }, '', close) - bmap(replace_pop, { 'i', 'n' }, '', accept_all) - bmap(replace_pop, { 'n' }, 'Y', accept_all) - - local function list_down() - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - if row < #results then vim.api.nvim_win_set_cursor(list_pop.winid, { row + 1, 0 }) end - end - local function list_up() - local row = vim.api.nvim_win_get_cursor(list_pop.winid)[1] - if row > 1 then vim.api.nvim_win_set_cursor(list_pop.winid, { row - 1, 0 }) end - end - - -- List panel: all keys mapped for both modes (mode-switching in nui is unreliable) - bmap(list_pop, { 'i', 'n' }, '', cycle) - bmap(list_pop, { 'i', 'n' }, '', close) - bmap(list_pop, { 'i', 'n' }, 'q', close) - bmap(list_pop, { 'i', 'n' }, '', jump) - bmap(list_pop, { 'i', 'n' }, 'y', accept_one) - bmap(list_pop, { 'i', 'n' }, 'Y', accept_all) - bmap(list_pop, { 'i', 'n' }, 'j', list_down) - bmap(list_pop, { 'i', 'n' }, 'k', list_up) - bmap(list_pop, { 'i', 'n' }, '', list_down) - bmap(list_pop, { 'i', 'n' }, '', list_up) - - run_search(init_word) - focus_find() -end - -function M.setup() - vim.api.nvim_create_user_command('Find', function(opts) - M.open(opts.range > 0) - end, { desc = 'Find & replace / rename', range = true }) - - vim.keymap.set('n', 'S', function() M.open(false) end, { desc = 'Find & replace / rename' }) - vim.keymap.set('v', 'S', function() M.open(true) end, { desc = 'Find & replace / rename (selection)' }) -end - -M.setup() -return M diff --git a/nvim/lua/statusline.lua b/nvim/lua/statusline.lua deleted file mode 100644 index 2c1ba8c..0000000 --- a/nvim/lua/statusline.lua +++ /dev/null @@ -1,82 +0,0 @@ -local mode_map = { - n = { label = 'N', hl = 'StatusLineNormal' }, - i = { label = 'I', hl = 'StatusLineInsert' }, - v = { label = 'V', hl = 'StatusLineVisual' }, - V = { label = 'V', hl = 'StatusLineVisual' }, - ['\22'] = { label = 'V', hl = 'StatusLineVisual' }, - c = { label = 'C', hl = 'StatusLineCommand' }, - R = { label = 'R', hl = 'StatusLineReplace' }, - r = { label = 'R', hl = 'StatusLineReplace' }, - t = { label = 'T', hl = 'StatusLineNormal' }, -} - -local name_map = { - n = 'NORMAL', - i = 'INSERT', - v = 'VISUAL', - V = 'V-LINE', - ['\22'] = 'V-BLOCK', - c = 'COMMAND', - s = 'SELECT', - S = 'S-LINE', - ['\19'] = 'S-BLOCK', - R = 'REPLACE', - r = 'REPLACE', - ['!'] = 'SHELL', - t = 'TERMINAL', -} - -local function get_mode_info() - local mode = vim.api.nvim_get_mode().mode - local info = mode_map[mode] or { label = mode:upper(), hl = 'StatusLineNormal' } - return info.label, info.hl, (name_map[mode] or mode:upper()) -end - -function _G.custom_statusline() - local label, hl, name = get_mode_info() - - local filename = vim.fn.expand('%:t') - if filename == '' then filename = '[No Name]' end - - local modified = vim.bo.modified and ' [+]' or '' - - local saved_status = '' - if vim.g.statusline_save_msg then - saved_status = string.format('%%#StatusLineSaved# %s %%*', vim.g.statusline_save_msg) - vim.defer_fn(function() - vim.g.statusline_save_msg = nil - vim.cmd('redrawstatus') - end, 2000) - end - - local left = string.format('%%#%s# %s %%* %s%s', hl, label, filename, modified) - local right = string.format('%s %s %%p%%%% %%c Help', saved_status, name) - - return left .. '%=' .. right -end - --- mode highlights -vim.api.nvim_set_hl(0, 'StatusLineNormal', { fg = '#191724', bg = '#f6c177', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLineInsert', { fg = '#191724', bg = '#9ccfd8', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLineVisual', { fg = '#191724', bg = '#c4a7e7', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLineCommand', { fg = '#191724', bg = '#eb6f92', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLineReplace', { fg = '#191724', bg = '#eb6f92', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLineSaved', { fg = '#191724', bg = '#c4a7e7', bold = true }) -vim.api.nvim_set_hl(0, 'StatusLine', { fg = '#e0def4', bg = '#191724' }) -vim.api.nvim_set_hl(0, 'StatusLineNC', { fg = '#6e6a86', bg = '#191724' }) - -vim.opt.statusline = '%!v:lua.custom_statusline()' -vim.opt.laststatus = 2 - -vim.api.nvim_create_autocmd('BufWritePost', { - callback = function() - vim.g.statusline_save_msg = 'written' - vim.cmd('redrawstatus') - end -}) - -vim.api.nvim_create_autocmd('ModeChanged', { - callback = function() - vim.cmd('redrawstatus') - end -}) diff --git a/nvim/lua/theme-switcher.lua b/nvim/lua/theme-switcher.lua deleted file mode 100644 index b2e450e..0000000 --- a/nvim/lua/theme-switcher.lua +++ /dev/null @@ -1,215 +0,0 @@ -local M = {} - -local themes = { - { - name = 'Auto', - setup = function() - vim.cmd.colorscheme('default') - end, - }, - { - name = 'Knew-pines', - setup = function() - require('rose-pine').setup({ - variant = 'main', - disable_background = true, - disable_float_background = true, - disable_italics = false, - highlight_groups = { - CursorLineNr = { fg = 'gold', bold = true }, - }, - }) - vim.cmd.colorscheme('rose-pine') - end, - }, - { - name = 'Noir-cat', - setup = function() - vim.cmd.colorscheme('noir-cat') - end, - }, - { - name = 'Tokyo Night', - setup = function() - require('tokyonight').setup({ style = 'night', transparent = true }) - vim.cmd.colorscheme('tokyonight') - end, - }, - { - name = 'Catppuccin', - setup = function() - require('catppuccin').setup({ flavour = 'mocha', transparent_background = true }) - vim.cmd.colorscheme('catppuccin') - end, - }, - { - name = 'Gruvbox', - setup = function() - require('gruvbox').setup({ transparent_mode = true }) - vim.cmd.colorscheme('gruvbox') - end, - }, - { - name = 'Habamax', - setup = function() - vim.cmd.colorscheme('habamax') - end, - }, -} - -local current_theme_name = nil -local persist_path = vim.fn.stdpath('data') .. '/charvim_theme' - -local function save_theme(name) - local f = io.open(persist_path, 'w') - if f then - f:write(name) - f:close() - end -end - -local function load_saved_theme() - local f = io.open(persist_path, 'r') - if not f then return nil end - local name = f:read('*l') - f:close() - return name -end - -local function apply_transparent_background() - vim.api.nvim_set_hl(0, 'Normal', { bg = 'none' }) - vim.api.nvim_set_hl(0, 'NormalFloat', { bg = 'none' }) -end - -local function apply_theme(theme, persist) - theme.setup() - apply_transparent_background() - current_theme_name = theme.name - if persist then save_theme(theme.name) end -end - -local function restore_theme(name) - for _, t in ipairs(themes) do - if t.name == name then - apply_theme(t) - return - end - end -end - -function M.select_theme() - local ok, pickers = pcall(require, 'telescope.pickers') - if not ok then - -- fallback when telescope is unavailable - local items = vim.tbl_map(function(t) return t.name end, themes) - vim.ui.select(items, { - prompt = 'Select theme', - format_item = function(item) - return (item == current_theme_name and '▸ ' or ' ') .. item - end, - }, function(choice) - if not choice then return end - for _, t in ipairs(themes) do - if t.name == choice then apply_theme(t, true) end - end - end) - return - end - - local finders = require('telescope.finders') - local conf = require('telescope.config').values - local actions = require('telescope.actions') - local action_state = require('telescope.actions.state') - - local saved = current_theme_name or 'Auto' - - pickers.new({ - layout_strategy = 'center', - layout_config = { width = 0.28, height = 0.45 }, - sorting_strategy = 'ascending', - }, { - prompt_title = ' Themes', - previewer = false, - finder = finders.new_table({ - results = themes, - entry_maker = function(theme) - return { - value = theme, - display = (theme.name == saved and ' • ' or ' ') .. theme.name, - ordinal = theme.name, - } - end, - }), - sorter = conf.generic_sorter({}), - attach_mappings = function(prompt_bufnr, map) - local function preview_selected() - local entry = action_state.get_selected_entry() - if entry then - entry.value.setup() - apply_transparent_background() - end - end - - local function do_cancel() - restore_theme(saved) - actions.close(prompt_bufnr) - end - - -- wrap movement to live-preview each theme - map('i', '', function() actions.move_selection_next(prompt_bufnr); preview_selected() end) - map('i', '', function() actions.move_selection_previous(prompt_bufnr); preview_selected() end) - map('i', '', function() actions.move_selection_next(prompt_bufnr); preview_selected() end) - map('i', '', function() actions.move_selection_previous(prompt_bufnr); preview_selected() end) - map('n', 'j', function() actions.move_selection_next(prompt_bufnr); preview_selected() end) - map('n', 'k', function() actions.move_selection_previous(prompt_bufnr); preview_selected() end) - - -- confirm - actions.select_default:replace(function() - local entry = action_state.get_selected_entry() - actions.close(prompt_bufnr) - if entry then - apply_theme(entry.value, true) - else - restore_theme(saved) - end - end) - - -- cancel — restore original - map('i', '', do_cancel) - map('n', 'q', do_cancel) - map('n', '', do_cancel) - - return true - end, - }):find() -end - -function M.set_theme(name) - for _, t in ipairs(themes) do - if t.name:lower() == name:lower() then - apply_theme(t, true) - return - end - end - vim.notify('Unknown theme: ' .. name, vim.log.levels.ERROR) -end - -function M.setup() - vim.schedule(function() - local saved = load_saved_theme() - if saved then - restore_theme(saved) - else - apply_theme(themes[1]) - end - end) - - vim.api.nvim_create_user_command('ThemeSelect', M.select_theme, { desc = 'Open theme picker' }) - vim.api.nvim_create_user_command('Theme', function(opts) - M.set_theme(opts.args) - end, { nargs = 1, desc = 'Switch to theme by name' }) -end - -M.setup() - -return M diff --git a/nvim/lua/treesitter-config.lua b/nvim/lua/treesitter-config.lua deleted file mode 100644 index 9949445..0000000 --- a/nvim/lua/treesitter-config.lua +++ /dev/null @@ -1,47 +0,0 @@ -local status_ok, ts = pcall(require, 'nvim-treesitter') -if not status_ok then return end - -vim.filetype.add({ - extension = { cr = 'crystal', plist = 'xml' }, -}) - -ts.setup({ - ensure_installed = { - "c", "lua", "vim", "vimdoc", "query", - "javascript", "typescript", "python", "rust", "go", - "html", "css", "json", "markdown", "bash", "yaml", "toml", - "xml", "kotlin" - }, - - sync_install = false, - auto_install = true, - ignore_install = {}, - - highlight = { - enable = true, - disable = function(lang, buf) - local max_filesize = 100 * 1024 - -- get_parser() returns nil on failure in 0.12 instead of throwing - local parser = vim.treesitter.get_parser(buf, lang) - if not parser then return true end - local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(buf)) - if ok and stats and stats.size > max_filesize then return true end - end, - additional_vim_regex_highlighting = false, - }, - - indent = { - enable = true, - disable = { "python" }, - }, - - incremental_selection = { - enable = true, - keymaps = { - init_selection = "gnn", - node_incremental = "grn", - scope_incremental = "grc", - node_decremental = "grm", - }, - }, -}) diff --git a/nvim/lua/user-plugins.lua b/nvim/lua/user-plugins.lua new file mode 100644 index 0000000..83dc264 --- /dev/null +++ b/nvim/lua/user-plugins.lua @@ -0,0 +1,3 @@ +-- user-plugins.lua — personal plugins (git-ignored, never overwritten by updates) +-- Return a list of lazy.nvim plugin specs: https://lazy.folke.io/spec +return {} diff --git a/nvim/lua/user.lua b/nvim/lua/user.lua new file mode 100644 index 0000000..634141e --- /dev/null +++ b/nvim/lua/user.lua @@ -0,0 +1,6 @@ +-- user.lua — personal customizations (git-ignored, never overwritten by updates) +-- Add keymaps, options, autocommands, or anything else here. +-- This file is loaded last, so it can override any default setting. +-- +-- vim.opt.wrap = true +-- vim.keymap.set('n', 'x', ':!echo hi', { desc = 'My command' }) diff --git a/roadmap.html b/roadmap.html new file mode 100644 index 0000000..6e8bae5 --- /dev/null +++ b/roadmap.html @@ -0,0 +1,333 @@ + + + + + + Roadmap | CharVim + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +

Roadmap

+

From the very first commit to what's being built next, this is the full timeline of CharVim's evolution.

+
+ +
+ + +
+
+ + +
+ +
v1.6.0
+
Planned for 2026
+

Extended Language Support

+
+

Expanding the LSP and DAP ecosystem with additional language servers, better adapter reliability, and quality-of-life improvements across the board.

+
+ Rust / rust-analyzer + Ruby / solargraph + Better Swift DAP + Snippet library + Save popup v2 +
+
+
+ + +
+ +
v2.0.0
+
Planned for the future
+

CLI Tooling, Theme Authoring, and In-Editor Updates

+
+

This release focuses on making CharVim feel like a complete, polished product from the command line down. A dedicated CLI will wrap common workflows, a theme authoring guide and toolkit will make it easy to build and share custom themes, and the update system will move fully in-editor so you never have to leave Neovim to stay current.

+
+ cvim CLI command + Project scaffolding via CLI + Theme authoring guide + Theme template and validator + In-editor auto-update UI + Update diff preview + Plugin extension API +
+
+
+ + +
+ +
v3.0.0
+
Planned, long term
+

CharVim as a Standalone Distribution

+
+

The far-horizon goal: CharVim becomes its own self-contained program. No Neovim installation required, no plugin manager, no external dependencies to wrangle. The editor runtime, language servers, and tooling ship together as a single installable application. A native GUI interface is part of this release, making CharVim accessible as a first-class desktop editor while keeping the full modal Vim experience intact underneath.

+
+ Standalone binary + Native GUI interface + Bundled language servers + No plugin manager required + Self-updating runtime + Cross-platform app bundle +
+
+
+ + +
+ +
v1.5.2
+
June 26, 2026
+

Cross-Platform Setup

+
+

Rewrote the setup script to support macOS (Homebrew), Debian/Ubuntu (apt), and Arch Linux (pacman) alongside the existing Fedora support. Each platform function checks for missing binaries before installing, keeping installs minimal.

+
+ macOS via brew + Debian / Ubuntu via apt + Arch Linux via pacman + Per-platform installer design + Expanded dependency list +
+
+
+ + +
+ +
v1.5.1
+
June 26, 2026
+

Find & Replace Panel + Theme Persistence

+
+

Replaced the old inline rename with a full find & replace panel backed by ripgrep. Rewrote the theme switcher with live Telescope preview and cross-session persistence. Switched avante from local Ollama to Anthropic Claude.

+
+ rename-config.lua panel + Live ripgrep search + Theme persistence + Telescope theme preview + Persistent undo + avante → Anthropic Claude + SourceKit-LSP C/C++ conflict +
+
+
+ + +
+ +
v1.5.0
+
June 17, 2026
+

AI, Multi-Theme & Linting

+
+

Introduced local AI code completion via avante.nvim + Ollama, added Tokyo Night, Catppuccin, and Gruvbox themes with a runtime theme-switcher module, and integrated configurable linting via nvim-lint. GitHub Copilot was removed.

+
+ avante.nvim (Ollama) + Tokyo Night theme + Catppuccin theme + Gruvbox theme + theme-switcher module + lint-config.lua + noir-cat colorscheme + nvim-cmp + LuaSnip (restored) + GitHub Copilot +
+
+
+ + +
+ +
v1.3.0
+
May 6, 2026
+

Neovim 0.12 Compatibility

+
+

Major internal refactoring and optimizations for Neovim 0.12. Switched to native LSP completion, added Mason dashboard integration, new file runners, and resolved several breaking API changes.

+
+ Mason in dashboard + HTML / Python / Java runners + nvim-ts-autotag + Native LSP completion + 0.12 API compatibility + Treesitter parser retrieval + scrolloff option +
+
+
+ + +
+ +
v1.2.0
+
March 29, 2026
+

Project Management & CI

+
+

Added project.nvim for smart project detection and workspace switching via Telescope. Set up GitHub Actions for Neovim configuration tests.

+
+ project.nvim + GitHub Actions CI +
+
+
+ + +
+ +
v1.1.0
+
March 10, 2026
+

Debugger Integration & Setup Script

+
+

Integrated DAP debuggers for Java, Swift, Python, and C/C++ with customized UI layouts and session management. Added setup.sh for easier installation and improved COBOL language support.

+
+ DAP (Java, Swift, Python, C/C++) + setup.sh installer + COBOL ftplugin + GitHub Actions setup tests + Symlink creation reliability +
+
+
+ + +
+ +
v0.1.0
+
March 5, 2026
+

Initial Release

+
+

The first public release of CharVim. A full Neovim configuration with comprehensive LSP support, GitHub Copilot AI, a beautiful dashboard, telescope fuzzy finding, harpoon file navigation, and tmux integration.

+
+ GitHub Copilot + LSP via mason.nvim + JDTLS, Tinymist, gopls + alpha-nvim dashboard + noice.nvim + rose-pine colorscheme + telescope.nvim + harpoon2 + vim-tmux-navigator + nvim-surround + Official wiki +
+
+
+ +
+ + +
+ +
+ + + + + + + diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..bbbf25c --- /dev/null +++ b/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://ctrluserknown.github.io/Charvim/sitemap.xml diff --git a/setup.sh b/setup.sh deleted file mode 100755 index e5792d6..0000000 --- a/setup.sh +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env bash - -# --- CharVim: Install Script --- -# Author: CtrlUserUnknown -# GitHub: www.github.com/CrtlUserKnown - -REPO_URL="https://github.com/CrtlUserKnown/Charvim.git" -CONFIG_DIR="$HOME/.config" -CHARVIM_DIR="$CONFIG_DIR/charvim" -GUM_TEMP_PATH="/tmp/gum" - -# Variables for rollback -CREATED_DIR="" -CREATED_LINK="" -BACKUP_MADE="" - -# 1. Cleanup/Rollback on Interrupt -cleanup() { - echo -e "\n\033[31mInstallation cancelled. Rolling back...\033[0m" - - [[ -n "$CREATED_LINK" && -L "$CREATED_LINK" ]] && rm "$CREATED_LINK" - if [[ -n "$BACKUP_MADE" && -d "$BACKUP_MADE" ]]; then - mv "$BACKUP_MADE" "${BACKUP_MADE%.bak.*}" - fi - [[ -n "$CREATED_DIR" && -d "$CREATED_DIR" ]] && rm -rf "$CREATED_DIR" - [[ "$GUM" == "$GUM_TEMP_PATH" ]] && rm -f "$GUM_TEMP_PATH" - - exit 1 -} -trap cleanup SIGINT SIGTERM - -# 2. Temporary gum installation -install_gum() { - if command -v gum &>/dev/null; then - GUM="gum" - return - fi - if [[ -f "$GUM_TEMP_PATH" ]]; then - GUM="$GUM_TEMP_PATH" - return - fi - OS=$(uname -s); ARCH=$(uname -m) - [[ "$ARCH" == "x86_64" ]] && ARCH="x86_64" - [[ "$ARCH" == "arm64" ]] && ARCH="arm64" - GUM_URL=$(curl -s https://api.github.com/repos/charmbracelet/gum/releases/latest | grep -o "https://.*gum_${OS}_${ARCH}.tar.gz" | head -n 1) - if [[ -n "$GUM_URL" ]]; then - curl -sL "$GUM_URL" -o /tmp/gum.tar.gz - tar -xzf /tmp/gum.tar.gz -C /tmp gum - GUM="/tmp/gum" - rm /tmp/gum.tar.gz - else - GUM="" - fi -} - -# 3. Helper for same-line updates -# Usage: run_step "Initial Title" "Final Result Text" "Command" -run_step() { - local title="$1" - local result="$2" - local cmd="$3" - - if [[ -n "$GUM" ]]; then - $GUM spin --spinner dots --title " $title" -- bash -c "$cmd" - echo -e "\033[1A\033[K $result" - else - echo -n "$title... " - eval "$cmd" - echo "Done." - echo " $result" - fi -} - -# 4. Distro detection -detect_distro() { - DISTRO="" - DISTRO_LIKE="" - if [[ "$(uname -s)" == "Darwin" ]]; then - DISTRO="macos" - return - fi - if [[ -f /etc/os-release ]]; then - source /etc/os-release - DISTRO="${ID,,}" - DISTRO_LIKE="${ID_LIKE,,}" - fi -} - -# 5. Install dependencies — one function per package manager - -install_deps_macos() { - if ! command -v brew &>/dev/null; then - echo "Homebrew not found. Install it from https://brew.sh and re-run." - exit 1 - fi - local pkgs=(neovim git ripgrep fd python3 make node) - local bins=(nvim git rg fd python3 make node) - local missing_pkgs=() - for i in "${!pkgs[@]}"; do - command -v "${bins[$i]}" &>/dev/null || missing_pkgs+=("${pkgs[$i]}") - done - [[ ${#missing_pkgs[@]} -eq 0 ]] && return - run_step \ - "Installing dependencies (${missing_pkgs[*]})" \ - "installed: ${missing_pkgs[*]}" \ - "brew install ${missing_pkgs[*]} &>/dev/null" -} - -install_deps_fedora() { - local pkgs=(neovim git ripgrep fd-find python3 make nodejs) - local bins=(nvim git rg fd python3 make node) - local missing_pkgs=() - for i in "${!pkgs[@]}"; do - command -v "${bins[$i]}" &>/dev/null || missing_pkgs+=("${pkgs[$i]}") - done - [[ ${#missing_pkgs[@]} -eq 0 ]] && return - run_step \ - "Installing dependencies (${missing_pkgs[*]})" \ - "installed: ${missing_pkgs[*]}" \ - "sudo dnf install -y ${missing_pkgs[*]} &>/dev/null" -} - -install_deps_debian() { - local pkgs=(neovim git ripgrep fd-find python3 make nodejs) - local bins=(nvim git rg fdfind python3 make node) - local missing_pkgs=() - for i in "${!pkgs[@]}"; do - command -v "${bins[$i]}" &>/dev/null || missing_pkgs+=("${pkgs[$i]}") - done - [[ ${#missing_pkgs[@]} -eq 0 ]] && return - run_step \ - "Updating package lists" \ - "package lists updated" \ - "sudo apt-get update -qq &>/dev/null" - run_step \ - "Installing dependencies (${missing_pkgs[*]})" \ - "installed: ${missing_pkgs[*]}" \ - "sudo apt-get install -y ${missing_pkgs[*]} &>/dev/null" -} - -install_deps_arch() { - local pkgs=(neovim git ripgrep fd python make nodejs) - local bins=(nvim git rg fd python3 make node) - local missing_pkgs=() - for i in "${!pkgs[@]}"; do - command -v "${bins[$i]}" &>/dev/null || missing_pkgs+=("${pkgs[$i]}") - done - [[ ${#missing_pkgs[@]} -eq 0 ]] && return - run_step \ - "Installing dependencies (${missing_pkgs[*]})" \ - "installed: ${missing_pkgs[*]}" \ - "sudo pacman -S --noconfirm ${missing_pkgs[*]} &>/dev/null" -} - -install_gum -detect_distro - -if [[ -n "$GUM" ]]; then - clear - $GUM style --foreground 212 --bold "CharVim Installation" - echo "" -fi - -if [[ "$DISTRO" == "macos" ]]; then - install_deps_macos -elif [[ "$DISTRO" == "fedora" ]] || [[ "$DISTRO_LIKE" == *"fedora"* ]]; then - install_deps_fedora -elif [[ "$DISTRO" == "ubuntu" ]] || [[ "$DISTRO" == "debian" ]] || [[ "$DISTRO_LIKE" == *"debian"* ]]; then - install_deps_debian -elif [[ "$DISTRO" == "arch" ]] || [[ "$DISTRO_LIKE" == *"arch"* ]]; then - install_deps_arch -fi - -# Step: ~/.config -if [[ ! -d "$CONFIG_DIR" ]]; then - run_step "Preparing directory" "directory: $CONFIG_DIR" "mkdir -p '$CONFIG_DIR'" -fi - -# Step: Source Detection — remote URL check dropped intentionally. -# Presence of .git and nvim/ in the working directory is sufficient. -IS_CLONED=false -if [[ -d ".git" ]] && [[ -d "nvim" ]]; then - IS_CLONED=true - SOURCE_DIR="$(pwd)" -fi - -# Step: Handle Files -if [[ "$IS_CLONED" == "true" ]]; then - run_step "Checking source" "local source detected: $SOURCE_DIR" "true" -else - if [[ -d "$CHARVIM_DIR" ]]; then - run_step "Updating repository" "updated repository: $CHARVIM_DIR" "git -C '$CHARVIM_DIR' pull &>/dev/null" - else - run_step "Cloning repository" "cloned to: $CHARVIM_DIR" "git clone '$REPO_URL' '$CHARVIM_DIR' &>/dev/null" - CREATED_DIR="$CHARVIM_DIR" - fi - SOURCE_DIR="$CHARVIM_DIR" -fi - -# Step: Backup -if [[ -L "$CONFIG_DIR/nvim" ]] || [[ -d "$CONFIG_DIR/nvim" ]]; then - BACKUP_DIR="$CONFIG_DIR/nvim.bak.$(date +%Y%m%d_%H%M%S)" - run_step "Backing up config" "backup created: $BACKUP_DIR" "mv '$CONFIG_DIR/nvim' '$BACKUP_DIR'" - BACKUP_MADE="$BACKUP_DIR" -fi - -# Step: Symlink -run_step "Linking files" "linked: $SOURCE_DIR/nvim -> $CONFIG_DIR/nvim" "ln -s '$SOURCE_DIR/nvim' '$CONFIG_DIR/nvim'" -CREATED_LINK="$CONFIG_DIR/nvim" - -# Step: Cleanup gum -if [[ "$GUM" == "$GUM_TEMP_PATH" ]]; then - run_step "Finishing" "cleaned up temporary files" "rm '$GUM_TEMP_PATH'" -fi - -if [[ -n "$GUM" ]]; then - echo "" - $GUM style --foreground 82 --bold "CharVim Installation Complete!" -else - echo -e "\nCharVim Installation Complete!" -fi diff --git a/site.webmanifest b/site.webmanifest new file mode 100644 index 0000000..02d9966 --- /dev/null +++ b/site.webmanifest @@ -0,0 +1,25 @@ +{ + "name": "CharVim", + "short_name": "CharVim", + "description": "Neovim, crafted for flow. A custom Neovim configuration built for speed and a seamless development experience.", + "start_url": "./index.html", + "scope": "./", + "display": "browser", + "background_color": "#191724", + "theme_color": "#191724", + "lang": "en", + "categories": ["developer tools", "productivity"], + "icons": [ + { + "src": "img/favicon/charvim.svg", + "sizes": "any", + "type": "image/svg+xml" + }, + { + "src": "img/favicon/charvim-atl.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "maskable" + } + ] +} diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..d2858fd --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,39 @@ + + + + + https://ctrluserknown.github.io/Charvim/ + 2026-06-30 + weekly + 1.0 + + + + https://ctrluserknown.github.io/Charvim/about.html + 2026-06-30 + monthly + 0.8 + + + + https://ctrluserknown.github.io/Charvim/docs.html + 2026-06-30 + weekly + 0.9 + + + + https://ctrluserknown.github.io/Charvim/roadmap.html + 2026-06-30 + monthly + 0.7 + + + + https://ctrluserknown.github.io/Charvim/changelog.html + 2026-06-30 + weekly + 0.8 + + +