├── .gitignore
├── .stylua.toml
├── LICENSE
├── README.md
├── README_zh.md
├── init.lua
├── lua
├── core
│ ├── basic.lua
│ ├── commands.lua
│ ├── ft.lua
│ ├── health.lua
│ ├── init.lua
│ ├── keymap.lua
│ ├── miscellaneous.lua
│ ├── symbols.lua
│ └── utils.lua
├── lsp
│ ├── completion.lua
│ ├── extra.lua
│ ├── format.lua
│ ├── init.lua
│ ├── lsp.lua
│ └── plugins.lua
└── plugins
│ ├── colorscheme.lua
│ ├── config.lua
│ ├── init.lua
│ ├── keymap.lua
│ ├── lazy.lua
│ └── utils.lua
└── screenshots
├── 1.jpg
├── 2.jpg
├── 3.jpg
└── 4.jpg
/.gitignore:
--------------------------------------------------------------------------------
1 | lazy-lock.json
2 | lua/custom/
3 | bin/
4 |
--------------------------------------------------------------------------------
/.stylua.toml:
--------------------------------------------------------------------------------
1 | column_width = 120
2 | line_endings = "Unix"
3 | indent_type = "Spaces"
4 | indent_width = 4
5 | quote_style = "AutoPreferDouble"
6 | call_parentheses = "None"
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023-2025 Shaobin Jiang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
IceNvim
2 |
3 |
4 |
5 | *Please consider starring the project ✨✨✨. Your support is much appreciated.*
6 |
7 | [](https://github.com/neovim/neovim)
8 | [](https://github.com/neovim/neovim)
9 | [](https://github.com/Shaobin-Jiang/IceNvim/blob/master/LICENSE)
10 |
11 |
12 |
13 | Read this in other languages: [English](README.md) / [中文](README_zh.md)
14 |
15 | IceNvim is a beautiful, powerful and customizable neovim config. Powerful, yet blazing fast.
16 |
17 | For a detailed introduction on the various features of and on how to use IceNvim, refer to the [wiki](https://github.com/Shaobin-Jiang/IceNvim/wiki/Introduction).
18 |
19 | ## Showcase
20 |
21 | 
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | ## Features
30 |
31 | - Ideal for development:
32 | - Set up for C# / Flutter / Lua / Python / Rust / Web development and markdown writing
33 | - Git integration
34 | - Enhanced editing experience:
35 | - Plugins such as `hop.nvim`, `undotree` and `nvim-surround`
36 | - For Chinese users, automatic IME switching when changing modes (needs [additional setup](#mark-im-select))
37 | - Nice looks:
38 | - Multiple colorschemes made ready
39 | - A custom colorschemes picker
40 | - User friendly:
41 | - Uses which-key.nvim for new comers to check out keymaps
42 | - Provides a health check for new comers to locate missing dependencies
43 | - Well equiped:
44 | - An icon viewer to check whether your font works well with icons
45 | - A configuration file selector
46 | - Modern: uses `Lazy` and `Mason`
47 | - Customizable:
48 | - Easily override defaults with your own [config file](#mark-custom-configuration)
49 |
50 | ## Requirements
51 |
52 | - IceNvim requires neovim **0.10.0+** or newer
53 | - Additionally, you need to install these also:
54 | - A [nerd font](https://www.nerdfonts.com/font-downloads): this is optional, but things may look funny without one installed
55 | - git: almost all the plugin and lsp installations depend on it
56 | - Required by Mason:
57 | - curl
58 | - gzip / 7zip
59 | - wget
60 | - Required by telescope:
61 | - fd
62 | - ripgrep (also required by grug-far.nvim)
63 | - Required by nvim treesitter:
64 | - gcc
65 | - cmake
66 | - node
67 | - npm
68 | - Required by markdown-preview.nvim:
69 | - yarn
70 | - Required by rustaceanvim:
71 | - rust-analyzer (NOT the rust-analyzer provided by Mason!!!)
72 | - python3 and pip3
73 | - Additional dependencies on Linux / WSL:
74 | - unzip
75 | - python virtual environment
76 | - xclip (for accessing system clipboard)
77 | - zip
78 |
79 | Note that some of the packages might have different names with different package managers!
80 |
81 | Installing dependencies on MacOS:
82 |
83 | ```bash
84 | brew install wget fd ripgrep node yarn cmake
85 | ```
86 |
87 | Installing dependencies on Arch:
88 |
89 | ```bash
90 | sudo pacman -S --needed curl gzip wget fd ripgrep gcc nodejs npm python python-pip unzip zip xclip python-virtualenv
91 | ```
92 |
93 | Installing dependencies on Windows (via scoop):
94 |
95 | ```bash
96 | scoop install curl gzip wget fd ripgrep mingw nodejs-lts python
97 | ```
98 |
99 | To verify if these are installed, try opening neovim with `nvim --noplugin` and then running `IceHealth` after following the installation instruction in the next section.
100 |
101 | ## Installation
102 |
103 | On Windows:
104 |
105 | ```bash
106 | git clone https://github.com/Shaobin-Jiang/IceNvim "$env:LOCALAPPDATA\nvim"
107 | ```
108 |
109 | On Linux / MacOS:
110 |
111 | ```bash
112 | git clone https://github.com/Shaobin-Jiang/IceNvim ~/.config/nvim
113 | ```
114 |
115 | Download `im-select.exe` (recommended for windows / wsl users)
116 |
117 | For automatic IME switching when inputing Chinese, im-select.exe is needed.
118 |
119 | Download it from [https://github.com/daipeihust/im-select/raw/master/win/out/x86/im-select.exe](https://github.com/daipeihust/im-select/raw/master/win/out/x86/im-select.exe) and place to the `bin` directory in the configuration directory.
120 |
121 | Additionally, if you are using wsl, you might have to do this:
122 |
123 | ```bash
124 | chmod +x ~/.config/nvim/bin/im-select.exe
125 | ```
126 |
127 | ### Install `macism` (recommended for MacOS users)
128 |
129 | Macism can be installed via the command below:
130 |
131 | ```bash
132 | brew tap laishulu/homebrew
133 | brew install macism
134 | ```
135 |
136 | Note that:
137 |
138 | - The first time you use this function, MacOS will popup a window asking you to grant permission of Accessibility
139 | - You need to enable the MacOS keyboard shortcut for "Select the previous input source", which can be found in "Preference -> Keyboard -> Shortcuts -> Input Source"
140 |
141 | ### Download `uclip.exe` (recommended for windows / wsl users)
142 |
143 | Although text yanked from within IceNvim is already available from outside, one might find that unicode characters are not copied properly on Windows and WSL. This is because the functionality is dealt with by Windows' `CLIP` command which does a poor job when used with utf-8 characters.
144 |
145 | To solve this, one might need to download [uclip.exe](https://github.com/suzusime/uclip/releases/download/v0.1.0/uclip.exe) and place it in the `bin` directory in the configuration directory.
146 |
147 | Additionally, if you are using wsl, you might have to do this:
148 |
149 | ```bash
150 | chmod +x ~/.config/nvim/bin/uclip.exe
151 | ```
152 |
153 | Custom Configuration
154 |
155 | This neovim configuration allows users to override the default configuration by creating a `custom` dir under `lua/`.
156 |
157 | IceNvim will try to detect and load `custom/init.lua`. Since `custom/` is git-ignored, it will be easy for you to make your own configurations without messing up the original git repo and missing follow-up updates.
158 |
159 | Most IceNvim config options can be found under a global variable `Ice`. The entire setup follows this routine:
160 |
161 | - IceNvim sets its default options and store some of them, e.g., plugin config and keymaps, in `Ice`
162 | - IceNvim loads `custom/init.lua`
163 | - IceNvim uses `Ice` to set up plugins and create keymaps
164 |
165 | Therefore, almost everything IceNvim defines can be re-configured by you.
166 |
167 | An example `custom/init.lua`:
168 |
169 | ```lua
170 | Ice.plugins["nvim-transparent"].enabled = false
171 |
172 | Ice.keymap.general.open_terminal = { "n", "terminal", ":split term://bash" }
173 |
174 | local autogroup = vim.api.nvim_create_augroup("OverrideFtplugin", { clear = true })
175 | vim.api.nvim_create_autocmd("BufEnter", {
176 | group = autogroup,
177 | callback = function()
178 | if vim.bo.filetype == "lua" then
179 | vim.cmd "setlocal colorcolumn=120"
180 | end
181 | end,
182 | })
183 | ```
184 |
185 | ## Troubleshooting
186 |
187 | ### Alt-Combination Keys Not Working in Kitty for MacOS
188 |
189 | Add `macos_option_as_alt yes` to your `kitty.conf`.
190 |
191 | ### Installing Omnisharp / Csharpier
192 |
193 | When installing omnisharp, make sure that dotnet sdk is installed.
194 |
195 | When receiving nuget-related errors when installing csharpier, you might have to configure nuget source (see [https://learn.microsoft.com/zh-cn/nuget/reference/errors-and-warnings/nu1100#solution-2](https://learn.microsoft.com/zh-cn/nuget/reference/errors-and-warnings/nu1100#solution-2)):
196 |
197 | ```shell
198 | dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
199 | ```
200 |
201 | ### Rust Not Working Properly
202 |
203 | You need to check how you installed rust. I have not been able to set up rust-analyzer when installing rust only (e.g., via `scoop install rust` or `sudo zypper in rust`) either, but with the officially recommended way, i.e., by installing rustup, everything works properly.
204 |
205 | Also, you might find that completion does not work when first opening a rust project. That is because some time needs to be taken to index the code, and completion would only work after indexing is done.
206 |
207 | ### Installation Failure for typst-preview.nvim
208 |
209 | When installing [typst-preview.nvim](https://github.com/chomosuke/typst-preview.nvim), you might have this error: `Downloading typst-preview binary failed, exit code: 35`. This might be due to the use of proxies. Shut down softwares of such kind and run this command again:
210 |
211 | ```vim
212 | lua require("typst-preview").update()
213 | ```
214 |
215 | ## Star History
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | IceNvim
2 |
3 |
4 |
5 | *如果可以的话,可以点一个 star✨✨✨。谢谢您的支持~*
6 |
7 | [](https://github.com/neovim/neovim)
8 | [](https://github.com/neovim/neovim)
9 | [](https://github.com/Shaobin-Jiang/IceNvim/blob/master/LICENSE)
10 |
11 |
12 |
13 | 通过其他语言查看:[English](README.md) / [中文](README_zh.md)
14 |
15 | IceNvim 是一个美观、功能强大、支持高度自定义的 neovim 配置,且流畅迅速。
16 |
17 | 如果想要更详细地了解 IceNvim 的一些特性和使用方法,可以参考 [wiki](https://github.com/Shaobin-Jiang/IceNvim/wiki/Introduction)(目前仅有英文版本,但之后计划重写了)。
18 |
19 | ## 截图
20 |
21 | 
22 |
23 | 
24 |
25 | 
26 |
27 | 
28 |
29 | ## 特性
30 |
31 | - 适合开发工作:
32 | - 支持 C# / Flutter / Lua / Python / Rust / Web 开发以及 markdown 编辑
33 | - 结合了 Git
34 | - 更好的编辑体验
35 | - 使用了 `hop.nvim`、`undotree`、`nvim-surround` 等插件
36 | - 针对中文用户,添加了切换模式时自动切换输入法的功能(需要[额外配置](#mark-im-select))
37 | - 美观:
38 | - 预装了多种主题
39 | - 主题切换工具
40 | - 用户友好:
41 | - 使用 which-key.nvim 显示快捷键
42 | - 预装了 health check,可以帮助新用户检查是否缺少某些依赖
43 | - 功能齐全
44 | - 提供了查看图标的工具,用来检查字体是否和图标兼容
45 | - 一键打开配置文件的工具
46 | - 现代:使用 `Lazy` 和 `Mason`
47 | - 自定义:
48 | - 轻松使用自己的 [配置文件](#mark-custom-configuration) 覆盖默认设置
49 |
50 | ## 依赖
51 |
52 | - IceNvim 需要 neovim **0.9.0+**,不过 **0.10.0+** 更好
53 | - 此外,你需要安装以下内容:
54 | - 一款 [nerd font](https://www.nerdfonts.com/font-downloads):可选,但是如果不安装看起来会比较奇怪
55 | - git:几乎所有的插件和 lsp 安装都需要它
56 | - Mason 的依赖项:
57 | - curl
58 | - gzip / 7zip
59 | - wget
60 | - telescope 的依赖项
61 | - fd
62 | - ripgrep (也是 grug-far.nvim 的依赖项)
63 | - nvim treesitter 的依赖项
64 | - gcc
65 | - cmake
66 | - node
67 | - npm
68 | - markdown-preview.nvim 的依赖项
69 | - yarn
70 | - rustaceanvim 的依赖项
71 | - rust-analyzer (不是 Mason 提供的那个!!!)
72 | - python3 和 pip3
73 | - Linux / WSL 上的额外依赖
74 | - unzip
75 | - python 虚拟环境
76 | - xclip(用来访问系统剪贴板)
77 | - zip
78 |
79 | 注意,不同包管理器里这些包的名称可能有所不同!
80 |
81 | 在 MacOS 上安装依赖:
82 |
83 | ```bash
84 | brew install wget fd ripgrep node yarn cmake
85 | ```
86 |
87 | 在 Arch 上安装依赖:
88 |
89 | ```bash
90 | sudo pacman -S --needed curl gzip wget fd ripgrep gcc nodejs npm python python-pip unzip zip xclip python-virtualenv
91 | ```
92 |
93 | 在 Windows 上安装依赖(使用 scoop):
94 |
95 | ```bash
96 | scoop install curl gzip wget fd ripgrep mingw nodejs-lts python
97 | ```
98 |
99 | 如果需要确认依赖项是否被正确安装,可以在按照下一节内容安装完毕后,通过 `nvim --noplugin` 启动 neovim 并运行 `IceHealth`。
100 |
101 | ## 安装
102 |
103 | Windows:
104 |
105 | ```bash
106 | git clone https://github.com/Shaobin-Jiang/IceNvim "$env:LOCALAPPDATA\nvim"
107 | ```
108 |
109 | Linux / MacOS:
110 |
111 | ```bash
112 | git clone https://github.com/Shaobin-Jiang/IceNvim ~/.config/nvim
113 | ```
114 |
115 | 下载 `im-select.exe` (推荐 windows / wsl 用户安装)
116 |
117 | 如果想要在使用中文输入的时候进行自动化的输入法切换则需要 im-select.exe。
118 |
119 | 从 [https://github.com/daipeihust/im-select/raw/master/win/out/x86/im-select.exe](https://github.com/daipeihust/im-select/raw/master/win/out/x86/im-select.exe) 下载并放到配置文件夹下的 `bin` 文件夹内部。
120 |
121 | 此外,如果你使用的是 wsl,还需要运行这行命令:
122 |
123 | ```bash
124 | chmod +x ~/.config/nvim/bin/im-select.exe
125 | ```
126 |
127 | ### 安装 `macism` (推荐 MacOS 用户安装)
128 |
129 | 你可以通过下面的命令安装 macism:
130 |
131 | ```bash
132 | brew tap laishulu/homebrew
133 | brew install macism
134 | ```
135 |
136 | 请注意:
137 |
138 | - 第一次使用这个功能的时候,MacOS 会弹出窗口请求相应权限
139 | - 你需要启用“选择上一个输入法”快捷键,启用选项在“系统设置 -> 键盘 -> 键盘快捷键 -> 输入法” 中可以找到
140 |
141 | ### 下载 `uclip.exe` (推荐 windows / wsl 用户安装)
142 |
143 | 尽管外部程序可以使用 IceNvim 内部复制的文字,你可能会发现在 Windows 和 WSL 上复制的 unicode 无法被正确粘贴,这是因为这一功能是由 Windows 的 `CLIP` 命令处理的,这个命令处理 utf-8 字符很差。
144 |
145 | 如果要解决这个问题,你需要下载 [uclip.exe](https://github.com/suzusime/uclip/releases/download/v0.1.0/uclip.exe) 并放到配置文件夹下的 `bin` 文件夹内部。
146 |
147 | 此外,如果你使用的是 wsl,还需要运行这行命令:
148 |
149 | ```bash
150 | chmod +x ~/.config/nvim/bin/uclip.exe
151 | ```
152 |
153 | 自定义配置
154 |
155 | 我们允许你通过在 `lua/` 下创建一个 `custom` 文件夹来覆盖默认的配置。
156 |
157 | IceNvim 会尝试检测并加载 `custom/init.lua`。`custom/` 不会被 git 追踪,所以你可以放心地编写自己的配置,不用担心弄乱原本的 git 仓库以及错过后续更新。
158 |
159 | IceNvim 多数的配置内容可以在全局变量 `Ice` 下找到。IceNvim 遵循以下流程进行配置:
160 |
161 | - IceNvim 进行一些默认项的设置,并把其中一些内容存储到 `Ice` 中,比如插件配置和快捷键(此时尚未生效)
162 | - IceNvim 加载 `custom/init.lua`
163 | - IceNvim 使用 `Ice` 里的内容加载插件、创建快捷键
164 |
165 | 因此,IceNvim 定义的所有内容几乎都可以由你进行重新配置。
166 |
167 | 一份示例的 `custom/init.lua`:
168 |
169 | ```lua
170 | Ice.plugins["nvim-transparent"].enabled = false
171 |
172 | Ice.keymap.general.open_terminal = { "n","terminal", ":split term://bash" }
173 |
174 | local autogroup = vim.api.nvim_create_augroup("OverrideFtplugin",{ clear = true })
175 | vim.api.nvim_create_autocmd("BufEnter",{
176 | group = autogroup,
177 | callback = function()
178 | if vim.bo.filetype == "lua" then
179 | vim.cmd "setlocal colorcolumn=120"
180 | end
181 | end,
182 | })
183 | ```
184 |
185 | ## 可能的问题
186 |
187 | ### Alt 相关的快捷键在 MacOS 的 Kitty 上不生效
188 |
189 | 在 `kitty.conf` 中添加 `macos_option_as_alt yes`。
190 |
191 | ### 安装 Omnisharp / Csharpier
192 |
193 | 安装 omnisharp 的时候需要确保你已经安装了 dotnet sdk。
194 |
195 | 安装 csharpier 的时候如果遇到了 nuget 相关的错误,可能需要配置 nuget 源(见 [https://learn.microsoft.com/zh-cn/nuget/reference/errors-and-warnings/nu1100#solution-2](https://learn.microsoft.com/zh-cn/nuget/reference/errors-and-warnings/nu1100#solution-2)):
196 |
197 | ```shell
198 | dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
199 | ```
200 |
201 | ### Rust 支持有问题
202 |
203 | 你可能需要检查一下你是怎么安装 rust 的。我在仅安装了 rust (例如,通过 `scoop install rust` 或者 `sudo zypper in rust`)的时候也没法正常使用 rust-analyzer,但是如果通过官方推荐的方式——即安装 rustup——就可以了。
204 |
205 | 此外,你可能会发现第一次打开 rust 项目的时候补全功能不能正常工作。这是因为索引代码需要时间,索引结束后才能开始补全。
206 |
207 | ### 安装 typst-preview.nvim 失败
208 |
209 | 安装 [typst-preview.nvim](https://github.com/chomosuke/typst-preview.nvim) 的时候,你可能会遇到这样的报错:`Downloading typst-preview binary failed, exit code: 35`。这可能是因为你使用了代理,你可以把代理软件关掉然后运行下面的命令:
210 |
211 | ```vim
212 | lua require("typst-preview").update()
213 | ```
214 |
215 | ## Star 走势
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
--------------------------------------------------------------------------------
/init.lua:
--------------------------------------------------------------------------------
1 | Ice = {}
2 |
3 | require "core.init"
4 | require "plugins.init"
5 |
6 | -- Load user configuration files
7 | local config_root = string.gsub(vim.fn.stdpath "config", "\\", "/")
8 | if not vim.api.nvim_get_runtime_file("lua/custom/", false)[1] then
9 | os.execute('mkdir "' .. config_root .. '/lua/custom"')
10 | end
11 |
12 | local custom_path = config_root .. "/lua/custom/"
13 | if require("core.utils").file_exists(custom_path .. "init.lua") then
14 | require "custom.init"
15 | end
16 |
17 | -- Define keymap
18 | local keymap = Ice.keymap.general
19 | require("core.utils").group_map(keymap)
20 |
21 | for filetype, config in pairs(Ice.ft) do
22 | require("core.utils").ft(filetype, config)
23 | end
24 |
25 | -- Only load plugins and colorscheme when --noplugin arg is not present
26 | if not require("core.utils").noplugin then
27 | -- Load plugins
28 | require("lazy").setup(vim.tbl_values(Ice.plugins), Ice.lazy)
29 |
30 | vim.api.nvim_create_autocmd("User", {
31 | once = true,
32 | pattern = "VeryLazy",
33 | callback = function()
34 | local rtp_plugin_path = vim.opt.packpath:get()[1] .. "/plugin"
35 | local dir = vim.uv.fs_scandir(rtp_plugin_path)
36 | if dir ~= nil then
37 | while true do
38 | local plugin = vim.uv.fs_scandir_next(dir)
39 | if plugin == nil then
40 | break
41 | else
42 | vim.cmd(string.format("source %s/%s", rtp_plugin_path, plugin))
43 | end
44 | end
45 | end
46 |
47 | require("core.utils").group_map(Ice.keymap.plugins)
48 |
49 | -- Define colorscheme
50 | if not Ice.colorscheme then
51 | local colorscheme_cache = vim.fn.stdpath "data" .. "/colorscheme"
52 | if require("core.utils").file_exists(colorscheme_cache) then
53 | local colorscheme_cache_file = io.open(colorscheme_cache, "r")
54 | ---@diagnostic disable: need-check-nil
55 | local colorscheme = colorscheme_cache_file:read "*a"
56 | colorscheme_cache_file:close()
57 | Ice.colorscheme = colorscheme
58 | else
59 | Ice.colorscheme = "tokyonight"
60 | end
61 | end
62 |
63 | require("plugins.utils").colorscheme(Ice.colorscheme)
64 | end,
65 | })
66 | end
67 |
68 | -- Prepend this to runtimepath last as it would be overridden by lazy otherwise
69 | vim.opt.rtp:prepend(custom_path)
70 |
--------------------------------------------------------------------------------
/lua/core/basic.lua:
--------------------------------------------------------------------------------
1 | local g = vim.g
2 | local opt = vim.opt
3 |
4 | -- This MUST NOT be en_US, but en_US.UTF-8
5 | -- I originally set it to en_US without UTF-8 and `yGp` ceased to work
6 | -- It just threw an 'E353: Nothing in register "' error at me
7 | vim.cmd "language en_US.UTF-8"
8 |
9 | vim.cmd "syntax off"
10 | g.encoding = "UTF-8"
11 | opt.fileencoding = "utf-8"
12 |
13 | local win_height = vim.fn.winheight(0)
14 | opt.scrolloff = math.floor((win_height - 1) / 2)
15 | opt.sidescrolloff = math.floor((win_height - 1) / 2)
16 |
17 | opt.number = true
18 | opt.relativenumber = true
19 |
20 | opt.cursorline = true
21 |
22 | opt.signcolumn = "yes"
23 |
24 | opt.colorcolumn = "80"
25 |
26 | opt.tabstop = 4
27 | opt.shiftwidth = 0
28 | opt.expandtab = true
29 | opt.shiftround = true
30 |
31 | -- Case insensitive searching when no upper case character is present
32 | opt.ignorecase = true
33 | opt.smartcase = true
34 |
35 | -- Disable the ugly highlight during searches
36 | opt.hlsearch = false
37 |
38 | opt.cmdheight = 1
39 | opt.cmdwinheight = 1
40 |
41 | -- Auto load the file when modified externally
42 | opt.autoread = true
43 |
44 | -- Use left / right arrow to move to the previous / next line when at the start
45 | -- or end of a line.
46 | -- See doc (:help 'whichwrap')
47 | opt.whichwrap = "<,>,[,]"
48 |
49 | -- Allow hiding modified buffer
50 | opt.hidden = true
51 |
52 | -- Add mouse support for all modes
53 | opt.mouse = "a"
54 | opt.mousemodel = "extend"
55 |
56 | opt.backup = false
57 | opt.writebackup = false
58 | opt.swapfile = false
59 |
60 | -- Time to wait for a sequence of key combination
61 | opt.timeoutlen = 500
62 |
63 | -- Split window from below and right
64 | opt.splitbelow = true
65 | opt.splitright = true
66 |
67 | opt.termguicolors = true
68 |
69 | -- Do not display the character "W" before search count
70 | opt.shortmess = vim.o.shortmess .. "s"
71 |
72 | -- Maximum of 16 lines of prompt
73 | -- This affects both neovim's native completion and that of nvim-cmp
74 | opt.pumheight = 16
75 |
76 | -- Always show tab line
77 | -- Otherwise, when bufferline is loaded, it will "flash" a bit initially
78 | opt.showtabline = 2
79 | opt.tabline = "%!''"
80 |
81 | opt.showmode = false
82 |
83 | opt.nrformats = "bin,hex,alpha"
84 |
85 | opt.foldlevel = 99
86 | opt.foldlevelstart = 99
87 | opt.foldenable = false
88 |
89 | if require("core.utils").is_windows then
90 | opt.shellslash = true
91 | end
92 |
93 | vim.api.nvim_create_autocmd("TermOpen", {
94 | callback = function()
95 | vim.wo.number = false
96 | vim.wo.relativenumber = false
97 | end,
98 | })
99 |
100 | opt.shadafile = "NONE"
101 | vim.api.nvim_create_autocmd({ "CmdlineEnter", "CmdwinEnter" }, {
102 | once = true,
103 | callback = function()
104 | local shada = vim.fn.stdpath "state" .. "/shada/main.shada"
105 | vim.o.shadafile = shada
106 | vim.cmd("rshada! " .. shada)
107 | end,
108 | })
109 |
110 | vim.api.nvim_create_autocmd("CmdwinEnter", {
111 | callback = function()
112 | vim.cmd "startinsert"
113 | vim.wo.number = false
114 | vim.wo.relativenumber = false
115 | end,
116 | })
117 |
118 | vim.api.nvim_create_autocmd("WinNew", {
119 | callback = function()
120 | vim.wo.wrap = false
121 | end
122 | })
123 |
124 | -- WinNew is not triggered for the first window
125 | vim.wo.wrap = false
126 |
--------------------------------------------------------------------------------
/lua/core/commands.lua:
--------------------------------------------------------------------------------
1 | vim.api.nvim_create_user_command("IceAbout", function()
2 | local buf = vim.api.nvim_create_buf(false, true)
3 | vim.keymap.set("n", "q", "c", { buffer = buf })
4 |
5 | local win_width = vim.fn.winwidth(0)
6 | local win_height = vim.fn.winheight(0)
7 | local width = 80
8 | local height = math.floor(win_height * 0.3)
9 | local left = math.floor((win_width - width) / 2)
10 | local top = math.floor((win_height - height) / 2)
11 |
12 | vim.api.nvim_buf_set_lines(buf, 0, -1, false, {
13 | "",
14 | "A beautiful, powerful and highly customizable neovim config.",
15 | "",
16 | "Author: Shaobin Jiang",
17 | "",
18 | "Url: https://github.com/Shaobin-Jiang/IceNvim",
19 | "",
20 | string.format("Copyright © 2022-%s Shaobin Jiang", os.date "%Y"),
21 | })
22 |
23 | local win = vim.api.nvim_open_win(buf, true, {
24 | relative = "win",
25 | width = width,
26 | height = height,
27 | row = top,
28 | col = left,
29 | border = "rounded",
30 | title = "About IceNvim",
31 | title_pos = "center",
32 | footer = "Press q to close window",
33 | footer_pos = "center",
34 | })
35 |
36 | vim.api.nvim_set_option_value("number", false, { win = win })
37 | vim.api.nvim_set_option_value("relativenumber", false, { win = win })
38 | vim.api.nvim_set_option_value("modifiable", false, { buf = buf })
39 | end, { nargs = 0 })
40 |
41 | vim.api.nvim_create_user_command("IceCheckIcons", function()
42 | local buf = vim.api.nvim_create_buf(false, true)
43 | vim.keymap.set("n", "q", "c", { buffer = buf })
44 |
45 | local item_width = 24
46 | local item_name_width = 18
47 | local win_width = vim.fn.winwidth(0)
48 | local win_height = vim.fn.winheight(0)
49 | local columns = math.floor(win_width / item_width) - 1
50 |
51 | local content = {}
52 | local items_in_row = 0
53 | local line = ""
54 | local item_number = 0
55 | for name, icon in require("core.utils").ordered_pair(Ice.symbols) do
56 | item_number = item_number + 1
57 | line = string.format(
58 | "%s%s%s%s%s",
59 | line,
60 | name,
61 | string.rep(" ", item_name_width - #name),
62 | icon,
63 | string.rep(" ", item_width - item_name_width - vim.fn.strdisplaywidth(icon))
64 | )
65 |
66 | items_in_row = items_in_row + 1
67 |
68 | if items_in_row == columns then
69 | content[#content + 1] = line
70 | items_in_row = 0
71 | line = ""
72 | end
73 | end
74 |
75 | vim.api.nvim_buf_set_lines(buf, 0, -1, false, content)
76 |
77 | local width = columns * item_width
78 | local height = math.ceil(item_number / columns)
79 | local left = math.floor((win_width - width) / 2)
80 | local top = math.floor((win_height - height) / 2)
81 |
82 | local win = vim.api.nvim_open_win(buf, true, {
83 | relative = "win",
84 | width = width,
85 | height = height,
86 | row = top,
87 | col = left,
88 | border = "rounded",
89 | title = "Check Nerd Font Icons",
90 | title_pos = "center",
91 | footer = "Press q to close window",
92 | footer_pos = "center",
93 | })
94 |
95 | vim.api.nvim_set_option_value("number", false, { win = win })
96 | vim.api.nvim_set_option_value("relativenumber", false, { win = win })
97 | vim.api.nvim_set_option_value("wrap", false, { win = win })
98 | vim.api.nvim_set_option_value("modifiable", false, { buf = buf })
99 | end, { nargs = 0 })
100 |
101 | vim.api.nvim_create_user_command("IceCheckPlugins", function()
102 | local plugins_path = vim.fn.stdpath "data" .. "/lazy/"
103 | local dir = vim.uv.fs_scandir(plugins_path)
104 |
105 | local stale_plugins = {}
106 |
107 | local plugin_count = 0
108 |
109 | if dir ~= nil then
110 | local co = coroutine.create(function()
111 | local checked_plugin_count = 1
112 | while plugin_count > checked_plugin_count do
113 | coroutine.yield()
114 | checked_plugin_count = checked_plugin_count + 1
115 | end
116 |
117 | table.sort(stale_plugins, function(a, b)
118 | return a[2] > b[2]
119 | end)
120 |
121 | local really_stale_plugin_count = 0
122 | local report = vim.tbl_map(function(entry)
123 | if entry[2] > 365 then
124 | really_stale_plugin_count = really_stale_plugin_count + 1
125 | end
126 | return string.format("%s: %d days", entry[1], entry[2])
127 | end, stale_plugins)
128 |
129 | -- Calling the api related to buffer / window directly in a coroutine seems to cause problems
130 | -- We have to wrap it with a `vim.schedule` call
131 | vim.schedule(function()
132 | local report_buf = vim.api.nvim_create_buf(false, true)
133 | vim.keymap.set("n", "q", "c", { buffer = report_buf })
134 |
135 | local win_width = vim.fn.winwidth(0)
136 | local win_height = vim.fn.winheight(0)
137 | local report_width = math.floor(win_width * 0.5)
138 | local report_height = math.min(math.floor(win_height * 0.7), #stale_plugins)
139 | local row = math.floor((win_height - report_height) / 2)
140 | local col = math.floor((win_width - report_width) / 2)
141 |
142 | -- If setting end to 0 instead of -1, there would be an empty line at the end
143 | vim.api.nvim_buf_set_lines(report_buf, 0, -1, false, report)
144 |
145 | local ns_id = vim.api.nvim_create_namespace "out-of-date-plugins"
146 | for line = 0, really_stale_plugin_count - 1 do
147 | vim.hl.range(report_buf, ns_id, "ErrorMsg", {line, 0}, {line, -1}, {})
148 | end
149 | for line = really_stale_plugin_count, #stale_plugins - 1 do
150 | vim.hl.range(report_buf, ns_id, "WarningMsg", {line, 0}, {line, -1}, {})
151 | end
152 |
153 | local win = vim.api.nvim_open_win(report_buf, true, {
154 | relative = "win",
155 | width = report_width,
156 | height = report_height,
157 | row = row,
158 | col = col,
159 | border = "rounded",
160 | title = string.format("%d plugins possibly out of date", #stale_plugins),
161 | title_pos = "center",
162 | })
163 |
164 | vim.api.nvim_set_option_value("number", false, { win = win })
165 | vim.api.nvim_set_option_value("relativenumber", false, { win = win })
166 | vim.api.nvim_set_option_value("modifiable", false, { buf = report_buf })
167 | end)
168 | end)
169 |
170 | while true do
171 | local item, item_type = vim.uv.fs_scandir_next(dir)
172 |
173 | if not item then
174 | break
175 | end
176 |
177 | if item_type == "directory" and item ~= "readme" then
178 | plugin_count = plugin_count + 1
179 | vim.system(
180 | { "git", "log", "-1", "--format=%cd", "--date=short" },
181 | { cwd = plugins_path .. item },
182 | function(obj)
183 | local date = string.gsub(obj.stdout, "\n", "") -- e.g., "2000-06-15"
184 | local year = string.sub(date, 1, 4)
185 | local month = string.sub(date, 6, 7)
186 | local day = string.sub(date, 9, 10)
187 | local last_update_timestamp = os.time { year = year, month = month, day = day }
188 | local current_timestamp = os.time()
189 | local stale_days = math.floor((current_timestamp - last_update_timestamp) / 86400)
190 | if stale_days > 30 then
191 | stale_plugins[#stale_plugins + 1] = { item, stale_days }
192 | end
193 | coroutine.resume(co)
194 | end
195 | )
196 | end
197 | end
198 | end
199 | end, { nargs = 0 })
200 |
201 | vim.api.nvim_create_user_command("IceUpdate", "lua require('core.utils').update()", { nargs = 0 })
202 |
203 | vim.api.nvim_create_user_command("IceHealth", "checkhealth core", { nargs = 0 })
204 |
205 | -- Allow a command to be repeated based on v:count1
206 | vim.api.nvim_create_user_command("IceRepeat", function(args)
207 | for _ = 1, vim.v.count1 do
208 | vim.cmd(args.args)
209 | end
210 | end, { nargs = "+", complete = "command" })
211 |
212 | -- View the output of a command in an external buffer
213 | vim.api.nvim_create_user_command("IceView", function(args)
214 | local path = vim.fn.stdpath "data" .. "/ice-view.txt"
215 | if args.args == "" then
216 | vim.cmd("edit " .. path)
217 | else
218 | vim.cmd(string.format(
219 | [[
220 | redir! > %s
221 | silent %s
222 | redir END
223 | edit %s
224 | ]],
225 | path,
226 | args.args,
227 | path
228 | ))
229 | end
230 | end, { nargs = "*", complete = "command" })
231 |
--------------------------------------------------------------------------------
/lua/core/ft.lua:
--------------------------------------------------------------------------------
1 | Ice.ft = {
2 | c = function ()
3 | vim.bo.tabstop = 2
4 | end,
5 | cs = function()
6 | vim.wo.colorcolumn = "100"
7 | end,
8 | css = function()
9 | vim.bo.comments = "s1:/*,ex:*/"
10 | end,
11 | dart = function()
12 | vim.bo.tabstop = 2
13 | end,
14 | html = function()
15 | vim.wo.wrap = true
16 | vim.wo.linebreak = true
17 | vim.wo.breakindent = true
18 |
19 | if vim.bo.filetype == "html" then
20 | vim.wo.colorcolumn = "120"
21 | end
22 | end,
23 | javascript = function()
24 | vim.wo.colorcolumn = "120"
25 | vim.bo.commentstring = "// %s"
26 | end,
27 | markdown = function()
28 | vim.wo.wrap = true
29 | vim.wo.linebreak = true
30 | vim.wo.breakindent = true
31 | end,
32 | org = function()
33 | vim.bo.tabstop = 2
34 | end,
35 | python = function()
36 | vim.bo.formatoptions = "tcqjor"
37 | vim.wo.colorcolumn = "88" -- specified by black
38 | end,
39 | typescript = function()
40 | vim.wo.colorcolumn = "120"
41 | vim.bo.commentstring = "// %s"
42 | end,
43 | typst = function()
44 | vim.wo.wrap = true
45 | vim.wo.linebreak = true
46 | vim.wo.breakindent = true
47 | vim.bo.tabstop = 2
48 | vim.bo.commentstring = "// %s"
49 | end,
50 | -- Convenience method for setting FileType callback
51 | -- Extends default callback if already set
52 | ---@param self table
53 | ---@param ft string
54 | ---@param callback function
55 | set = function(self, ft, callback)
56 | local default_callback = self[ft]
57 | if default_callback ~= nil then
58 | self[ft] = function()
59 | default_callback()
60 | callback()
61 | end
62 | else
63 | self[ft] = callback
64 | end
65 | end,
66 | }
67 |
--------------------------------------------------------------------------------
/lua/core/health.lua:
--------------------------------------------------------------------------------
1 | local M = {}
2 |
3 | -- Checks whether the cmd is executable and determines how to behave if it is not
4 | --
5 | ---@param cmd string the command to check
6 | ---@param behavior function | nil what to do if cmd is not executable; throws an error message by default
7 | local function check(cmd, behavior)
8 | if vim.fn.executable(cmd) == 1 then
9 | vim.health.ok(cmd .. " is installed.")
10 | else
11 | if not behavior then
12 | vim.health.error(cmd .. " is missing.")
13 | else
14 | behavior()
15 | end
16 | end
17 | end
18 |
19 | M.check = function()
20 | vim.health.start "IceNvim Prerequisites"
21 | vim.health.info "IceNvim does not check this for you, but at least one [nerd font] should be installed."
22 |
23 | for _, cmd in ipairs { "curl", "wget", "fd", "rg", "gcc", "cmake", "node", "npm", "yarn", "python3", "pip3" } do
24 | check(cmd)
25 | end
26 |
27 | if vim.fn.executable "gzip" == 1 or vim.fn.executable "7z" == 1 then
28 | vim.health.ok "One of gzip / 7zip is installed."
29 | else
30 | vim.health.error "You must install one of gzip or 7zip."
31 | end
32 |
33 | check("rust-analyzer", function()
34 | vim.health.warn "For best experience with rust development, you should install rust-analyzer."
35 | end)
36 |
37 | if require("core.utils").is_linux then
38 | vim.health.start "IceNvim Prerequisites for Linux"
39 | vim.health.info "IceNvim does not check this for you, but you need a [python virtualenv]."
40 |
41 | for _, cmd in ipairs { "unzip", "xclip", "zip" } do
42 | check(cmd)
43 | end
44 | end
45 |
46 | if require("core.utils").is_windows or require("core.utils").is_wsl then
47 | vim.health.start "IceNvim Optional Dependencies for Windows and WSL"
48 |
49 | check(vim.fn.stdpath "config" .. "/bin/im-select.exe", function()
50 | vim.health.warn "You need im-select.exe to enable automatic IME switching for Chinese. Consider downloading it at https://github.com/daipeihust/im-select/raw/master/win/out/x86/im-select.exe"
51 | end)
52 |
53 | check(vim.fn.stdpath "config" .. "/bin/uclip.exe", function()
54 | vim.health.warn "You need uclip.exe for correct unicode copy / paste. Consider downloading it at https://github.com/suzusime/uclip/releases/download/v0.1.0/uclip.exe"
55 | end)
56 | end
57 |
58 | if require("core.utils").is_mac then
59 | vim.health.start "IceNvim Optional Dependencies for MacOS"
60 |
61 | check("im-select", function()
62 | vim.health.warn "You need im-select to enable automatic IME switching for Chinese. Please refer to the wiki for instruction on how to install it."
63 | end)
64 | end
65 | end
66 |
67 | return M
68 |
--------------------------------------------------------------------------------
/lua/core/init.lua:
--------------------------------------------------------------------------------
1 | require "core.basic"
2 | require "core.commands"
3 | require "core.ft"
4 | require "core.keymap"
5 | require "core.miscellaneous"
6 | require "core.symbols"
7 |
--------------------------------------------------------------------------------
/lua/core/keymap.lua:
--------------------------------------------------------------------------------
1 | vim.g.mapleader = " "
2 | vim.g.maplocalleader = ","
3 |
4 | -- Generates a comment function that can be used in a keymap
5 | --
6 | -- Uses the buffer's local commentstring to add a comment; the "%s" in the commentstring is removed
7 | -- Upon the addition of a comment, the user ends up in insert mode, with the cursor at the exact same position as the %s
8 | -- Adding a comment at the end of a line will have a blank before it only if the line is non-blank.
9 | --
10 | ---@param pos string Can be one of "above" / "below" / "end"; indicates where the comment is to be inserted
11 | local function comment(pos)
12 | return function()
13 | local row = vim.api.nvim_win_get_cursor(0)[1]
14 | local total_lines = vim.api.nvim_buf_line_count(0)
15 | local commentstring = vim.bo.commentstring
16 | local cmt = string.gsub(commentstring, "%%s", "")
17 | local index = string.find(commentstring, "%%s")
18 |
19 | local target_line
20 | if pos == "below" then
21 | -- Uses the same indentation as the next line if we are adding a comment below
22 | -- We have to consider whether the current line is the last one in the buffer
23 | if row == total_lines then
24 | target_line = vim.api.nvim_buf_get_lines(0, row - 1, row, true)[1]
25 | else
26 | target_line = vim.api.nvim_buf_get_lines(0, row, row + 1, true)[1]
27 | end
28 | else
29 | target_line = vim.api.nvim_get_current_line()
30 | end
31 |
32 | if pos == "end" then
33 | -- Only insert a blank space before the comment if the current line is non-blank
34 | if string.find(target_line, "%S") then
35 | cmt = " " .. cmt
36 | index = index + 1
37 | end
38 | vim.api.nvim_buf_set_lines(0, row - 1, row, false, { target_line .. cmt })
39 | vim.api.nvim_win_set_cursor(0, { row, #target_line + index - 2 })
40 | else
41 | -- Get the index of the first non blank character
42 | local line_start = string.find(target_line, "%S") or (#target_line + 1)
43 | local blank = string.sub(target_line, 1, line_start - 1)
44 |
45 | if pos == "above" then
46 | vim.api.nvim_buf_set_lines(0, row - 1, row - 1, true, { blank .. cmt })
47 | vim.api.nvim_win_set_cursor(0, { row, #blank + index - 2 })
48 | end
49 |
50 | if pos == "below" then
51 | vim.api.nvim_buf_set_lines(0, row, row, true, { blank .. cmt })
52 | vim.api.nvim_win_set_cursor(0, { row + 1, #blank + index - 2 })
53 | end
54 | end
55 |
56 | vim.api.nvim_feedkeys("a", "n", false)
57 | end
58 | end
59 |
60 | -- If the line is not empty, apply gcc. Otherwise, add a comment to it
61 | -- This is necessary as the default gcc does not work on blank lines
62 | local function comment_line()
63 | local line = vim.api.nvim_get_current_line()
64 |
65 | local row = vim.api.nvim_win_get_cursor(0)[1]
66 | local commentstring = vim.bo.commentstring
67 | local cmt = string.gsub(commentstring, "%%s", "")
68 | local index = string.find(commentstring, "%%s")
69 |
70 | if not string.find(line, "%S") then
71 | vim.api.nvim_buf_set_lines(0, row - 1, row, false, { line .. cmt })
72 | vim.api.nvim_win_set_cursor(0, { row, #line + index - 1 })
73 | else
74 | -- WARN: I have no clue as to why neovim is not including this in its official documentations
75 | -- It is possible that the api will be renamed in future releases
76 | require("vim._comment").toggle_lines(row, row, { row, 0 })
77 | end
78 | end
79 |
80 | -- J joins + 1 lines
81 | local function join_lines()
82 | local v_count = vim.v.count1 + 1
83 | local mode = vim.api.nvim_get_mode().mode
84 | local keys
85 | if mode == "n" then
86 | keys = v_count .. "J"
87 | else
88 | keys = "J"
89 | end
90 | vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(keys, true, false, true), "n", false)
91 | end
92 |
93 | -- Open the current html file with the default browser.
94 | local function open_html_file()
95 | if vim.bo.filetype == "html" then
96 | local utils = require "core.utils"
97 | local command
98 | if utils.is_linux or utils.is_wsl then
99 | command = "xdg-open"
100 | elseif utils.is_windows then
101 | command = "explorer"
102 | else
103 | command = "open"
104 | end
105 | if require("core.utils").is_windows then
106 | local old_shellslash = vim.opt.shellslash
107 | vim.opt.shellslash = false
108 | vim.cmd(string.format('silent exec "!%s %%:p"', command))
109 | vim.opt.shellslash = old_shellslash
110 | else
111 | vim.cmd(string.format('silent exec "!%s %%:p"', command))
112 | end
113 | end
114 | end
115 |
116 | -- Save when the buffer is modified
117 | local function save_file()
118 | local buffer_is_modified = vim.api.nvim_get_option_value("modified", { buf = 0 })
119 | if buffer_is_modified then
120 | vim.cmd "write"
121 | else
122 | print "Buffer not modified. No writing is done."
123 | end
124 | vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false)
125 | end
126 |
127 | -- When evoked under normal / insert / visual mode, call vim's `undo` command and then go to normal mode.
128 | local function undo()
129 | local mode = vim.api.nvim_get_mode().mode
130 |
131 | -- Only undo in normal / insert / visual mode
132 | if mode == "n" or mode == "i" or mode == "v" then
133 | vim.cmd "undo"
134 | -- Back to normal mode
135 | vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("", true, false, true), "n", false)
136 | end
137 | end
138 |
139 | -- Determine in advance what shell to use for the keymap
140 | local terminal_command
141 | if not require("core.utils").is_windows then
142 | terminal_command = "split | terminal" -- let $SHELL decide the default shell
143 | else
144 | local executables = { "pwsh", "powershell", "bash", "cmd" }
145 | for _, executable in require("core.utils").ordered_pair(executables) do
146 | if vim.fn.executable(executable) == 1 then
147 | terminal_command = "split term://" .. executable .. ""
148 | break
149 | end
150 | end
151 | end
152 |
153 | Ice.keymap = {}
154 | Ice.keymap.general = {
155 | -- See `:h quote_`
156 | black_hole_register = { { "n", "v" }, "\\", '"_' },
157 | clear_cmd_line = { { "n", "i", "v", "t" }, "", "mode" },
158 | cmd_forward = { "c", "", "", { silent = false } },
159 | cmd_backward = { "c", "", "", { silent = false } },
160 | cmd_home = { "c", "", "", { silent = false } },
161 | cmd_end = { "c", "", "", { silent = false } },
162 | cmd_word_forward = { "c", "", "", { silent = false } },
163 | cmd_word_backward = { "c", "", "", { silent = false } },
164 |
165 | comment_line = { "n", "gcc", comment_line },
166 | comment_above = { "n", "gcO", comment "above" },
167 | comment_below = { "n", "gco", comment "below" },
168 | comment_end = { "n", "gcA", comment "end" },
169 |
170 | disable_ctrl_left_mouse = { "n", "", "" },
171 | disable_right_mouse = { { "n", "i", "v", "t" }, "", "" },
172 |
173 | join_lines = { { "n", "v" }, "J", join_lines },
174 |
175 | -- Move the cursor through wrapped lines with j and k
176 | -- https://github.com/NvChad/NvChad/blob/b9963e29b21a672325af5b51f1d32a9191abcdaa/lua/core/mappings.lua#L40C5-L41C99
177 | move_down = { "n", "j", 'v:count || mode(1)[0:1] == "no" ? "j" : "gj"', { expr = true } },
178 | move_up = { "n", "k", 'v:count || mode(1)[0:1] == "no" ? "k" : "gk"', { expr = true } },
179 |
180 | new_line_below_normal = { "n", "", "o" },
181 | new_line_above_normal = { "n", "", "O" },
182 |
183 | open_html_file = { "n", "", open_html_file },
184 | open_terminal = { "n", "", terminal_command },
185 | normal_mode_in_terminal = { "t", "", "" },
186 | save_file = { { "n", "i", "v" }, "", save_file },
187 | undo = { { "n", "i", "v", "t", "c" }, "", undo },
188 | visual_line = { "n", "V", "0v$" },
189 | }
190 |
--------------------------------------------------------------------------------
/lua/core/miscellaneous.lua:
--------------------------------------------------------------------------------
1 | local utils = require "core.utils"
2 |
3 | local config_path = string.gsub(vim.fn.stdpath "config", "\\", "/")
4 |
5 | -- Yanking on windows / wsl
6 | local clip_path = config_path .. "/bin/uclip.exe"
7 | if not require("core.utils").file_exists(clip_path) then
8 | local root
9 | if utils.is_windows then
10 | root = "C:"
11 | else
12 | root = "/mnt/c"
13 | end
14 | clip_path = root .. "/Windows/System32/clip.exe"
15 | end
16 |
17 | if utils.is_windows or utils.is_wsl then
18 | vim.api.nvim_create_autocmd("TextYankPost", {
19 | callback = function()
20 | if vim.v.event.operator == "y" then
21 | vim.fn.system(clip_path, vim.fn.getreg "0")
22 | end
23 | end,
24 | })
25 | else
26 | vim.cmd "set clipboard+=unnamedplus"
27 | end
28 |
29 | -- IME switching on windows / wsl
30 | if utils.is_windows or utils.is_wsl then
31 | local im_select_path = config_path .. "/bin/im-select.exe"
32 |
33 | if require("core.utils").file_exists(im_select_path) then
34 | local ime_autogroup = vim.api.nvim_create_augroup("ImeAutoGroup", { clear = true })
35 |
36 | local function autocmd(event, code)
37 | vim.api.nvim_create_autocmd(event, {
38 | group = ime_autogroup,
39 | callback = function()
40 | vim.cmd(":silent :!" .. im_select_path .. " " .. code)
41 | end,
42 | })
43 | end
44 |
45 | autocmd("InsertLeave", 1033)
46 | autocmd("InsertEnter", 2052)
47 | autocmd("VimLeavePre", 2052)
48 | end
49 | elseif utils.is_mac then
50 | if vim.fn.executable "macism" == 1 then
51 | local ime_autogroup = vim.api.nvim_create_augroup("ImeAutoGroup", { clear = true })
52 |
53 | vim.api.nvim_create_autocmd("InsertLeave", {
54 | group = ime_autogroup,
55 | callback = function()
56 | vim.system({ "macism" }, { text = true }, function(out)
57 | Ice.__PREVIOUS_IM_CODE_MAC = string.gsub(out.stdout, "\n", "")
58 | end)
59 | vim.cmd ":silent :!macism com.apple.keylayout.ABC"
60 | end,
61 | })
62 |
63 | vim.api.nvim_create_autocmd("InsertEnter", {
64 | group = ime_autogroup,
65 | callback = function()
66 | if Ice.__PREVIOUS_IM_CODE_MAC then
67 | vim.cmd(":silent :!macism " .. Ice.__PREVIOUS_IM_CODE_MAC)
68 | end
69 | Ice.__PREVIOUS_IM_CODE_MAC = nil
70 | end,
71 | })
72 | end
73 | elseif utils.is_linux then
74 | vim.cmd [[
75 | let fcitx5state=system("fcitx5-remote")
76 | autocmd InsertLeave * :silent let fcitx5state=system("fcitx5-remote")[0] | silent !fcitx5-remote -c
77 | autocmd InsertEnter * :silent if fcitx5state == 2 | call system("fcitx5-remote -o") | endif
78 | ]]
79 | end
80 |
81 | -- Automatic switch to root directory
82 | vim.api.nvim_create_autocmd("BufEnter", {
83 | group = vim.api.nvim_create_augroup("AutoChdir", { clear = true }),
84 | callback = function()
85 | if not (Ice.auto_chdir or Ice.auto_chdir == nil) then
86 | return
87 | end
88 |
89 | local default_exclude_filetype = { "NvimTree", "help" }
90 | local default_exclude_buftype = { "terminal", "nofile" }
91 |
92 | local exclude_filetype = Ice.chdir_exclude_filetype
93 | if exclude_filetype == nil or type(exclude_filetype) ~= "table" then
94 | exclude_filetype = default_exclude_filetype
95 | end
96 |
97 | local exclude_buftype = Ice.chdir_exclude_buftype
98 | if exclude_buftype == nil or type(exclude_buftype) ~= "table" then
99 | exclude_buftype = default_exclude_buftype
100 | end
101 |
102 | if table.find(exclude_filetype, vim.bo.filetype) or table.find(exclude_buftype, vim.bo.buftype) then
103 | return
104 | end
105 |
106 | vim.api.nvim_set_current_dir(require("core.utils").get_root())
107 | end,
108 | })
109 |
110 | -- Clears redundant shada.tmp.X files (for windows only)
111 | if utils.is_windows then
112 | local remove_shada_tmp_group = vim.api.nvim_create_augroup("RemoveShadaTmp", { clear = true })
113 | vim.api.nvim_create_autocmd("VimLeavePre", {
114 | group = remove_shada_tmp_group,
115 | callback = function()
116 | local dir = vim.fn.stdpath "data" .. "/shada/"
117 | local shada_dir = vim.uv.fs_scandir(dir)
118 |
119 | local shada_temp = ""
120 | while shada_temp ~= nil do
121 | if string.find(shada_temp, ".tmp.") then
122 | local full_path = dir .. shada_temp
123 | os.remove(full_path)
124 | end
125 | shada_temp = vim.uv.fs_scandir_next(shada_dir)
126 | end
127 | end,
128 | })
129 | end
130 |
--------------------------------------------------------------------------------
/lua/core/symbols.lua:
--------------------------------------------------------------------------------
1 | Ice.symbols = {
2 | Affirmative = "✓ ",
3 | Array = " ",
4 | Boolean = " ",
5 | Class = " ",
6 | CodeAction = " ",
7 | Color = " ",
8 | Component = " ",
9 | Constant = " ",
10 | Constructor = " ",
11 | Definition = " ",
12 | Diagnostic = " ",
13 | Dos = " ",
14 | Enum = " ",
15 | EnumMember = " ",
16 | Error = " ",
17 | Event = " ",
18 | Field = " ",
19 | File = " ",
20 | FittenCode = "",
21 | Folder = " ",
22 | Fragment = " ",
23 | Function = " ",
24 | Hint = " ",
25 | Info = " ",
26 | Interface = " ",
27 | Key = " ",
28 | Keyword = " ",
29 | LspSagaFinder = " ",
30 | Mac = " ",
31 | Method = " ",
32 | Module = " ",
33 | Namespace = " ",
34 | Negative = "✗ ",
35 | Null = " ",
36 | Number = " ",
37 | Object = " ",
38 | Operator = " ",
39 | Package = " ",
40 | Pending = "➜ ",
41 | Property = " ",
42 | Reference = " ",
43 | RenamePrompt = "➤ ",
44 | Snippet = " ",
45 | String = " ",
46 | Struct = " ",
47 | Text = " ",
48 | TypeParameter = " ",
49 | Unit = " ",
50 | Unix = " ",
51 | Value = " ",
52 | Variable = " ",
53 | Warn = " ",
54 | }
55 |
--------------------------------------------------------------------------------
/lua/core/utils.lua:
--------------------------------------------------------------------------------
1 | -- Do not use vim.version as it loads the vim.version module
2 | -- Using vim.fn.api_info().version is no good either as api_info also consumes much time
3 | local version = vim.fn.matchstr(vim.fn.execute('version'), 'NVIM v\\zs[^\\n]*')
4 |
5 | local argv = vim.api.nvim_get_vvar "argv"
6 | local noplugin = vim.list_contains(argv, "--noplugin") or vim.list_contains(argv, "--noplugins")
7 |
8 | local utils = {
9 | is_linux = vim.uv.os_uname().sysname == "Linux",
10 | is_mac = vim.uv.os_uname().sysname == "Darwin",
11 | is_windows = vim.uv.os_uname().sysname == "Windows_NT",
12 | is_wsl = string.find(vim.uv.os_uname().release, "WSL") ~= nil,
13 | noplugin = noplugin,
14 | version = version,
15 | }
16 |
17 | local ft_group = vim.api.nvim_create_augroup("IceFt", { clear = true })
18 |
19 | -- Checks if a file exists
20 | ---@param file string
21 | ---@return boolean
22 | utils.file_exists = function(file)
23 | local fid = io.open(file, "r")
24 | if fid ~= nil then
25 | io.close(fid)
26 | return true
27 | else
28 | return false
29 | end
30 | end
31 |
32 | -- Add callback to filetype
33 | ---@param filetype string
34 | ---@param config function
35 | utils.ft = function(filetype, config)
36 | vim.api.nvim_create_autocmd("FileType", {
37 | pattern = filetype,
38 | group = ft_group,
39 | callback = config,
40 | })
41 | end
42 |
43 | -- Get the parent directory of target. If target is nil, the parent directory of the current file will be looked for,
44 | -- suffixed with a "/" (which is because this function is intended to be used together with fs_scandir, where errors
45 | -- would occur sometimes should a path without an ending "/" be passed to it, such as "C:" instead of "C:/").
46 | --
47 | -- If the target has no parent directory, such as "/" on Linux or "C:" on Windows, nil will be returned.
48 | ---@param target string?
49 | ---@return string?
50 | utils.get_parent = function(target)
51 | if target == nil then
52 | local parent = vim.fn.expand("%:p:h", true)
53 |
54 | if utils.is_windows then
55 | parent = string.gsub(parent, "\\", "/")
56 | end
57 |
58 | return parent
59 | end
60 |
61 | if utils.is_windows then
62 | target = string.gsub(target, "\\", "/")
63 | end
64 |
65 | -- removes trailing slash
66 | if string.sub(target, #target, #target) == "/" then
67 | target = string.sub(target, 1, #target - 1)
68 | end
69 |
70 | if string.find(target, "/") == nil then
71 | return nil
72 | end
73 |
74 | return string.sub(target, 1, string.findlast(target, "/"))
75 | end
76 |
77 | utils.get_root = function()
78 | local uv = vim.uv
79 |
80 | local default_pattern = {
81 | ".git",
82 | "package.json",
83 | ".prettierrc",
84 | "tsconfig.json",
85 | "pubspec.yaml",
86 | ".gitignore",
87 | "stylua.toml",
88 | "README.md",
89 | }
90 |
91 | local pattern = Ice.chdir_root_pattern
92 | if pattern == nil or type(pattern) ~= "table" then
93 | pattern = default_pattern
94 | end
95 |
96 | local parent = utils.get_parent()
97 | local root = parent
98 | local has_found_root = false
99 |
100 | while not (has_found_root or parent == nil) do
101 | local dir = uv.fs_scandir(parent)
102 |
103 | if dir == nil then
104 | break
105 | end
106 |
107 | local file = ""
108 |
109 | while file ~= nil do
110 | file = uv.fs_scandir_next(dir)
111 | if table.find(pattern, file) then
112 | root = parent
113 | has_found_root = true
114 | break
115 | end
116 | end
117 |
118 | parent = utils.get_parent(parent)
119 | end
120 |
121 | return root
122 | end
123 |
124 | -- Maps a group of keymaps with the same opt; if no opt is provided, the default opt is used.
125 | -- The keymaps should be in the format like below:
126 | -- desc = { mode, lhs, rhs, [opt] }
127 | -- For example:
128 | -- black_hole_register = { { "n", "v" }, "\\", '"_' },
129 | -- The desc part will automatically merged into the keymap's opt, unless one is already provided there, with the slight
130 | -- modification of replacing "_" with a blank space.
131 | ---@param group table list of keymaps
132 | ---@param opt table | nil default opt
133 | utils.group_map = function(group, opt)
134 | if not opt then
135 | opt = {}
136 | end
137 |
138 | for desc, keymap in pairs(group) do
139 | desc = string.gsub(desc, "_", " ")
140 | local default_option = vim.tbl_extend("force", { desc = desc, nowait = true, silent = true }, opt)
141 | local map = vim.tbl_deep_extend("force", { nil, nil, nil, default_option }, keymap)
142 | vim.keymap.set(map[1], map[2], map[3], map[4])
143 | end
144 | end
145 |
146 | -- Allow ordered iteration through a table
147 | ---@param t table
148 | ---@return function
149 | utils.ordered_pair = function(t)
150 | local a = {}
151 |
152 | for n in pairs(t) do
153 | a[#a + 1] = n
154 | end
155 |
156 | table.sort(a)
157 |
158 | local i = 0
159 |
160 | return function()
161 | i = i + 1
162 | return a[i], t[a[i]]
163 | end
164 | end
165 |
166 | -- Updates IceNvim
167 | utils.update = function()
168 | vim.system({ "git", "pull" }, { cwd = vim.fn.stdpath "config", text = true }, function(out)
169 | if out.code == 0 then
170 | vim.notify "IceNvim up to date"
171 | else
172 | vim.notify("IceNvim update failed: " .. out.stderr, vim.log.levels.WARN)
173 | end
174 | end)
175 | end
176 |
177 | -- Looks for the last match of `pattern`
178 | -- WARN: this function does poorly with unicode characters!
179 | ---@param s string | number
180 | ---@param pattern string | number
181 | ---@param last integer?
182 | ---@param plain boolean?
183 | ---@return integer | nil, integer | nil, ... | any
184 | string.findlast = function(s, pattern, last, plain)
185 | local reverse = string.reverse(s)
186 |
187 | if last == nil then
188 | last = #s
189 | end
190 |
191 | local start, finish = string.find(reverse, string.reverse(pattern), #s + 1 - last, plain)
192 | if start == nil then
193 | return nil
194 | else
195 | return #s + 1 - finish, #s + 1 - start
196 | end
197 | end
198 |
199 | -- Finds the first occurence of the target in table and returns the key / index.
200 | -- If the target is not in the table, nil is returned.
201 | ---@param t table
202 | ---@param target ... | any
203 | ---@return ... | any
204 | table.find = function(t, target)
205 | for key, value in pairs(t) do
206 | if value == target then
207 | return key
208 | end
209 | end
210 |
211 | return nil
212 | end
213 |
214 | return utils
215 |
--------------------------------------------------------------------------------
/lua/lsp/completion.lua:
--------------------------------------------------------------------------------
1 | Ice.plugins["blink-cmp"] = {
2 | "saghen/blink.cmp",
3 | dependencies = { "rafamadriz/friendly-snippets" },
4 | event = { "InsertEnter", "CmdlineEnter", "User IceLoad" },
5 | version = "*",
6 | opts = {
7 | appearance = {
8 | kind_icons = Ice.symbols,
9 | },
10 | cmdline = {
11 | completion = {
12 | menu = {
13 | auto_show = true,
14 | }
15 | },
16 | keymap = {
17 | preset = "none",
18 | [""] = {"accept"},
19 | [""] = { "select_prev", "fallback" },
20 | [""] = { "select_next", "fallback" },
21 | }
22 | },
23 | completion = {
24 | accept = {
25 | auto_brackets = { enabled = true },
26 | },
27 | documentation = {
28 | auto_show = true,
29 | auto_show_delay_ms = 200,
30 | },
31 | ghost_text = {
32 | enabled = true,
33 | show_without_selection = true,
34 | },
35 | list = {
36 | selection = {
37 | preselect = false,
38 | auto_insert = true,
39 | },
40 | },
41 | menu = {
42 | draw = {
43 | columns = {
44 | { "label", "label_description", gap = 1 },
45 | { "kind_icon" },
46 | },
47 | treesitter = { "lsp" },
48 | },
49 | },
50 | },
51 | enabled = function()
52 | local filetype_is_allowed = not vim.tbl_contains({ "grug-far", "TelescopePrompt" }, vim.bo.filetype)
53 |
54 | local ok, stats = pcall(vim.uv.fs_stat, vim.api.nvim_buf_get_name(0))
55 | local filesize_is_allowed = true
56 | if ok and stats then
57 | ---@diagnostic disable-next-line: need-check-nil
58 | filesize_is_allowed = stats.size < 100 * 1024
59 | end
60 | return filetype_is_allowed and filesize_is_allowed
61 | end,
62 | keymap = {
63 | preset = "none",
64 | [""] = {
65 | function(cmp)
66 | if not cmp.is_menu_visible() then
67 | return
68 | end
69 |
70 | local completion_list = require "blink.cmp.completion.list"
71 | local selected_id = completion_list.selected_item_idx or 1
72 | local item = completion_list.items[selected_id]
73 | local source = item.source_name
74 | return cmp.select_and_accept {
75 | callback = function()
76 | if source == "fittencode" then
77 | -- Do not just feed a or keys of such sort
78 | -- Should the previous line be a comment, the new line might be a comment as well
79 | local line_number = 1
80 | local insert_text = item.insertText
81 | for _ in string.gmatch(insert_text, "\n") do
82 | line_number = line_number + 1
83 | end
84 | local row = vim.api.nvim_win_get_cursor(0)[1] + line_number - 1
85 | local line = vim.api.nvim_get_current_line()
86 | local line_start = string.find(line, "%S") or 1
87 | local blank = string.sub(line, 1, line_start - 1)
88 | vim.api.nvim_buf_set_lines(0, row, row, true, { blank })
89 | vim.api.nvim_win_set_cursor(0, { row + 1, #blank + 1 })
90 |
91 | -- It seems that I have to defer the next call to fittencode
92 | -- Otherwise the completion menu would not show up
93 | vim.defer_fn(function()
94 | require("blink.cmp").show { providers = { "fittencode" } }
95 | end, 20)
96 | end
97 | end,
98 | }
99 | end,
100 | "snippet_forward",
101 | "fallback",
102 | },
103 | [""] = { "snippet_backward", "fallback" },
104 | [""] = { "select_prev", "fallback" },
105 | [""] = { "select_next", "fallback" },
106 | [""] = {
107 | -- DO NOT add "fallback" here!!!
108 | -- It would cause the letter "c" to be inserted as well
109 | function(cmp)
110 | if cmp.is_menu_visible() then
111 | cmp.cancel()
112 | else
113 | cmp.show()
114 | end
115 | end,
116 | },
117 | [""] = { "scroll_documentation_down", "fallback" },
118 | [""] = { "scroll_documentation_up", "fallback" },
119 | },
120 | sources = {
121 | default = function()
122 | local cmdwin_type = vim.fn.getcmdwintype()
123 | if cmdwin_type == "/" or cmdwin_type == "?" then
124 | return { "buffer" }
125 | end
126 | if cmdwin_type == ":" or cmdwin_type == "@" then
127 | return { "cmdline" }
128 | end
129 |
130 | local source = { "lsp", "path", "snippets", "buffer" }
131 | if Ice.__FITTENCODE_SOURCE_ADDED then
132 | source[#source + 1] = "fittencode"
133 | end
134 | if vim.bo.filetype == "org" and Ice.__ORGMODE_SOURCE_ADDED then
135 | source[#source + 1] = "orgmode"
136 | end
137 | return source
138 | end,
139 | providers = {
140 | snippets = {
141 | opts = {
142 | search_paths = { vim.fn.stdpath "config" .. "/lua/custom/snippets" },
143 | },
144 | },
145 | },
146 | },
147 | },
148 | }
149 |
--------------------------------------------------------------------------------
/lua/lsp/extra.lua:
--------------------------------------------------------------------------------
1 | Ice.plugins.lspsaga = {
2 | "nvimdev/lspsaga.nvim",
3 | cmd = "Lspsaga",
4 | opts = {
5 | finder = {
6 | keys = {
7 | toggle_or_open = "",
8 | },
9 | },
10 | symbol_in_winbar = {
11 | enable = false,
12 | },
13 | },
14 | keys = {
15 | { "lr", "Lspsaga rename", desc = "rename", silent = true },
16 | { "lc", "Lspsaga code_action", desc = "code action", silent = true },
17 | { "ld", "Lspsaga goto_definition", desc = "go to definition", silent = true },
18 | {
19 | "lh",
20 | function()
21 | local win = require "lspsaga.window"
22 | local old_new_float = win.new_float
23 | win.new_float = function(self, float_opt, enter, force)
24 | local window = old_new_float(self, float_opt, enter, force)
25 | local _, winid = window:wininfo()
26 | vim.api.nvim_set_current_win(winid)
27 |
28 | win.new_float = old_new_float
29 | return window
30 | end
31 |
32 | vim.cmd "Lspsaga hover_doc"
33 | end,
34 | desc = "hover doc",
35 | silent = true,
36 | },
37 | { "lR", "Lspsaga finder", desc = "references", silent = true },
38 | { "li", "Lspsaga finder", desc = "go_to_implementation", silent = true },
39 | { "lP", "Lspsaga show_line_diagnostics", desc = "show_line_diagnostic", silent = true },
40 | { "ln", "Lspsaga diagnostic_jump_next", desc = "next_diagnostic", silent = true },
41 | { "lp", "Lspsaga diagnostic_jump_prev", desc = "prev_diagnostic", silent = true },
42 | },
43 | }
44 |
45 | Ice.plugins.trouble = {
46 | "folke/trouble.nvim",
47 | opts = {},
48 | cmd = "Trouble",
49 | keys = {
50 | { "lt", "Trouble diagnostics toggle focus=true", desc = "trouble toggle", silent = true },
51 | },
52 | }
53 |
--------------------------------------------------------------------------------
/lua/lsp/format.lua:
--------------------------------------------------------------------------------
1 | -- While null-ls can do a lot more than just formatting, I am leaving the rest to LspSaga
2 | -- See extra.lua
3 | Ice.plugins["null-ls"] = {
4 | "nvimtools/none-ls.nvim",
5 | dependencies = { "nvim-lua/plenary.nvim" },
6 | event = "User IceLoad",
7 | opts = {
8 | debug = false,
9 | },
10 | config = function(_, opts)
11 | local null_ls = require "null-ls"
12 | local formatting = null_ls.builtins.formatting
13 |
14 | local sources = {}
15 | for _, config in pairs(Ice.lsp) do
16 | if config.formatter then
17 | local source = formatting[config.formatter]
18 | sources[#sources + 1] = source
19 | end
20 | end
21 |
22 | null_ls.setup(vim.tbl_deep_extend("keep", opts, { sources = sources }))
23 | end,
24 | keys = {
25 | {
26 | "lf",
27 | function()
28 | local active_client = vim.lsp.get_clients { bufnr = 0, name = "null-ls" }
29 |
30 | local format_option = { async = true }
31 | if #active_client > 0 then
32 | format_option.name = "null-ls"
33 | end
34 | vim.lsp.buf.format(format_option)
35 | end,
36 | mode = { "n", "v" },
37 | desc = "format code",
38 | },
39 | },
40 | }
41 |
--------------------------------------------------------------------------------
/lua/lsp/init.lua:
--------------------------------------------------------------------------------
1 | require "lsp.lsp"
2 | require "lsp.plugins"
3 | require "lsp.completion"
4 | require "lsp.format"
5 | require "lsp.extra"
6 |
--------------------------------------------------------------------------------
/lua/lsp/lsp.lua:
--------------------------------------------------------------------------------
1 | local lsp = {}
2 |
3 | -- For instructions on configuration, see official wiki:
4 | -- https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md
5 | lsp = {
6 | ["bash-language-server"] = {
7 | formatter = "shfmt",
8 | },
9 | clangd = {},
10 | ["css-lsp"] = {
11 | formatter = "prettier",
12 | setup = {
13 | settings = {
14 | css = {
15 | validate = true,
16 | lint = {
17 | unknownAtRules = "ignore",
18 | },
19 | },
20 | less = {
21 | validate = true,
22 | lint = {
23 | unknownAtRules = "ignore",
24 | },
25 | },
26 | scss = {
27 | validate = true,
28 | lint = {
29 | unknownAtRules = "ignore",
30 | },
31 | },
32 | },
33 | },
34 | },
35 | ["emmet-ls"] = {
36 | setup = {
37 | filetypes = { "html", "typescriptreact", "javascriptreact", "css", "sass", "scss", "less" },
38 | },
39 | },
40 | flutter = {
41 | managed_by_plugin = true,
42 | },
43 | gopls = {
44 | formatter = "gofumpt",
45 | setup = {
46 | settings = {
47 | gopls = {
48 | analyses = {
49 | unusedparams = true,
50 | },
51 | },
52 | },
53 | },
54 | },
55 | ["html-lsp"] = {
56 | formatter = "prettier",
57 | },
58 | ["json-lsp"] = {
59 | formatter = "prettier",
60 | },
61 | ["lua-language-server"] = {
62 | formatter = "stylua",
63 | setup = {
64 | settings = {
65 | Lua = {
66 | runtime = {
67 | version = "LuaJIT",
68 | path = (function()
69 | local runtime_path = vim.split(package.path, ";")
70 | table.insert(runtime_path, "lua/?.lua")
71 | table.insert(runtime_path, "lua/?/init.lua")
72 | return runtime_path
73 | end)(),
74 | },
75 | diagnostics = {
76 | globals = { "vim" },
77 | },
78 | hint = {
79 | enable = true,
80 | },
81 | workspace = {
82 | library = {
83 | vim.env.VIMRUNTIME,
84 | "${3rd}/luv/library",
85 | },
86 | checkThirdParty = false,
87 | },
88 | telemetry = {
89 | enable = false,
90 | },
91 | },
92 | },
93 | },
94 | enabled = true,
95 | },
96 | omnisharp = {
97 | formatter = "csharpier",
98 | setup = {
99 | cmd = {
100 | "dotnet",
101 | vim.fn.stdpath "data" .. "/mason/packages/omnisharp/libexec/Omnisharp.dll",
102 | },
103 | on_attach = function(client, _)
104 | client.server_capabilities.semanticTokensProvider = nil
105 | end,
106 | },
107 | },
108 | pyright = {
109 | formatter = "black",
110 | },
111 | rust = {
112 | managed_by_plugin = true,
113 | },
114 | tinymist = {
115 | formatter = "typstfmt",
116 | setup = {
117 | single_file_support = true,
118 | },
119 | },
120 | ["typescript-language-server"] = {
121 | formatter = "prettier",
122 | setup = {
123 | single_file_support = true,
124 | flags = lsp.flags,
125 | on_attach = function(client)
126 | if #vim.lsp.get_clients { name = "denols" } > 0 then
127 | client.stop()
128 | end
129 | end,
130 | },
131 | },
132 | }
133 |
134 | Ice.lsp = lsp
135 |
--------------------------------------------------------------------------------
/lua/lsp/plugins.lua:
--------------------------------------------------------------------------------
1 | local symbols = Ice.symbols
2 |
3 | Ice.plugins["flutter-tools"] = {
4 | "akinsho/flutter-tools.nvim",
5 | ft = "dart",
6 | dependencies = {
7 | "nvim-lua/plenary.nvim",
8 | "stevearc/dressing.nvim",
9 | },
10 | main = "flutter-tools",
11 | opts = {
12 | ui = {
13 | border = "rounded",
14 | },
15 | decorations = {
16 | statusline = {
17 | app_version = true,
18 | device = true,
19 | },
20 | },
21 | },
22 | enabled = function()
23 | return Ice.lsp.flutter.enabled == true
24 | end,
25 | }
26 |
27 | Ice.plugins.rustaceanvim = {
28 | "mrcjkb/rustaceanvim",
29 | ft = "rust",
30 | enabled = function()
31 | return Ice.lsp.rust.enabled == true
32 | end,
33 | }
34 |
35 | Ice.plugins["typst-preview"] = {
36 | "chomosuke/typst-preview.nvim",
37 | ft = "typst",
38 | build = function()
39 | require("typst-preview").update()
40 | end,
41 | opts = {},
42 | keys = {
43 | { "", "TypstPreviewToggle", desc = "typst preview toggle", ft = "typst", silent = true },
44 | },
45 | enabled = function()
46 | return Ice.lsp.tinymist.enabled == true
47 | end,
48 | }
49 |
50 | Ice.plugins.mason = {
51 | "mason-org/mason.nvim",
52 | dependencies = {
53 | "neovim/nvim-lspconfig",
54 | "mason-org/mason-lspconfig.nvim",
55 | },
56 | event = "User IceLoad",
57 | cmd = "Mason",
58 | opts = {
59 | ui = {
60 | icons = {
61 | package_installed = symbols.Affirmative,
62 | package_pending = symbols.Pending,
63 | package_uninstalled = symbols.Negative,
64 | },
65 | },
66 | },
67 | config = function(_, opts)
68 | require("mason").setup(opts)
69 |
70 | local registry = require "mason-registry"
71 | local function install(package)
72 | local s, p = pcall(registry.get_package, package)
73 | if s and not p:is_installed() then
74 | p:install()
75 | end
76 | end
77 |
78 | local lspconfig = require "lspconfig"
79 | local mason_lspconfig_mapping = require("mason-lspconfig").get_mappings().package_to_lspconfig
80 |
81 | local installed_packages = registry.get_installed_package_names()
82 |
83 | for lsp, config in pairs(Ice.lsp) do
84 | if not config.enabled then
85 | goto continue
86 | end
87 |
88 | local formatter = config.formatter
89 | install(lsp)
90 | install(formatter)
91 |
92 | if not vim.tbl_contains(installed_packages, lsp) then
93 | goto continue
94 | end
95 |
96 | lsp = mason_lspconfig_mapping[lsp]
97 | if not config.managed_by_plugin and lspconfig[lsp] ~= nil then
98 | local setup = config.setup
99 | if type(setup) == "function" then
100 | setup = setup()
101 | elseif setup == nil then
102 | setup = {}
103 | end
104 |
105 | local blink_capabilities = require("blink.cmp").get_lsp_capabilities()
106 | blink_capabilities.textDocument.foldingRange = {
107 | dynamicRegistration = false,
108 | lineFoldingOnly = true,
109 | }
110 | setup = vim.tbl_deep_extend("force", setup, {
111 | capabilities = blink_capabilities,
112 | })
113 |
114 | lspconfig[lsp].setup(setup)
115 | end
116 | ::continue::
117 | end
118 |
119 | vim.diagnostic.config {
120 | update_in_insert = true,
121 | severity_sort = true, -- necessary for lspsaga's show_line_diagnostics to work
122 | virtual_text = true,
123 | }
124 | local signs = {
125 | Error = symbols.Error,
126 | Warn = symbols.Warn,
127 | Hint = symbols.Hint,
128 | Info = symbols.Info,
129 | }
130 | for type, icon in pairs(signs) do
131 | local hl = "DiagnosticSign" .. type
132 | vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = hl })
133 | end
134 |
135 | vim.lsp.inlay_hint.enable()
136 |
137 | vim.cmd "LspStart"
138 | end,
139 | }
140 |
--------------------------------------------------------------------------------
/lua/plugins/colorscheme.lua:
--------------------------------------------------------------------------------
1 | -- Predefined colorschemes
2 | Ice.colorschemes = {
3 | ["github-dark"] = {
4 | name = "github_dark",
5 | background = "dark",
6 | },
7 | ["github-light"] = {
8 | name = "github_light",
9 | background = "light",
10 | },
11 | ["github-dark-dimmed"] = {
12 | name = "github_dark_dimmed",
13 | background = "dark",
14 | },
15 | ["github-dark-high-contrast"] = {
16 | name = "github_dark_high_contrast",
17 | background = "dark",
18 | },
19 | ["github-light-high-contrast"] = {
20 | name = "github_light_high_contrast",
21 | background = "light",
22 | },
23 | ["gruvbox-dark"] = {
24 | name = "gruvbox",
25 | setup = {
26 | italic = {
27 | strings = true,
28 | operators = false,
29 | comments = true,
30 | },
31 | contrast = "hard",
32 | },
33 | background = "dark",
34 | },
35 | ["gruvbox-light"] = {
36 | name = "gruvbox",
37 | setup = {
38 | italic = {
39 | strings = true,
40 | operators = false,
41 | comments = true,
42 | },
43 | contrast = "hard",
44 | },
45 | background = "light",
46 | },
47 | ["kanagawa-wave"] = {
48 | name = "kanagawa-wave",
49 | background = "dark",
50 | },
51 | ["kanagawa-dragon"] = {
52 | name = "kanagawa-dragon",
53 | background = "dark",
54 | },
55 | ["kanagawa-lotus"] = {
56 | name = "kanagawa-lotus",
57 | background = "light",
58 | },
59 | miasma = {
60 | name = "miasma",
61 | background = "dark",
62 | },
63 | nightfox = {
64 | name = "nightfox",
65 | background = "dark",
66 | },
67 | ["nightfox-carbon"] = {
68 | name = "carbonfox",
69 | background = "dark",
70 | },
71 | ["nightfox-day"] = {
72 | name = "dayfox",
73 | background = "light",
74 | },
75 | ["nightfox-dawn"] = {
76 | name = "dawnfox",
77 | background = "light",
78 | },
79 | ["nightfox-dusk"] = {
80 | name = "duskfox",
81 | background = "dark",
82 | },
83 | ["nightfox-nord"] = {
84 | name = "nordfox",
85 | background = "dark",
86 | },
87 | ["nightfox-tera"] = {
88 | name = "terafox",
89 | background = "dark",
90 | },
91 | tokyonight = {
92 | name = "tokyonight",
93 | setup = {
94 | style = "moon",
95 | styles = {
96 | comments = { italic = true },
97 | keywords = { italic = false },
98 | },
99 | },
100 | background = "dark",
101 | },
102 | }
103 |
--------------------------------------------------------------------------------
/lua/plugins/config.lua:
--------------------------------------------------------------------------------
1 | -- Configuration for each individual plugin
2 | ---@diagnostic disable: need-check-nil
3 | local config = {}
4 | local symbols = Ice.symbols
5 | local config_root = string.gsub(vim.fn.stdpath "config" --[[@as string]], "\\", "/")
6 |
7 | -- Add IceLoad event
8 | vim.api.nvim_create_autocmd("User", {
9 | pattern = "IceAfter colorscheme",
10 | callback = function()
11 | local function should_trigger()
12 | return vim.bo.filetype ~= "dashboard" and vim.api.nvim_buf_get_name(0) ~= ""
13 | end
14 |
15 | local function trigger()
16 | vim.api.nvim_exec_autocmds("User", { pattern = "IceLoad" })
17 | end
18 |
19 | if should_trigger() then
20 | trigger()
21 | return
22 | end
23 |
24 | local ice_load
25 | ice_load = vim.api.nvim_create_autocmd("BufEnter", {
26 | callback = function()
27 | if should_trigger() then
28 | trigger()
29 | vim.api.nvim_del_autocmd(ice_load)
30 | end
31 | end,
32 | })
33 | end,
34 | })
35 |
36 | config.bufferline = {
37 | "akinsho/bufferline.nvim",
38 | dependencies = { "nvim-tree/nvim-web-devicons" },
39 | event = "User IceLoad",
40 | opts = {
41 | options = {
42 | close_command = ":BufferLineClose %d",
43 | right_mouse_command = ":BufferLineClose %d",
44 | separator_style = "thin",
45 | offsets = {
46 | {
47 | filetype = "NvimTree",
48 | text = "File Explorer",
49 | highlight = "Directory",
50 | text_align = "left",
51 | },
52 | },
53 | diagnostics = "nvim_lsp",
54 | diagnostics_indicator = function(_, _, diagnostics_dict, _)
55 | local s = " "
56 | for e, n in pairs(diagnostics_dict) do
57 | local sym = e == "error" and symbols.Error or (e == "warning" and symbols.Warn or symbols.Info)
58 | s = s .. n .. sym
59 | end
60 | return s
61 | end,
62 | },
63 | },
64 | config = function(_, opts)
65 | vim.api.nvim_create_user_command("BufferLineClose", function(buffer_line_opts)
66 | local bufnr = 1 * buffer_line_opts.args
67 | local buf_is_modified = vim.api.nvim_get_option_value("modified", { buf = bufnr })
68 |
69 | local bdelete_arg
70 | if bufnr == 0 then
71 | bdelete_arg = ""
72 | else
73 | bdelete_arg = " " .. bufnr
74 | end
75 | local command = "bdelete!" .. bdelete_arg
76 | if buf_is_modified then
77 | local option = vim.fn.confirm("File is not saved. Close anyway?", "&Yes\n&No", 2)
78 | if option == 1 then
79 | vim.cmd(command)
80 | end
81 | else
82 | vim.cmd(command)
83 | end
84 | end, { nargs = 1 })
85 |
86 | require("bufferline").setup(opts)
87 |
88 | require("nvim-web-devicons").setup {
89 | override = {
90 | typ = { icon = "", color = "#239dad", name = "typst" },
91 | },
92 | }
93 | end,
94 | keys = {
95 | { "bc", "BufferLinePickClose", desc = "pick close", silent = true },
96 | { "bd", "BufferLineClose 0", desc = "close current buffer", silent = true },
97 | { "bh", "BufferLineCyclePrev", desc = "prev buffer", silent = true },
98 | { "bl", "BufferLineCycleNext", desc = "next buffer", silent = true },
99 | { "bo", "BufferLineCloseOthers", desc = "close others", silent = true },
100 | { "bp", "BufferLinePick", desc = "pick buffer", silent = true },
101 | { "bm", "IceRepeat BufferLineMoveNext", desc = "move right", silent = true },
102 | { "bM", "IceRepeat BufferLineMovePrev", desc = "move left", silent = true },
103 | },
104 | }
105 |
106 | config.colorizer = {
107 | "NvChad/nvim-colorizer.lua",
108 | main = "colorizer",
109 | event = "User IceLoad",
110 | opts = {
111 | filetypes = {
112 | "*",
113 | css = {
114 | names = true,
115 | },
116 | },
117 | user_default_options = {
118 | css = true,
119 | css_fn = true,
120 | names = false,
121 | always_update = true,
122 | },
123 | },
124 | config = function(_, opts)
125 | require("colorizer").setup(opts)
126 | vim.cmd "ColorizerToggle"
127 | end,
128 | }
129 |
130 | config.dashboard = {
131 | "nvimdev/dashboard-nvim",
132 | event = "User IceAfter colorscheme",
133 | opts = {
134 | theme = "doom",
135 | config = {
136 | -- https://patorjk.com/software/taag/#p=display&f=ANSI%20Shadow&t=icenvim
137 | header = {
138 | " ",
139 | "██╗ ██████╗███████╗███╗ ██╗██╗ ██╗██╗███╗ ███╗",
140 | "██║██╔════╝██╔════╝████╗ ██║██║ ██║██║████╗ ████║",
141 | "██║██║ █████╗ ██╔██╗ ██║██║ ██║██║██╔████╔██║",
142 | "██║██║ ██╔══╝ ██║╚██╗██║╚██╗ ██╔╝██║██║╚██╔╝██║",
143 | "██║╚██████╗███████╗██║ ╚████║ ╚████╔╝ ██║██║ ╚═╝ ██║",
144 | "╚═╝ ╚═════╝╚══════╝╚═╝ ╚═══╝ ╚═══╝ ╚═╝╚═╝ ╚═",
145 | " ",
146 | string.format(" %s ", require("core.utils").version),
147 | " ",
148 | },
149 | center = {
150 | {
151 | icon = " ",
152 | desc = "Lazy Profile",
153 | action = "Lazy profile",
154 | },
155 | {
156 | icon = " ",
157 | desc = "Edit preferences ",
158 | action = string.format("edit %s/lua/custom/init.lua", config_root),
159 | },
160 | {
161 | icon = " ",
162 | desc = "Mason",
163 | action = "Mason",
164 | },
165 | {
166 | icon = " ",
167 | desc = "About IceNvim",
168 | action = "IceAbout",
169 | },
170 | },
171 | footer = { "🧊 Hope that you enjoy using IceNvim 😀😀😀" },
172 | },
173 | },
174 | config = function(_, opts)
175 | require("dashboard").setup(opts)
176 |
177 | if vim.api.nvim_buf_get_name(0) == "" then
178 | vim.cmd "Dashboard"
179 | end
180 |
181 | -- Use the highlight command to replace instead of overriding the original highlight group
182 | -- Much more convenient than using vim.api.nvim_set_hl()
183 | vim.cmd "highlight DashboardFooter cterm=NONE gui=NONE"
184 | end,
185 | }
186 |
187 | config.fidget = {
188 | "j-hui/fidget.nvim",
189 | event = "VeryLazy",
190 | opts = {
191 | notification = {
192 | override_vim_notify = true,
193 | window = {
194 | x_padding = 2,
195 | align = "top",
196 | },
197 | },
198 | integration = {
199 | ["nvim-tree"] = {
200 | enable = false,
201 | },
202 | },
203 | },
204 | }
205 |
206 | config.fittencode = {
207 | "luozhiya/fittencode.nvim",
208 | opts = {
209 | inline_completion = {
210 | enable = false,
211 | },
212 | chat = {
213 | style = "floating",
214 | floating = {
215 | size = { width = 0.6, height = 0.6 },
216 | },
217 | },
218 | keymaps = {
219 | chat = {
220 | ["q"] = "close",
221 | [""] = "goto_previous_conversation",
222 | [""] = "goto_next_conversation",
223 | ["y"] = "copy_conversation",
224 | ["Y"] = "copy_all_conversations",
225 | ["d"] = "delete_conversation",
226 | ["D"] = "delete_all_conversations",
227 | },
228 | },
229 | source_completion = {
230 | enable = true,
231 | engine = "blink",
232 | },
233 | completion_mode = "source",
234 | log = {
235 | level = vim.log.levels.WARN,
236 | max_size = 1,
237 | },
238 | },
239 | config = function(_, opts)
240 | require("blink.cmp").add_source_provider("fittencode", {
241 | name = "fittencode",
242 | module = "fittencode.sources.blink",
243 | fallbacks = { "buffer" },
244 | })
245 |
246 | Ice.__FITTENCODE_SOURCE_ADDED = true
247 | require("fittencode").setup(opts)
248 | end,
249 | keys = {
250 | { "fb", "Fitten find_bugs", mode = "v", desc = "find bugs" },
251 | { "fc", "Fitten show_chat", mode = "v", desc = "show chat" },
252 | { "fd", "Fitten document_code", mode = "v", desc = "document code" },
253 | { "fe", "Fitten explain_code", mode = "v", desc = "explain code" },
254 | { "fE", "Fitten edit_code", mode = "v", desc = "edit code" },
255 | { "fo", "Fitten optimize_code", mode = "v", desc = "optimize code" },
256 | { "fr", "Fitten refactor_code", mode = "v", desc = "refactor code" },
257 | {
258 | "fs",
259 | "Fitten enable_completionlua vim.notify('FittenCode enabled')",
260 | desc = "start fittencode",
261 | },
262 | {
263 | "",
264 | function()
265 | require("blink.cmp").show { providers = { "fittencode" } }
266 | end,
267 | mode = "i",
268 | desc = "show fittencode completion",
269 | },
270 | },
271 | enabled = false,
272 | }
273 |
274 | config.gitsigns = {
275 | "lewis6991/gitsigns.nvim",
276 | event = "User IceLoad",
277 | main = "gitsigns",
278 | opts = {},
279 | keys = {
280 | { "gn", "Gitsigns next_hunk", desc = "next hunk", silent = true },
281 | { "gp", "Gitsigns prev_hunk", desc = "prev hunk", silent = true },
282 | { "gP", "Gitsigns preview_hunk", desc = "preview hunk", silent = true },
283 | { "gs", "Gitsigns stage_hunk", desc = "stage hunk", silent = true },
284 | { "gu", "Gitsigns undo_stage_hunk", desc = "undo stage", silent = true },
285 | { "gr", "Gitsigns reset_hunk", desc = "reset hunk", silent = true },
286 | { "gB", "Gitsigns stage_buffer", desc = "stage buffer", silent = true },
287 | { "gb", "Gitsigns blame", desc = "git blame", silent = true },
288 | { "gl", "Gitsigns blame_line", desc = "git blame line", silent = true },
289 | },
290 | }
291 |
292 | config["grug-far"] = {
293 | "MagicDuck/grug-far.nvim",
294 | opts = {
295 | disableBufferLineNumbers = true,
296 | startInInsertMode = true,
297 | windowCreationCommand = "tabnew %",
298 | },
299 | keys = {
300 | { "ug", "GrugFar", desc = "find and replace", silent = true },
301 | },
302 | }
303 |
304 | config.hop = {
305 | "smoka7/hop.nvim",
306 | main = "hop",
307 | opts = {
308 | -- This is actually equal to:
309 | -- require("hop.hint").HintPosition.END
310 | hint_position = 3,
311 | keys = "fjghdksltyrueiwoqpvbcnxmza",
312 | },
313 | keys = {
314 | { "hp", "HopWord", desc = "hop word", silent = true },
315 | },
316 | }
317 |
318 | config["indent-blankline"] = {
319 | "lukas-reineke/indent-blankline.nvim",
320 | event = "User IceAfter nvim-treesitter",
321 | main = "ibl",
322 | opts = {
323 | exclude = {
324 | filetypes = { "dashboard", "terminal", "help", "log", "markdown", "TelescopePrompt" },
325 | },
326 | indent = {
327 | highlight = {
328 | "IblIndent",
329 | "RainbowDelimiterRed",
330 | "RainbowDelimiterYellow",
331 | "RainbowDelimiterBlue",
332 | "RainbowDelimiterOrange",
333 | "RainbowDelimiterGreen",
334 | "RainbowDelimiterViolet",
335 | "RainbowDelimiterCyan",
336 | },
337 | },
338 | },
339 | }
340 |
341 | config.lualine = {
342 | "nvim-lualine/lualine.nvim",
343 | dependencies = { "nvim-tree/nvim-web-devicons" },
344 | event = "User IceLoad",
345 | main = "lualine",
346 | opts = {
347 | options = {
348 | theme = "auto",
349 | component_separators = { left = "", right = "" },
350 | section_separators = { left = "", right = "" },
351 | disabled_filetypes = { "undotree", "diff" },
352 | },
353 | extensions = { "nvim-tree" },
354 | sections = {
355 | lualine_b = { "branch", "diff" },
356 | lualine_c = {
357 | "filename",
358 | },
359 | lualine_x = {
360 | "filesize",
361 | {
362 | "fileformat",
363 | symbols = { unix = symbols.Unix, dos = symbols.Dos, mac = symbols.Mac },
364 | },
365 | "encoding",
366 | "filetype",
367 | },
368 | },
369 | },
370 | }
371 |
372 | config["markdown-preview"] = {
373 | "iamcco/markdown-preview.nvim",
374 | ft = "markdown",
375 | config = function()
376 | vim.g.mkdp_filetypes = { "markdown" }
377 | vim.g.mkdp_auto_close = 0
378 | end,
379 | build = "cd app && yarn install",
380 | keys = {
381 | {
382 | "",
383 | "MarkdownPreviewToggle",
384 | desc = "markdown preview",
385 | ft = "markdown",
386 | silent = true,
387 | },
388 | },
389 | }
390 |
391 | config.neogit = {
392 | "NeogitOrg/neogit",
393 | dependencies = { "nvim-lua/plenary.nvim" },
394 | main = "neogit",
395 | opts = {
396 | disable_hint = true,
397 | status = {
398 | recent_commit_count = 30,
399 | },
400 | commit_editor = {
401 | kind = "auto",
402 | show_staged_diff = false,
403 | },
404 | },
405 | keys = {
406 | { "gt", "Neogit", desc = "neogit", silent = true },
407 | },
408 | config = function(_, opts)
409 | require("neogit").setup(opts)
410 | Ice.ft.NeogitCommitMessage = function()
411 | vim.api.nvim_win_set_cursor(0, { 1, 0 })
412 | end
413 | end,
414 | }
415 |
416 | config.nui = {
417 | "MunifTanjim/nui.nvim",
418 | lazy = true,
419 | }
420 |
421 | config["nvim-autopairs"] = {
422 | "windwp/nvim-autopairs",
423 | event = "InsertEnter",
424 | main = "nvim-autopairs",
425 | opts = {},
426 | }
427 |
428 | config["nvim-scrollview"] = {
429 | "dstein64/nvim-scrollview",
430 | event = "User IceLoad",
431 | main = "scrollview",
432 | opts = {
433 | excluded_filetypes = { "nvimtree" },
434 | current_only = true,
435 | winblend = 75,
436 | base = "right",
437 | column = 1,
438 | },
439 | }
440 |
441 | config["nvim-transparent"] = {
442 | "xiyaowong/nvim-transparent",
443 | opts = {
444 | extra_groups = {
445 | "NvimTreeNormal",
446 | "NvimTreeNormalNC",
447 | },
448 | },
449 | config = function(_, opts)
450 | local autogroup = vim.api.nvim_create_augroup("transparent", { clear = true })
451 | vim.api.nvim_create_autocmd("ColorScheme", {
452 | group = autogroup,
453 | callback = function()
454 | local normal_hl = vim.api.nvim_get_hl(0, { name = "Normal" })
455 | local foreground = string.format("#%06x", normal_hl.fg)
456 | local background = string.format("#%06x", normal_hl.bg)
457 | vim.cmd("highlight default IceNormal guifg=" .. foreground .. " guibg=" .. background)
458 |
459 | require("transparent").clear()
460 | end,
461 | })
462 | -- Enable transparent by default
463 | local transparent_cache = vim.fn.stdpath "data" .. "/transparent_cache"
464 | if not require("core.utils").file_exists(transparent_cache) then
465 | local f = io.open(transparent_cache, "w")
466 | f:write "true"
467 | f:close()
468 | end
469 |
470 | require("transparent").setup(opts)
471 |
472 | local old_get_hl = vim.api.nvim_get_hl
473 | vim.api.nvim_get_hl = function(ns_id, opt)
474 | if opt.name == "Normal" then
475 | local attempt = old_get_hl(0, { name = "IceNormal" })
476 | if next(attempt) ~= nil then
477 | opt.name = "IceNormal"
478 | end
479 | end
480 | return old_get_hl(ns_id, opt)
481 | end
482 | end,
483 | }
484 |
485 | config["nvim-tree"] = {
486 | "nvim-tree/nvim-tree.lua",
487 | dependencies = { "nvim-tree/nvim-web-devicons" },
488 | opts = {
489 | on_attach = function(bufnr)
490 | local api = require "nvim-tree.api"
491 | local opt = { buffer = bufnr, silent = true }
492 |
493 | api.config.mappings.default_on_attach(bufnr)
494 |
495 | require("core.utils").group_map({
496 | edit = {
497 | "n",
498 | "",
499 | function()
500 | local node = api.tree.get_node_under_cursor()
501 | if node.name ~= ".." and node.fs_stat.type == "file" then
502 | -- Taken partially from:
503 | -- https://support.microsoft.com/en-us/windows/common-file-name-extensions-in-windows-da4a4430-8e76-89c5-59f7-1cdbbc75cb01
504 | --
505 | -- Not all are included for speed's sake
506 | -- stylua: ignore start
507 | local extensions_opened_externally = {
508 | "avi", "bmp", "doc", "docx", "exe", "flv", "gif", "jpg", "jpeg", "m4a", "mov", "mp3",
509 | "mp4", "mpeg", "mpg", "pdf", "png", "ppt", "pptx", "psd", "pub", "rar", "rtf", "tif",
510 | "tiff", "wav", "xls", "xlsx", "zip",
511 | }
512 | -- stylua: ignore end
513 | if table.find(extensions_opened_externally, node.extension) then
514 | api.node.run.system()
515 | return
516 | end
517 | end
518 |
519 | api.node.open.edit()
520 | end,
521 | },
522 | vertical_split = { "n", "V", api.node.open.vertical },
523 | horizontal_split = { "n", "H", api.node.open.horizontal },
524 | toggle_hidden_file = { "n", ".", api.tree.toggle_hidden_filter },
525 | reload = { "n", "", api.tree.reload },
526 | create = { "n", "a", api.fs.create },
527 | remove = { "n", "d", api.fs.remove },
528 | rename = { "n", "r", api.fs.rename },
529 | cut = { "n", "x", api.fs.cut },
530 | copy = { "n", "y", api.fs.copy.node },
531 | paste = { "n", "p", api.fs.paste },
532 | system_run = { "n", "s", api.node.run.system },
533 | show_info = { "n", "i", api.node.show_info_popup },
534 | }, opt)
535 | end,
536 | git = {
537 | enable = false,
538 | },
539 | update_focused_file = {
540 | enable = true,
541 | },
542 | filters = {
543 | dotfiles = false,
544 | custom = { "node_modules", "^.git$" },
545 | exclude = { ".gitignore" },
546 | },
547 | respect_buf_cwd = true,
548 | view = {
549 | width = 30,
550 | side = "left",
551 | number = false,
552 | relativenumber = false,
553 | signcolumn = "yes",
554 | },
555 | actions = {
556 | open_file = {
557 | resize_window = true,
558 | quit_on_open = true,
559 | },
560 | },
561 | },
562 | keys = {
563 | { "uf", "NvimTreeToggle", desc = "toggle nvim tree", silent = true },
564 | },
565 | }
566 |
567 | config["nvim-treesitter"] = {
568 | "nvim-treesitter/nvim-treesitter",
569 | build = ":TSUpdate",
570 | dependencies = { "hiphish/rainbow-delimiters.nvim" },
571 | event = "User IceAfter colorscheme",
572 | branch = "main",
573 | opts = {
574 | -- Preserved for compatibility concerns
575 | -- stylua: ignore start
576 | ensure_installed = {
577 | "bash", "c", "c_sharp", "cpp", "css", "go", "html", "javascript", "json", "lua", "markdown",
578 | "markdown_inline", "python", "query", "rust", "toml", "typescript", "typst", "tsx", "vim", "vimdoc",
579 | },
580 | -- stylua: ignore end
581 | },
582 | config = function(_, opts)
583 | local nvim_treesitter = require "nvim-treesitter"
584 | nvim_treesitter.setup()
585 |
586 | local pattern = {}
587 | for _, parser in ipairs(opts.ensure_installed) do
588 | local has_parser, _ = pcall(vim.treesitter.language.inspect, parser)
589 |
590 | if not has_parser then
591 | -- Needs restart to take effect
592 | nvim_treesitter.install(parser)
593 | else
594 | vim.list_extend(pattern, vim.treesitter.language.get_filetypes(parser))
595 | end
596 | end
597 |
598 | local group = vim.api.nvim_create_augroup("NvimTreesitterFt", { clear = true })
599 | vim.api.nvim_create_autocmd("FileType", {
600 | group = group,
601 | pattern = pattern,
602 | callback = function(ev)
603 | local max_filesize = 100 * 1024
604 | local ok, stats = pcall(vim.uv.fs_stat, vim.api.nvim_buf_get_name(ev.buf))
605 | if not (ok and stats and stats.size > max_filesize) then
606 | vim.treesitter.start()
607 | if vim.bo.filetype ~= "dart" then
608 | -- Conflicts with flutter-tools.nvim, causing performance issues
609 | vim.bo.indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()"
610 | end
611 | end
612 | end,
613 | })
614 |
615 | local rainbow_delimiters = require "rainbow-delimiters"
616 |
617 | vim.g.rainbow_delimiters = {
618 | strategy = {
619 | [""] = rainbow_delimiters.strategy["global"],
620 | vim = rainbow_delimiters.strategy["local"],
621 | },
622 | query = {
623 | [""] = "rainbow-delimiters",
624 | lua = "rainbow-blocks",
625 | },
626 | highlight = {
627 | "RainbowDelimiterRed",
628 | "RainbowDelimiterYellow",
629 | "RainbowDelimiterBlue",
630 | "RainbowDelimiterOrange",
631 | "RainbowDelimiterGreen",
632 | "RainbowDelimiterViolet",
633 | "RainbowDelimiterCyan",
634 | },
635 | }
636 | rainbow_delimiters.enable()
637 |
638 | -- In markdown files, the rendered output would only display the correct highlight if the code is set to scheme
639 | -- However, this would result in incorrect highlight in neovim
640 | -- Therefore, the scheme language should be linked to query
641 | vim.treesitter.language.register("query", "scheme")
642 |
643 | vim.api.nvim_exec_autocmds("User", { pattern = "IceAfter nvim-treesitter" })
644 | vim.api.nvim_exec_autocmds("FileType", { group = "NvimTreesitterFt" })
645 | end,
646 | }
647 |
648 | config.orgmode = {
649 | "nvim-orgmode/orgmode",
650 | ft = { "org" },
651 | -- See https://github.com/nvim-orgmode/orgmode/blob/master/DOCS.md
652 | opts = {
653 | org_agenda_files = "~/Documents/orgfiles/**/*",
654 | org_default_notes_file = "~/Documents/orgfiles/refile.org",
655 | org_todo_keywords = { "TODO(t)", "URGENT(u)", "PENDING(p)", "|", "DONE(d)" },
656 | win_split_mode = { "float", 0.6 },
657 | org_startup_folded = "inherit",
658 | org_todo_keyword_faces = {
659 | URGENT = ":foreground white :background red :weight bold",
660 | PENDING = ":foreground white :background gray",
661 | },
662 | org_hide_leading_stars = true,
663 | org_hide_emphasis_markers = true,
664 | mappings = {
665 | org = {
666 | org_toggle_checkbox = "oT",
667 | },
668 | },
669 | },
670 | config = function(_, opts)
671 | require("blink.cmp").add_source_provider("orgmode", {
672 | name = "Orgmode",
673 | module = "orgmode.org.autocompletion.blink",
674 | fallbacks = { "buffer" },
675 | })
676 | Ice.__ORGMODE_SOURCE_ADDED = true
677 | require("orgmode").setup(opts)
678 | end,
679 | keys = {
680 | { "lf", "gggqG", mode = "n", desc = "format file", ft = "org", silent = true },
681 | { "lf", "gq", mode = "v", desc = "format selected", ft = "org", silent = true },
682 | { "oa", "Org agenda", desc = "orgmode agenda" },
683 | { "oc", "Org capture", desc = "orgmode capture" },
684 | },
685 | }
686 |
687 | config.surround = {
688 | "kylechui/nvim-surround",
689 | version = "*",
690 | opts = {},
691 | event = "User IceLoad",
692 | }
693 |
694 | config.telescope = {
695 | "nvim-telescope/telescope.nvim",
696 | dependencies = {
697 | "nvim-lua/plenary.nvim",
698 | {
699 | "nvim-telescope/telescope-fzf-native.nvim",
700 | build = "cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=Release && "
701 | .. "cmake --build build --config Release && "
702 | .. "cmake --install build --prefix build",
703 | },
704 | },
705 | -- ensure that other plugins that use telescope can function properly
706 | cmd = "Telescope",
707 | opts = {
708 | defaults = {
709 | initial_mode = "insert",
710 | mappings = {
711 | i = {
712 | [""] = "move_selection_next",
713 | [""] = "move_selection_previous",
714 | [""] = "cycle_history_next",
715 | [""] = "cycle_history_prev",
716 | [""] = "close",
717 | [""] = "preview_scrolling_up",
718 | [""] = "preview_scrolling_down",
719 | },
720 | },
721 | },
722 | pickers = {
723 | find_files = {
724 | winblend = 20,
725 | },
726 | },
727 | extensions = {
728 | fzf = {
729 | fuzzy = true,
730 | override_generic_sorter = true,
731 | override_file_sorter = true,
732 | case_mode = "smart_case",
733 | },
734 | },
735 | },
736 | config = function(_, opts)
737 | local telescope = require "telescope"
738 | telescope.setup(opts)
739 | telescope.load_extension "fzf"
740 | end,
741 | keys = {
742 | { "tf", "Telescope find_files", desc = "find file", silent = true },
743 | { "t", "Telescope live_grep", desc = "live grep", silent = true },
744 | },
745 | }
746 |
747 | config["todo-comments"] = {
748 | "folke/todo-comments.nvim",
749 | dependencies = { "nvim-lua/plenary.nvim" },
750 | event = "User IceLoad",
751 | main = "todo-comments",
752 | opts = {},
753 | keys = {
754 | { "ut", "TodoTelescope", desc = "todo list", silent = true },
755 | },
756 | }
757 |
758 | config.ufo = {
759 | "kevinhwang91/nvim-ufo",
760 | dependencies = {
761 | "kevinhwang91/promise-async",
762 | },
763 | event = "VeryLazy",
764 | opts = {
765 | preview = {
766 | win_config = {
767 | border = "rounded",
768 | winhighlight = "Normal:Folded",
769 | winblend = 0,
770 | },
771 | },
772 | },
773 | config = function(_, opts)
774 | vim.opt.foldenable = true
775 |
776 | require("ufo").setup(opts)
777 | end,
778 | keys = {
779 | {
780 | "zR",
781 | function()
782 | require("ufo").openAllFolds()
783 | end,
784 | desc = "Open all folds",
785 | },
786 | {
787 | "zM",
788 | function()
789 | require("ufo").closeAllFolds()
790 | end,
791 | desc = "Close all folds",
792 | },
793 | {
794 | "zp",
795 | function()
796 | require("ufo").peekFoldedLinesUnderCursor()
797 | end,
798 | desc = "Preview folded content",
799 | },
800 | },
801 | }
802 |
803 | config.undotree = {
804 | "mbbill/undotree",
805 | config = function()
806 | vim.g.undotree_WindowLayout = 2
807 | vim.g.undotree_TreeNodeShape = "-"
808 | end,
809 | keys = {
810 | { "uu", "UndotreeToggle", desc = "undo tree toggle", silent = true },
811 | },
812 | }
813 |
814 | config["which-key"] = {
815 | "folke/which-key.nvim",
816 | event = "VeryLazy",
817 | opts = {
818 | icons = {
819 | mappings = false,
820 | },
821 | plugins = {
822 | marks = true,
823 | registers = true,
824 | spelling = {
825 | enabled = false,
826 | },
827 | presets = {
828 | operators = false,
829 | motions = true,
830 | text_objects = true,
831 | windows = true,
832 | nav = true,
833 | z = true,
834 | g = true,
835 | },
836 | },
837 | spec = {
838 | { "b", group = "+buffer" },
839 | { "c", group = "+comment" },
840 | { "f", group = "+fittencode" },
841 | { "g", group = "+git" },
842 | { "h", group = "+hop" },
843 | { "l", group = "+lsp" },
844 | { "o", group = "+orgmode" },
845 | { "t", group = "+telescope" },
846 | { "u", group = "+utils" },
847 | },
848 | win = {
849 | border = "none",
850 | padding = { 1, 0, 1, 0 },
851 | wo = {
852 | winblend = 0,
853 | },
854 | zindex = 1000,
855 | },
856 | },
857 | }
858 |
859 | -- Colorschemes
860 | config["github"] = { "projekt0n/github-nvim-theme", lazy = true }
861 | config["gruvbox"] = { "ellisonleao/gruvbox.nvim", lazy = true }
862 | config["kanagawa"] = { "rebelot/kanagawa.nvim", lazy = true }
863 | config["miasma"] = { "xero/miasma.nvim", lazy = true }
864 | config["nightfox"] = { "EdenEast/nightfox.nvim", lazy = true }
865 | config["tokyonight"] = { "folke/tokyonight.nvim", lazy = true }
866 |
867 | Ice.plugins = config
868 |
--------------------------------------------------------------------------------
/lua/plugins/init.lua:
--------------------------------------------------------------------------------
1 | require "plugins.lazy"
2 | require "plugins.config"
3 | require "plugins.colorscheme"
4 | require "plugins.keymap"
5 |
6 | require "lsp"
7 |
--------------------------------------------------------------------------------
/lua/plugins/keymap.lua:
--------------------------------------------------------------------------------
1 | local utils = require "plugins.utils"
2 |
3 | Ice.keymap.plugins = {
4 | lazy_profile = { "n", "ul", "Lazy profile" },
5 | select_colorscheme = { "n", "", utils.select_colorscheme },
6 | view_configuration = { "n", "uc", utils.view_configuration },
7 | }
8 |
--------------------------------------------------------------------------------
/lua/plugins/lazy.lua:
--------------------------------------------------------------------------------
1 | -- Set up lazy.nvim
2 | local lazypath = vim.fn.stdpath "data" .. "/lazy/lazy.nvim"
3 |
4 | if not require("core.utils").noplugin then
5 | if not vim.uv.fs_stat(lazypath) then
6 | vim.fn.system {
7 | "git",
8 | "clone",
9 | "--filter=blob:none",
10 | "https://github.com/folke/lazy.nvim.git",
11 | "--branch=stable",
12 | lazypath,
13 | }
14 | end
15 | vim.opt.rtp:prepend(lazypath)
16 | end
17 |
18 | Ice.lazy = {
19 | performance = {
20 | rtp = {
21 | disabled_plugins = {
22 | "editorconfig",
23 | "gzip",
24 | "man",
25 | "matchit",
26 | "matchparen",
27 | "netrwPlugin",
28 | "osc52",
29 | "rplugin",
30 | "shada",
31 | "spellfile",
32 | "tarPlugin",
33 | "tohtml",
34 | "tutor",
35 | "zipPlugin",
36 | },
37 | },
38 | },
39 | ui = {
40 | backdrop = 100,
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/lua/plugins/utils.lua:
--------------------------------------------------------------------------------
1 | ---@diagnostic disable: need-check-nil
2 | local utils = {}
3 |
4 | -- Set up colorscheme and Ice.colorscheme, but does not take care of lualine
5 | -- The colorscheme is a table with:
6 | -- - name: to be called with the `colorscheme` command
7 | -- - setup: optional; can either be:
8 | -- - a function called alongside `colorscheme`
9 | -- - a table for plugin setup
10 | -- - background: "light" / "dark"
11 | -- - lualine_theme: optional
12 | ---@param colorscheme_name string
13 | utils.colorscheme = function(colorscheme_name)
14 | Ice.colorscheme = colorscheme_name
15 |
16 | local colorscheme = Ice.colorschemes[colorscheme_name]
17 | if not colorscheme then
18 | vim.notify(colorscheme_name .. " is not a valid color scheme!", vim.log.levels.ERROR)
19 | return
20 | end
21 |
22 | if type(colorscheme.setup) == "table" then
23 | require(colorscheme.name).setup(colorscheme.setup)
24 | elseif type(colorscheme.setup) == "function" then
25 | colorscheme.setup()
26 | end
27 | require("lazy.core.loader").colorscheme(colorscheme.name)
28 | vim.cmd("colorscheme " .. colorscheme.name)
29 | vim.o.background = colorscheme.background
30 |
31 | vim.api.nvim_set_hl(0, "Visual", { reverse = true })
32 |
33 | vim.api.nvim_exec_autocmds("User", { pattern = "IceAfter colorscheme" })
34 | end
35 |
36 | -- Switch colorscheme
37 | utils.select_colorscheme = function()
38 | local status, _ = pcall(require, "telescope")
39 | if not status then
40 | return
41 | end
42 |
43 | local pickers = require "telescope.pickers"
44 | local finders = require "telescope.finders"
45 | local conf = require("telescope.config").values
46 | local actions = require "telescope.actions"
47 | local action_state = require "telescope.actions.state"
48 |
49 | local function picker(opts)
50 | opts = opts or {}
51 |
52 | local colorschemes = Ice.colorschemes
53 | local suffix_current = " (current)"
54 | local results = { Ice.colorscheme .. suffix_current }
55 | for name, _ in require("core.utils").ordered_pair(colorschemes) do
56 | if name ~= Ice.colorscheme then
57 | results[#results + 1] = name
58 | end
59 | end
60 |
61 | pickers
62 | .new(opts, {
63 | prompt_title = "Colorschemes",
64 | finder = finders.new_table {
65 | entry_maker = function(entry)
66 | local pattern = string.gsub(suffix_current, "%(", "%%%(")
67 | pattern = string.gsub(pattern, "%)", "%%%)")
68 | local colorscheme, _ = string.gsub(entry, pattern, "")
69 |
70 | return {
71 | value = colorscheme,
72 | display = entry,
73 | ordinal = entry,
74 | }
75 | end,
76 | results = results,
77 | },
78 | sorter = conf.generic_sorter(opts),
79 | attach_mappings = function(prompt_bufnr, _)
80 | actions.select_default:replace(function()
81 | actions.close(prompt_bufnr)
82 |
83 | local selection = action_state.get_selected_entry()
84 | if selection == nil then
85 | return
86 | end
87 |
88 | local colorscheme = selection.value
89 | local config = colorschemes[colorscheme]
90 |
91 | if Ice.plugins["nvim-transparent"].enabled ~= false then
92 | if config.background == "light" then
93 | ---@diagnostic disable-next-line: param-type-mismatch
94 | pcall(vim.cmd, "TransparentDisable")
95 | else
96 | ---@diagnostic disable-next-line: param-type-mismatch
97 | pcall(vim.cmd, "TransparentEnable")
98 | end
99 | end
100 |
101 | utils.colorscheme(selection.value)
102 |
103 | local colorscheme_cache = vim.fn.stdpath "data" .. "/colorscheme"
104 | local f = io.open(colorscheme_cache, "w")
105 | f:write(colorscheme)
106 | f:close()
107 | end)
108 | return true
109 | end,
110 | })
111 | :find()
112 | end
113 |
114 | picker()
115 | end
116 |
117 | -- Quickly look through configuration files using telescope
118 | utils.view_configuration = function()
119 | local status, _ = pcall(require, "telescope")
120 | if not status then
121 | return
122 | end
123 |
124 | local pickers = require "telescope.pickers"
125 | local finders = require "telescope.finders"
126 | local conf = require("telescope.config").values
127 | local actions = require "telescope.actions"
128 | local action_state = require "telescope.actions.state"
129 | local previewers = require "telescope.previewers.buffer_previewer"
130 | local from_entry = require "telescope.from_entry"
131 |
132 | local function picker(opts)
133 | opts = opts or {}
134 |
135 | local config_root = vim.fn.stdpath "config"
136 | local files = require("plenary.scandir").scan_dir(config_root, { hidden = true })
137 | local sep = require("plenary.path").path.sep
138 | local picker_sep = "/" -- sep that is displayed in the picker
139 | local results = {}
140 |
141 | local make_entry = require("telescope.make_entry").gen_from_file
142 |
143 | for _, item in pairs(files) do
144 | item = string.gsub(item, config_root, "")
145 | item = string.gsub(item, sep, picker_sep)
146 | item = string.sub(item, 2)
147 | if not (string.find(item, "bin/") or string.find(item, ".git/") or string.find(item, "screenshots/")) then
148 | results[#results + 1] = item
149 | end
150 | end
151 |
152 | pickers
153 | .new(opts, {
154 | prompt_title = "Configuration Files",
155 | finder = finders.new_table {
156 | entry_maker = make_entry(opts),
157 | results = results,
158 | },
159 | previewer = (function(_opts)
160 | _opts = _opts or {}
161 | return previewers.new_buffer_previewer {
162 | title = "Configuration",
163 | get_buffer_by_name = function(_, entry)
164 | return from_entry.path(entry, false)
165 | end,
166 | define_preview = function(self, entry)
167 | local p = config_root .. "/" .. entry.filename
168 | if p == nil or p == "" then
169 | return
170 | end
171 | conf.buffer_previewer_maker(p, self.state.bufnr, {
172 | bufname = self.state.bufname,
173 | winid = self.state.winid,
174 | preview = _opts.preview,
175 | file_encoding = _opts.file_encoding,
176 | })
177 | end,
178 | }
179 | end)(opts),
180 | sorter = conf.generic_sorter(opts),
181 | attach_mappings = function(prompt_bufnr, _)
182 | actions.select_default:replace(function()
183 | actions.close(prompt_bufnr)
184 |
185 | local selected_entry = action_state.get_selected_entry()
186 | if selected_entry ~= nil then
187 | local selection = selected_entry[1]
188 | selection = string.gsub(selection, picker_sep, sep)
189 | local full_path = config_root .. sep .. selection
190 |
191 | vim.cmd("edit " .. full_path)
192 | end
193 | end)
194 | return true
195 | end,
196 | })
197 | :find()
198 | end
199 |
200 | picker()
201 | end
202 |
203 | return utils
204 |
--------------------------------------------------------------------------------
/screenshots/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shaobin-Jiang/IceNvim/1a1c595f0a03c50f6bc10f7e496db286a507d825/screenshots/1.jpg
--------------------------------------------------------------------------------
/screenshots/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shaobin-Jiang/IceNvim/1a1c595f0a03c50f6bc10f7e496db286a507d825/screenshots/2.jpg
--------------------------------------------------------------------------------
/screenshots/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shaobin-Jiang/IceNvim/1a1c595f0a03c50f6bc10f7e496db286a507d825/screenshots/3.jpg
--------------------------------------------------------------------------------
/screenshots/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Shaobin-Jiang/IceNvim/1a1c595f0a03c50f6bc10f7e496db286a507d825/screenshots/4.jpg
--------------------------------------------------------------------------------