Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ doesn't support:
- [x] package names
- [ ] `build.sh`
- [x] `PKGBUILD`: by [pyalpm](https://github.com/ornitorrincos/pyalpm)
- [ ] `ebuild`
- [x] `ebuild`: by [portage](https://wiki.gentoo.org/wiki/Portage)
- [ ] [Code Action](https://microsoft.github.io/language-server-protocol/specifications/specification-current#textDocument_codeAction)
- [ ] `PKGBUILD`
- [ ] generate a template by the name of directory containing `PKGBUILD`, the
Expand Down Expand Up @@ -141,7 +141,7 @@ Other features:

## How Does It Work

See [here](https://github.com/neomutt/lsp-tree-sitter#usage).
See [lsp-tree-sitter documentation](https://github.com/neomutt/lsp-tree-sitter#usage).

Read
[![readthedocs](https://shields.io/readthedocs/termux-language-server)](https://termux-language-server.readthedocs.io)
Expand Down
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
https://www.sphinx-doc.org/en/master/usage/configuration.html
"""

from termux_language_server import __version__ as version # type: ignore
from termux_language_server._metainfo import ( # type: ignore
author,
copyright,
project,
)

from termux_language_server import __version__ as version # type: ignore

__all__ = ["version", "author", "copyright", "project"]

# -- Path setup --------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ file = "requirements/misc.txt"

[tool.setuptools.dynamic.optional-dependencies.pkgbuild]
file = "requirements/pkgbuild.txt"

[tool.setuptools.dynamic.optional-dependencies.ebuild]
file = "requirements/ebuild.txt"
# end: scripts/update-pyproject.toml.pl

[tool.setuptools_scm]
Expand Down
7 changes: 7 additions & 0 deletions src/termux_language_server/assets/jinja2/ebuild.md.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{{ pkg.description }}

- version: **{{ pkg.version }}**
- slot: **{{ pkg.slot }}**
- homepage: <{{ pkg.homepage }}>
- license: *{{ pkg.license }}*
- keywords: {{ pkg.keywords }}
2 changes: 1 addition & 1 deletion src/termux_language_server/assets/queries/package.scm
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
value: (array (word) @package)
)
(#match?
@variable.name "^(depends|optdepends|makedepends|conflicts|provides)$"
@variable.name "^(depends|optdepends|makedepends|conflicts|provides|DEPEND|RDEPEND|BDEPEND|IDEPEND|PDEPEND)$"
)
)
13 changes: 12 additions & 1 deletion src/termux_language_server/packages/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@
"conflicts",
"provides",
"replaces",
}
},
"ebuild": {
"DEPEND",
"RDEPEND",
"BDEPEND",
"IDEPEND",
"PDEPEND",
},
}


Expand All @@ -27,6 +34,8 @@ def search_package_document(name: str, filetype: FILETYPE) -> str:
"""
if filetype == "PKGBUILD":
from .pkgbuild import get_package_document
elif filetype == "ebuild":
from .ebuild import get_package_document
else:
raise NotImplementedError
return get_package_document(name)
Expand All @@ -43,6 +52,8 @@ def search_package_names(name: str, filetype: FILETYPE) -> dict[str, str]:
"""
if filetype == "PKGBUILD":
from .pkgbuild import get_package_names
elif filetype == "ebuild":
from .ebuild import get_package_names
else:
raise NotImplementedError
return get_package_names(name)
70 changes: 70 additions & 0 deletions src/termux_language_server/packages/ebuild.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
r"""Ebuild packages
==================
"""

from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
from types import SimpleNamespace

from jinja2 import Template
from platformdirs import user_config_path
from portage import db, root

PORTTREE = db[root]["porttree"].dbapi
_ALL_PACKAGES = PORTTREE.cp_all()
_EXECUTOR = ThreadPoolExecutor(max_workers=1)
TEMPLATE_NAME = "ebuild.md.j2"
PATH = user_config_path("portage") / TEMPLATE_NAME
if not PATH.exists():
PATH = Path(__file__).parent.parent / "assets" / "jinja2" / TEMPLATE_NAME
TEMPLATE = PATH.read_text()


def _render_document(cp: str, template: str = TEMPLATE) -> str:
r"""Render document.

:param cp:
:type cp: str
:param template:
:type template: str
:rtype: str
"""
versions = PORTTREE.cp_list(cp)
if not versions:
return ""
cpv = versions[-1]
description, homepage, license_, slot, keywords = PORTTREE.aux_get(
cpv, ["DESCRIPTION", "HOMEPAGE", "LICENSE", "SLOT", "KEYWORDS"]
)
version = cpv[len(cp) + 1 :]
pkg = SimpleNamespace(
description=description,
version=version,
slot=slot,
homepage=homepage,
license=license_,
keywords=keywords,
)
return Template(template).render(pkg=pkg)


def get_package_document(name: str, template: str = TEMPLATE) -> str:
r"""Get package document.

:param name:
:type name: str
:param template:
:type template: str
:rtype: str
"""
return _EXECUTOR.submit(_render_document, name, template).result()


def get_package_names(name: str) -> dict[str, str]:
r"""Get package names.

:param name:
:type name: str
:rtype: dict[str, str]
"""
return {cp: "" for cp in _ALL_PACKAGES if cp.startswith(name)}
112 changes: 92 additions & 20 deletions src/termux_language_server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
Hover,
MarkupContent,
MarkupKind,
Position,
PublishDiagnosticsParams,
Range,
TextDocumentPositionParams,
TextEdit,
)
Expand All @@ -47,6 +49,63 @@
)
from .utils import get_filetype, get_schema, parser

RE_PKG_START = re.compile(r"[A-Za-z_0-9/\-\.>=<!~*]*$")
RE_PKG_END = re.compile(r"^[A-Za-z_0-9/\-\.>=<!~*]*")
RE_EMPTY = re.compile(r"^")


def _is_in_dep_string(tree, position, filetype):
r"""Check if cursor is inside a dependency variable's string.

:param tree:
:param position:
:type position: Position
:param filetype:
:type filetype: str
:rtype: bool
"""
point = (position.line, position.character)
node = tree.root_node.descendant_for_point_range(point, point)
return (
node is not None
and node.type == "string"
and node.parent is not None
and node.parent.children[0].text is not None
and node.parent.children[0].text.decode()
in PACKAGE_VARIABLES.get(filetype, set())
)


def _package_completions(prefix, filetype, position):
r"""Build completion list for package names.

:param prefix:
:type prefix: str
:param filetype:
:type filetype: str
:param position:
:type position: Position
:rtype: CompletionList
"""
edit_range = Range(
start=Position(position.line, position.character - len(prefix)),
end=position,
)
return CompletionList(
False,
[
CompletionItem(
k,
kind=CompletionItemKind.Module,
documentation=MarkupContent(MarkupKind.Markdown, v)
if v
else None,
text_edit=TextEdit(range=edit_range, new_text=k),
)
for k, v in search_package_names(prefix, filetype).items()
],
)


class TermuxLanguageServer(LanguageServer):
r"""Termux language server."""
Expand Down Expand Up @@ -190,13 +249,20 @@ def hover(params: TextDocumentPositionParams) -> Hover | None:
}
):
if (
parent.type == "array"
parent.type in {"array", "string"}
and parent.parent is not None
and parent.parent.children[0].text is not None
and parent.parent.children[0].text.decode()
in PACKAGE_VARIABLES.get(filetype, set())
):
result = search_package_document(text, filetype)
result = search_package_document(
document.word_at_position(
params.position, RE_PKG_START, RE_PKG_END
)
if parent.type == "string"
else text,
filetype,
)
if result is None:
return None
return Hover(
Expand Down Expand Up @@ -239,34 +305,40 @@ def completions(params: CompletionParams) -> CompletionList:
uni = PositionFinder(params.position, right_equal=True).find(
document.uri, self.trees[document.uri]
)
if uni is None:
return CompletionList(False, [])
if uni is None or uni.node.type == '"':
if _is_in_dep_string(
self.trees[document.uri],
params.position,
filetype,
):
return _package_completions(
document.word_at_position(
params.position, RE_PKG_START, RE_EMPTY
),
filetype,
params.position,
)
if uni is None:
return CompletionList(False, [])
parent = uni.node.parent
if parent is None:
return CompletionList(False, [])
text = uni.text
if (
parent.type == "array"
parent.type in {"array", "string"}
and parent.parent is not None
and parent.parent.children[0].text is not None
and parent.parent.children[0].text.decode()
in PACKAGE_VARIABLES.get(filetype, set())
):
return CompletionList(
False,
[
CompletionItem(
k,
kind=CompletionItemKind.Module,
documentation=MarkupContent(
MarkupKind.Markdown, v
),
insert_text=k,
)
for k, v in search_package_names(
text, filetype
).items()
],
return _package_completions(
document.word_at_position(
params.position, RE_PKG_START, RE_EMPTY
)
if parent.type == "string"
else text,
filetype,
params.position,
)
schema = get_schema(filetype)
if (
Expand Down
1 change: 1 addition & 0 deletions tests/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os

from lsp_tree_sitter.finders import SchemaFinder

from termux_language_server.schema import BashTrie
from termux_language_server.utils import get_filetype, get_schema, parser

Expand Down