9 Commits

Author SHA1 Message Date
Ivar Fatland 4cf6720f2f some progress but eh 2026-05-03 16:56:40 +02:00
Ivar Fatland 685387f77f Merge branch 'master' into feature/ctags-ls 2026-05-02 18:08:29 +02:00
Ivar Fatland c4318fcfca some progress 2026-05-01 17:33:32 +02:00
Ivar Fatland 9ff13e3007 some shite WIP 2026-04-30 20:41:50 +02:00
Ivar Fatland 47c5b39af5 ignore tags files 2026-04-30 20:41:38 +02:00
Ivar Fatland 7e7f385f10 add capabilities
did this do something? idk
2026-04-30 20:41:18 +02:00
Ivar Fatland 2fea6fe116 kinda sorta 2026-04-30 15:49:45 +02:00
Ivar Fatland c6c426621d Merge branch 'master' into feature/ctags-ls 2026-04-29 22:47:42 +02:00
Ivar Fatland db54f3d9eb not working :( 2026-04-29 22:42:34 +02:00
10 changed files with 303 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
__pycache__
+16
View File
@@ -0,0 +1,16 @@
# language server for ctags
Initial version is written in python.
The goal of this is for the language server to detect changes in a ctags file
in the pwd, and update the internal representation of the code based on that.
No automatic calling of ctags, the user is responsible for keeping it updated
as and when they like.
Hope to sort all possible go to definitions somewhat intelligently based on the
current path of the file.
Fuzzy completions?
Built with [pygls](https://github.com/openlawlibrary/pygls).
+4
View File
@@ -0,0 +1,4 @@
test:
uv run pytest
tag:
ctags -R ~/cig/ .
+17
View File
@@ -0,0 +1,17 @@
[project]
name = "ctags-ls"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = [
"pygls>=2.1.1",
"pytest>=9.0.3",
]
[project.scripts]
ctags-ls = "ctags_ls:main"
[build-system]
requires = ["uv_build>=0.11.8,<0.12.0"]
build-backend = "uv_build"
+29
View File
@@ -0,0 +1,29 @@
from pygls.lsp.server import LanguageServer
from lsprotocol import types
from ctags_ls.tags import Tags
ls = LanguageServer("ctags-ls", "v0.1")
tags = Tags(ls)
@ls.feature(
types.TEXT_DOCUMENT_COMPLETION,
types.CompletionOptions(trigger_characters=["."])
)
def completions(params: types.CompletionParams):
items = [
types.CompletionItem(label=symbol, documentation=f'***{tag.type}*** **{tag.location}** {tag.file}')
for symbol, tags in tags.symbols.items()
for tag in tags
]
document = ls.workspace.get_text_document(params.text_document.uri)
current_line = document.lines[params.position.line][:params.position.character]
if current_line.endswith("hello."):
items = [
types.CompletionItem(label="world"),
types.CompletionItem(label="friend"),
]
return types.CompletionList(is_incomplete=False, items=items)
def main():
ls.start_io()
+69
View File
@@ -0,0 +1,69 @@
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
import os
from pathlib import Path
import re
from typing import final
TAGS_PATH = Path('tags')
@final
class Tags:
def __init__(self, tags_path: Path = TAGS_PATH) -> None:
self._last_mtime: float = 0.0
self._cache: dict[str, list[Tag]] = {}
self.TAGS_PATH = tags_path
@property
def symbols(self) -> Mapping[str, Sequence[Tag]]:
self._assure_valid_cache()
return self._cache
def _assure_valid_cache(self) -> None:
curr_mtime = os.path.getmtime(self.TAGS_PATH)
if curr_mtime > self._last_mtime:
self._update_cache()
self._last_mtime = curr_mtime
def _update_cache(self):
self._cache = {}
with self.TAGS_PATH.open('r') as f:
lines = (l for l in f.readlines() if not l.startswith('!_') and not l.startswith('__anon'))
for line in lines:
symbol, file, location, type, *_ = line.split('\t')
location = location.removesuffix(';"')
if is_int(location):
location = int(location)
else:
location = (
location
.removesuffix("/")
.removesuffix("$")
.removeprefix("/^")
.replace("\\\\", "\\")
.replace("\\/", "/")
)
location = re.compile(f'^{re.escape(location)}')
l = self._cache.get(symbol, [])
l.append(Tag(
symbol=symbol,
file=Path(file),
location=location,
type=type.strip()
))
self._cache[symbol] = l
def is_int(s: str) -> bool:
if not s:
return False
if s[0] in '+-':
return s[1:].isdecimal() and len(s) > 1
return s.isdecimal()
@dataclass(slots=True, frozen=True)
class Tag:
symbol: str
file: Path
location: re.Pattern[str] | int
type: str
View File
+20
View File
@@ -0,0 +1,20 @@
import re
from pathlib import Path
from ctags_ls.tags import Tag, Tags
import pytest
tags_path = Path(__file__).parent.resolve() / 'tags'
tags = (tag for tags in Tags(tags_path=tags_path).symbols.values() for tag in tags)
@pytest.mark.parametrize("tag", tags)
def test_tag(tag: Tag):
assert len(tag.type) == 1
assert tag.file.is_file()
text = tag.file.read_text()
if isinstance(tag.location, re.Pattern):
assert next(tag.location.finditer(text), None) is not None, f'{tag.location.pattern} has no matches in {tag.file}'
else:
assert text.count('\n') >= tag.location
assert tag.symbol in text.splitlines()[tag.location-1]
+137
View File
@@ -0,0 +1,137 @@
version = 1
revision = 3
requires-python = ">=3.14"
[[package]]
name = "attrs"
version = "26.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
]
[[package]]
name = "cattrs"
version = "26.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a0/ec/ba18945e7d6e55a58364d9fb2e46049c1c2998b3d805f19b703f14e81057/cattrs-26.1.0.tar.gz", hash = "sha256:fa239e0f0ec0715ba34852ce813986dfed1e12117e209b816ab87401271cdd40", size = 495672, upload-time = "2026-02-18T22:15:19.406Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/80/56/60547f7801b97c67e97491dc3d9ade9fbccbd0325058fd3dfcb2f5d98d90/cattrs-26.1.0-py3-none-any.whl", hash = "sha256:d1e0804c42639494d469d08d4f26d6b9de9b8ab26b446db7b5f8c2e97f7c3096", size = 73054, upload-time = "2026-02-18T22:15:17.958Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "ctags-ls"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "pygls" },
{ name = "pytest" },
]
[package.metadata]
requires-dist = [
{ name = "pygls", specifier = ">=2.1.1" },
{ name = "pytest", specifier = ">=9.0.3" },
]
[[package]]
name = "iniconfig"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
]
[[package]]
name = "lsprotocol"
version = "2025.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "cattrs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/26/67b84e6ec1402f0e6764ef3d2a0aaf9a79522cc1d37738f4e5bb0b21521a/lsprotocol-2025.0.0.tar.gz", hash = "sha256:e879da2b9301e82cfc3e60d805630487ac2f7ab17492f4f5ba5aaba94fe56c29", size = 74896, upload-time = "2025-06-17T21:30:18.156Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/f0/92f2d609d6642b5f30cb50a885d2bf1483301c69d5786286500d15651ef2/lsprotocol-2025.0.0-py3-none-any.whl", hash = "sha256:f9d78f25221f2a60eaa4a96d3b4ffae011b107537facee61d3da3313880995c7", size = 76250, upload-time = "2025-06-17T21:30:19.455Z" },
]
[[package]]
name = "packaging"
version = "26.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" },
]
[[package]]
name = "pluggy"
version = "1.6.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
]
[[package]]
name = "pygls"
version = "2.1.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "cattrs" },
{ name = "lsprotocol" },
]
sdist = { url = "https://files.pythonhosted.org/packages/da/2e/7bbe061d175c0baddde8fc9edb908a4c31ba5d9165b8c68e3439c3a9f138/pygls-2.1.1.tar.gz", hash = "sha256:1da03ba9053201bb337dcdd8d121df70feb2a91e1a0dcc74de5da79755b1a201", size = 55091, upload-time = "2026-03-25T11:19:10.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/1a/208293b6c350f5abea6941d5606080d4a492644052504f5312e5de30a902/pygls-2.1.1-py3-none-any.whl", hash = "sha256:510a6dea2476177230c7d851125e5948efdf3fdb9ebfd8543fc434972f8faed4", size = 68975, upload-time = "2026-03-25T11:19:11.374Z" },
]
[[package]]
name = "pygments"
version = "2.20.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
]
[[package]]
name = "pytest"
version = "9.0.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
{ name = "iniconfig" },
{ name = "packaging" },
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
+10 -2
View File
@@ -679,6 +679,16 @@ require'lazy'.setup{ --{{{1
end,
})
vim.lsp.config['ctags-ls'] = {
cmd={ 'ctags-ls' },
filetypes=nil,
root_dir=function(_, on_dir)
on_dir(vim.fn.getcwd())
end,
capabilities = vim.lsp.protocol.make_client_capabilities()
}
vim.lsp.enable('ctags-ls')
vim.keymap.set( 'n', ',fd', vim.lsp.buf.definition, { noremap = true, silent = true})
vim.cmd [[
@@ -808,7 +818,6 @@ require'lazy'.setup{ --{{{1
'hrsh7th/cmp-path',
'dcampos/nvim-snippy',
'dcampos/cmp-snippy',
'quangnguyen30192/cmp-nvim-tags',
},
config = function()
local cmp = require'cmp'
@@ -827,7 +836,6 @@ require'lazy'.setup{ --{{{1
{
{ name = 'snippy', priority = 100000000000000000000 },
{ name = 'nvim_lsp', priority = 1000000000},
{ name = 'tags', priority = 100 },
{ name = 'path', priority = 1},
}
),