├── LICENSE
├── README.md
├── lua
└── markid.lua
└── plugin
└── markid.lua
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Dr. David A. Kunz
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 | # markid
2 |
3 | A Neovim extension to highlight same-name identifiers with the same color.
4 |
5 | ## Motivation
6 |
7 | Syntax highlighting is mostly based on element kinds of the abstract syntax tree.
8 | This sometimes leads to different visual representations of the same variable, consider this example:
9 |
10 |
11 |
12 | Here, `myParam` has the colors yellow and white, making it hard for the developer to recognise that both represent the same thing.
13 |
14 | Now with markid, it's ensured that same-name identifiers are represented with the same color:
15 |
16 |
17 |
18 | Here's a slightly more complicated example, try to track the flow of `qux`:
19 |
20 |
21 |
22 |
23 |
24 |
25 | ## Installation
26 |
27 | Requirements: [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) including a parser for your language
28 |
29 | For [vim-plug](https://github.com/junegunn/vim-plug):
30 | ```
31 | Plug 'David-Kunz/markid'
32 | ```
33 | For [packer](https://github.com/wbthomason/packer.nvim):
34 | ```
35 | use 'David-Kunz/markid'
36 | ```
37 |
38 | Enable the [nvim-treesitter](https://github.com/nvim-treesitter/nvim-treesitter) plugin:
39 |
40 | ```lua
41 | require'nvim-treesitter.configs'.setup {
42 | markid = { enable = true }
43 | }
44 | ```
45 |
46 | ## Options
47 |
48 | These are the configuration options (with defaults):
49 |
50 | ```lua
51 | local m = require'markid'
52 | require'nvim-treesitter.configs'.setup {
53 | markid = {
54 | enable = true,
55 | colors = m.colors.medium,
56 | queries = m.queries,
57 | is_supported = function(lang)
58 | local queries = configs.get_module("markid").queries
59 | return pcall(vim.treesitter.parse_query, lang, queries[lang] or queries['default'])
60 | end
61 | }
62 | }
63 |
64 | M.colors = {
65 | dark = { "#619e9d", "#9E6162", "#81A35C", "#7E5CA3", "#9E9261", "#616D9E", "#97687B", "#689784", "#999C63", "#66639C" },
66 | bright = {"#f5c0c0", "#f5d3c0", "#f5eac0", "#dff5c0", "#c0f5c8", "#c0f5f1", "#c0dbf5", "#ccc0f5", "#f2c0f5", "#98fc03" },
67 | medium = { "#c99d9d", "#c9a99d", "#c9b79d", "#c9c39d", "#bdc99d", "#a9c99d", "#9dc9b6", "#9dc2c9", "#9da9c9", "#b29dc9" }
68 | }
69 |
70 | m.queries = {
71 | default = '(identifier) @markid',
72 | javascript = [[
73 | (identifier) @markid
74 | (property_identifier) @markid
75 | (shorthand_property_identifier_pattern) @markid
76 | ]]
77 | }
78 | m.queries.typescript = m.queries.javascript
79 | ```
80 |
81 | The `m.queries` table above can be used to define language-specific highlighting rules via custom Treesitter queries. Alternatively, markid can also source queries from standalone files located in your local runtime `queries/` directory. Simply create a new directory in your nvim config folder for your language of choice, e.g. `$HOME/.config/nvim/queries/python`, and write your query in a file called `markid.scm`
82 |
83 | ## Custom Highlight Groups
84 |
85 | For more control, you can define the highlight groups `markid1`, `markid2`, ..., `markid10`, this is especially useful for theme designers.
86 |
87 | Example:
88 | ```lua
89 | vim.api.nvim_set_hl(0, 'markid1', { fg = '#c99d9d', bg = '#003844', underline = true })
90 | vim.api.nvim_set_hl(0, 'markid2', { fg = '#c9a99d', bg = '#003844', underline = true })
91 | -- ...
92 | vim.api.nvim_set_hl(0, 'markid10', { fg = '#c9b79d', bg = '#003844', underline = true })
93 | ```
94 |
--------------------------------------------------------------------------------
/lua/markid.lua:
--------------------------------------------------------------------------------
1 | local ts = require("nvim-treesitter")
2 | local parsers = require("nvim-treesitter.parsers")
3 | local configs = require("nvim-treesitter.configs")
4 |
5 | local namespace = vim.api.nvim_create_namespace("markid")
6 |
7 | -- Global table to store names of created highlight groups
8 | local hl_group_of_identifier = {}
9 |
10 | local string_to_int = function(str)
11 | if str == nil then
12 | return 0
13 | end
14 | local int = 0
15 | for i = 1, #str do
16 | local c = str:sub(i, i)
17 | int = int + string.byte(c)
18 | end
19 | return int
20 | end
21 |
22 |
23 | local M = {}
24 |
25 | M.colors = {
26 | dark = { "#619e9d", "#9E6162", "#81A35C", "#7E5CA3", "#9E9261", "#616D9E", "#97687B", "#689784", "#999C63", "#66639C" },
27 | bright = {"#f5c0c0", "#f5d3c0", "#f5eac0", "#dff5c0", "#c0f5c8", "#c0f5f1", "#c0dbf5", "#ccc0f5", "#f2c0f5", "#d8e4bc" },
28 | medium = { "#c99d9d", "#c9a99d", "#c9b79d", "#c9c39d", "#bdc99d", "#a9c99d", "#9dc9b6", "#9dc2c9", "#9da9c9", "#b29dc9" }
29 | }
30 |
31 | M.queries = {
32 | default = "(identifier) @markid",
33 | javascript = [[
34 | (identifier) @markid
35 | (property_identifier) @markid
36 | (shorthand_property_identifier_pattern) @markid
37 | (shorthand_property_identifier) @markid
38 | ]]
39 | }
40 | M.queries.typescript = M.queries.javascript
41 |
42 | function M.init()
43 | ts.define_modules {
44 | markid = {
45 | module_path = "markid",
46 | attach = function(bufnr, lang)
47 | local config = configs.get_module("markid")
48 |
49 | local query = vim.treesitter.query.get(lang, 'markid')
50 | if query == nil or query == '' then
51 | query = vim.treesitter.query.parse(lang, config.queries[lang] or config.queries["default"])
52 | end
53 | local parser = parsers.get_parser(bufnr, lang)
54 | local tree = parser:parse()[1]
55 | local root = tree:root()
56 |
57 | local highlight_tree = function(root_tree, cap_start, cap_end)
58 | if not vim.api.nvim_buf_is_loaded(bufnr) then
59 | return
60 | end
61 | vim.api.nvim_buf_clear_namespace(bufnr, namespace, cap_start, cap_end)
62 | for id, node in query:iter_captures(root_tree, bufnr, cap_start, cap_end) do
63 | local name = query.captures[id]
64 | if name == "markid" then
65 | local text = vim.treesitter.get_node_text(node, bufnr)
66 | if text ~= nil then
67 | if hl_group_of_identifier[text] == nil then
68 | -- semi random: Allows to have stable global colors for the same name
69 | local colors_count = 0
70 | if not config.colors then
71 | while vim.fn.hlexists('markid' .. colors_count + 1) == 1 do
72 | colors_count = colors_count + 1
73 | end
74 | else
75 | colors_count = #config.colors
76 | end
77 | if colors_count == 0 then
78 | return
79 | end
80 | local idx = (string_to_int(text) % colors_count) + 1
81 | local group_name = "markid" .. idx
82 | if config.colors then
83 | vim.api.nvim_set_hl(0, group_name, { default = true, fg = config.colors[idx] })
84 | end
85 | hl_group_of_identifier[text] = group_name
86 | end
87 | local start_row, start_col, end_row, end_col = node:range()
88 | local range_start = {start_row, start_col}
89 | local range_end = {end_row, end_col}
90 | vim.highlight.range(
91 | bufnr,
92 | namespace,
93 | hl_group_of_identifier[text],
94 | range_start,
95 | range_end
96 | )
97 | end
98 | end
99 | end
100 | end
101 |
102 | highlight_tree(root, 0, -1)
103 | parser:register_cbs(
104 | {
105 | on_changedtree = function(changes, tree)
106 | highlight_tree(tree:root(), 0, -1) -- can be made more efficient, but for plain identifier changes, `changes` is empty
107 | end
108 | }
109 | )
110 | end,
111 | detach = function(bufnr)
112 | if vim.api.nvim_buf_is_valid(bufnr) then
113 | vim.api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
114 | end
115 | end,
116 | is_supported = function(lang)
117 | local queries = configs.get_module("markid").queries
118 | return pcall(vim.treesitter.query.parse, lang, queries[lang] or queries["default"])
119 | end,
120 | colors = M.colors.medium,
121 | queries = M.queries
122 | }
123 | }
124 | end
125 |
126 | return M
127 |
--------------------------------------------------------------------------------
/plugin/markid.lua:
--------------------------------------------------------------------------------
1 | require "markid".init()
2 |
--------------------------------------------------------------------------------