├── 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 | --------------------------------------------------------------------------------