Skip to content

Commit

Permalink
✨ Add SchemaFinder
Browse files Browse the repository at this point in the history
  • Loading branch information
Freed-Wu committed Feb 20, 2024
1 parent dcd39ec commit b2bbcf9
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 21 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ A language server for [tmux](https://github.com/tmux/tmux)'s tmux.conf.
- [x] [Hover](https://microsoft.github.io/language-server-protocol/specifications/specification-current#textDocument_hover)
- [x] [Completion](https://microsoft.github.io/language-server-protocol/specifications/specification-current#textDocument_completion)

![Diagnostic](https://github.com/Freed-Wu/tmux-language-server/assets/32936898/a92a7d41-4ade-486b-98ef-14382d6d4722)

![Document hover](https://github.com/Freed-Wu/tmux-language-server/assets/32936898/631db877-4cde-4b87-9548-c0a66335a83d)

![Completion](https://github.com/Freed-Wu/tmux-language-server/assets/32936898/a9793a05-7da6-4fcb-88bf-4ca82ccfbfc1)
Expand Down
15 changes: 14 additions & 1 deletion src/tmux_language_server/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ def get_parser():
default="auto",
help="when to display color, default: %(default)s",
)
parser.add_argument(
"--convert",
nargs="*",
default={},
help="convert files to output format",
)
parser.add_argument(
"--output-format",
choices=["json", "yaml", "toml"],
Expand All @@ -68,12 +74,13 @@ def main():
r"""Parse arguments and provide shell completions."""
args = get_parser().parse_args()

if args.generate_schema or args.check:
if args.generate_schema or args.check or args.convert:
from tree_sitter_lsp.diagnose import check
from tree_sitter_lsp.utils import pprint
from tree_sitter_tmux import parser

from .finders import DIAGNOSTICS_FINDER_CLASSES
from .schema import TmuxTrie

if args.generate_schema:
from .misc import get_schema
Expand All @@ -84,6 +91,12 @@ def main():
indent=args.indent,
)
return None
for file in args.convert:
pprint(
TmuxTrie.from_file(file, parser.parse).to_json(),
filetype=args.output_format,
indent=args.indent,
)
exit(
check(
args.check,
Expand Down
23 changes: 14 additions & 9 deletions src/tmux_language_server/assets/json/tmux.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@
"$comment": "Don't edit this file directly! It is generated by `tmux-language-server --generate-schema=tmux`.",
"type": "object",
"properties": {
"patternProperties": {
"@[-_\\da-zA-Z]": {
"type": "string"
}
},
"attach-session": {
"description": "```tmux\nattach-session [-dErx] [-c working-directory] [-f flags] [-t target-session]\nattach [-dErx] [-c working-directory] [-f flags] [-t target-session]\n```\n\nIf run from outside tmux, create a new client in the current terminal and attach it to target-session. If used from inside, switch the current client. If -d is specified, any other clients attached to the session are detached. If -x is given, send SIGHUP to the parent process of the client as well as detaching the client, typically causing it to exit. -f sets a comma-separated list of client flags. The flags are:"
},
Expand Down Expand Up @@ -91,7 +86,12 @@
"description": "```tmux\nshow-messages [-JT] [-t target-client]\nshowmsgs [-JT] [-t target-client]\n```\n\nShow server messages or information. Messages are stored, up to a maximum of the limit set by the message-limit server option. -J and -T show debugging information about jobs and terminals."
},
"source-file": {
"description": "```tmux\nsource-file [-Fnqv] path ...\nsource [-Fnqv] path ...\n```\n\nExecute commands from one or more files specified by path (which may be glob(7) patterns). If -F is present, then path is expanded as a format. If -q is given, no error will be returned if path does not exist. With -n, the file is parsed but no commands are executed. -v shows the parsed commands and line numbers if possible."
"description": "```tmux\nsource-file [-Fnqv] path ...\nsource [-Fnqv] path ...\n```\n\nExecute commands from one or more files specified by path (which may be glob(7) patterns). If -F is present, then path is expanded as a format. If -q is given, no error will be returned if path does not exist. With -n, the file is parsed but no commands are executed. -v shows the parsed commands and line numbers if possible.",
"type": "array",
"uniqueItems": true,
"items": {
"type": "string"
}
},
"source": {
"description": "```tmux\nsource-file [-Fnqv] path ...\nsource [-Fnqv] path ...\n```\n\nExecute commands from one or more files specified by path (which may be glob(7) patterns). If -F is present, then path is expanded as a format. If -q is given, no error will be returned if path does not exist. With -n, the file is parsed but no commands are executed. -v shows the parsed commands and line numbers if possible."
Expand Down Expand Up @@ -367,9 +367,6 @@
"description": "```tmux\nunbind-key [-anq] [-T key-table] key\nunbind [-anq] [-T key-table] key\n```\n\nUnbind the command bound to key. -n and -T are the same as for bind-key. If -a is present, all key bindings are removed. The -q option prevents errors being returned."
},
"set-option": {
"description": "```tmux\nset-option [-aFgopqsuUw] [-t target-pane] option value\nset [-aFgopqsuUw] [-t target-pane] option value\n```\n\nSet a pane option with -p, a window option with -w, a server option with -s, otherwise a session option. If the option is not a user option, -w or -s may be unnecessary - tmux will infer the type from the option name, assuming -w for pane options. If -g is given, the global session or window option is set."
},
"set": {
"description": "```tmux\nset-option [-aFgopqsuUw] [-t target-pane] option value\nset [-aFgopqsuUw] [-t target-pane] option value\n```\n\nSet a pane option with -p, a window option with -w, a server option with -s, otherwise a session option. If the option is not a user option, -w or -s may be unnecessary - tmux will infer the type from the option name, assuming -w for pane options. If -g is given, the global session or window option is set.",
"properties": {
"backspace": {
Expand Down Expand Up @@ -991,8 +988,16 @@
"description": "```tmux\nset window-style style\n```\n\nSet the pane style. For how to specify style, see the \u201cSTYLES\u201d section.",
"type": "string"
}
},
"patternProperties": {
"@[-_\\da-zA-Z]": {
"type": "string"
}
}
},
"set": {
"description": "```tmux\nset-option [-aFgopqsuUw] [-t target-pane] option value\nset [-aFgopqsuUw] [-t target-pane] option value\n```\n\nSet a pane option with -p, a window option with -w, a server option with -s, otherwise a session option. If the option is not a user option, -w or -s may be unnecessary - tmux will infer the type from the option name, assuming -w for pane options. If -g is given, the global session or window option is set."
},
"show-options": {
"description": "```tmux\nshow-options [-AgHpqsvw] [-t target-pane] [option]\nshow [-AgHpqsvw] [-t target-pane] [option]\n```\n\nShow the pane options (or a single option if option is provided) with -p, the window options with -w, the server options with -s, otherwise the session options. If the option is not a user option, -w or -s may be unnecessary - tmux will infer the type from the option name, assuming -w for pane options. Global session or window options are listed if -g is used. -v shows only the option value, not the name. If -q is set, no error will be returned if option is unset. -H includes hooks (omitted by default). -A includes options inherited from a parent set of options, such options are marked with an asterisk."
},
Expand Down
19 changes: 17 additions & 2 deletions src/tmux_language_server/finders.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from dataclasses import dataclass

from lsprotocol.types import DiagnosticSeverity
from tree_sitter_lsp.finders import ErrorFinder, QueryFinder
from tree_sitter_lsp.finders import ErrorFinder, QueryFinder, SchemaFinder

from .utils import get_query
from .schema import TmuxTrie
from .utils import get_query, get_schema


@dataclass(init=False)
Expand All @@ -30,6 +31,20 @@ def __init__(
super().__init__(query, message, severity)


@dataclass(init=False)
class TmuxFinder(SchemaFinder):
r"""Tmuxfinder."""

def __init__(self) -> None:
r"""Init.
:rtype: None
"""
self.validator = self.schema2validator(get_schema())
self.cls = TmuxTrie


DIAGNOSTICS_FINDER_CLASSES = [
ErrorFinder,
TmuxFinder,
]
20 changes: 11 additions & 9 deletions src/tmux_language_server/misc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ def get_schema() -> dict[str, Any]:
f"`{project} --generate-schema={filetype}`."
),
"type": "object",
"properties": {
"patternProperties": {"@[-_\\da-zA-Z]": {"type": "string"}}
},
"properties": {},
}
soup = get_soup("tmux.1", "groff", "mdoc")
p = soup.find("p", string="CLIENTS AND SESSIONS")
Expand Down Expand Up @@ -67,7 +65,7 @@ def get_schema() -> dict[str, Any]:
continue
if name == "backspace":
isoption = 1
schema["properties"]["set"]["properties"] = {}
schema["properties"]["set-option"]["properties"] = {}
s = ""
enum = []
if isoption:
Expand All @@ -86,14 +84,14 @@ def get_schema() -> dict[str, Any]:
description += "\n" + p.text.replace("\n", " ")
description = description.replace("\u2212", "-")
if isoption:
schema["properties"]["set"]["properties"][name] = {
schema["properties"]["set-option"]["properties"][name] = {
"description": description,
"type": _type,
}
if len(enum) > 1:
schema["properties"]["set"]["properties"][name]["enum"] = (
enum
)
schema["properties"]["set-option"]["properties"][name][
"enum"
] = enum
if s in {
"number",
"height",
Expand All @@ -102,7 +100,7 @@ def get_schema() -> dict[str, Any]:
"time",
"lines",
}:
schema["properties"]["set"]["properties"][name][
schema["properties"]["set-option"]["properties"][name][
"pattern"
] = "\\d+"
else:
Expand All @@ -112,4 +110,8 @@ def get_schema() -> dict[str, Any]:
if name == "window-style":
isoption = 0
p = p.find_next("p")
data = {"type": "array", "uniqueItems": True, "items": {"type": "string"}}
schema["properties"]["source-file"] |= data
data = {"patternProperties": {"@[-_\\da-zA-Z]": {"type": "string"}}}
schema["properties"]["set-option"] |= data
return schema
73 changes: 73 additions & 0 deletions src/tmux_language_server/schema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
r"""Schema
==========
"""

from dataclasses import dataclass

from lsprotocol.types import Position, Range
from tree_sitter import Node
from tree_sitter_lsp import UNI
from tree_sitter_lsp.schema import Trie

DIRECTIVES = {
"set_option_directive",
"source_file_directive",
}


@dataclass
class TmuxTrie(Trie):
r"""Tmux Trie."""

value: dict[str, "Trie"] | list["Trie"] | str # type: ignore

@classmethod
def from_node(cls, node: Node, parent: "Trie | None") -> "Trie":
r"""From node.
:param node:
:type node: Node
:param parent:
:type parent: Trie | None
:rtype: "Trie"
"""
if node.type == "value":
return cls(
UNI.node2range(node), parent, UNI.node2text(node).strip("'\"")
)
if node.type == "file":
trie = cls(Range(Position(0, 0), Position(1, 0)), parent, {})
for child in node.children:
if child.type not in DIRECTIVES:
continue
# directive name
_type = child.type.split("_directive")[0].replace("_", "-")
# add directive name to trie.value if it doesn't exist
_value: dict[str, Trie] = trie.value # type: ignore
if _type not in _value:
trie.value[_type] = cls( # type: ignore
UNI.node2range(child),
trie,
{} if _type != "source-file" else [],
)
# the dictionary's key corresponding to directive name
subtrie: Trie = trie.value[_type] # type: ignore
# currently, only support set and source
# set is a dict, source is a list
value = subtrie.value # type: ignore
# fill subtrie.value
if child.type == "set_option_directive":
value: dict[str, Trie]
value[UNI.node2text(child.children[-2])] = cls.from_node(
child.children[-1], subtrie
)
elif child.type == "source_file_directive":
value += [ # type: ignore
cls(
UNI.node2range(child.children[1]),
subtrie,
UNI.node2text(child.children[1]),
)
]
return trie
raise NotImplementedError(node.type)
2 changes: 2 additions & 0 deletions tests/tmux.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
set -g pane-base-index a
set -g set-titles not-on

0 comments on commit b2bbcf9

Please sign in to comment.