skill-ls/skillls/main.py

144 lines
4.5 KiB
Python

from collections.abc import Generator
from logging import INFO, basicConfig, getLogger
from re import fullmatch
import re
from time import time
from lsprotocol.types import (
TEXT_DOCUMENT_DID_CHANGE,
TEXT_DOCUMENT_DID_OPEN,
TEXT_DOCUMENT_DID_SAVE,
TEXT_DOCUMENT_DOCUMENT_SYMBOL,
TEXT_DOCUMENT_HOVER,
TEXT_DOCUMENT_INLAY_HINT,
CompletionItem,
Diagnostic,
DiagnosticSeverity,
DidChangeTextDocumentParams,
DidOpenTextDocumentParams,
DidSaveTextDocumentParams,
DocumentSymbol,
DocumentSymbolParams,
Hover,
HoverParams,
InlayHint,
InlayHintParams,
MessageType,
Position,
Range,
)
from pygls.server import LanguageServer
from pygls.workspace import TextDocument
from skillls.parsing.iterative import IterativeParser, TokenParser
from .cache import Cache
URI = str
basicConfig(filename="skillls.log", level=INFO)
cache: Cache[str, CompletionItem] = Cache()
logger = getLogger(__name__)
class SkillLanguageServer(LanguageServer):
def _diagnose_parens(self, doc: TextDocument) -> Generator[Diagnostic, None, None]:
open: list[tuple[int, int]] = []
in_str: bool = False
last = ""
for row, line in enumerate(doc.lines):
for col, char in enumerate(line):
match char:
case "(":
if not in_str:
open.append((row, col))
case ")":
if not in_str:
if len(open) > 0:
open.pop()
else:
yield (
Diagnostic(
Range(
Position(row, col),
Position(row, col),
),
"unopened ) encountered",
)
)
case '"':
if not (in_str and last == "\\"):
in_str = not in_str
case _:
last = char
last = char
if len(open) > 0:
for row, col in open:
yield (
Diagnostic(
Range(Position(row, col), Position(row, col)),
"unclosed ) encountered",
)
)
def _diagnose_cisms(self, doc: TextDocument) -> Generator[Diagnostic, None, None]:
for row, line in enumerate(doc.lines):
for col, char in enumerate(line):
if col > 0:
if fullmatch("\\w", line[col - 1]) and char == "(":
if m := re.match(r"([a-zA-Z_][a-zA-Z_0-9]*)$", line[:col]):
tok = m.group(1)
r = Range(
Position(row, col - len(tok)),
Position(row, col + 1),
)
else:
tok = "<token>"
r = Range(
Position(row, col - 1),
Position(row, col + 1),
)
yield Diagnostic(
r,
f"change `{tok}(` to `( {tok}` [cism]",
DiagnosticSeverity.Hint,
)
def diagnose(self, doc: TextDocument) -> None:
diags: list[Diagnostic] = []
diags.extend(self._diagnose_parens(doc))
diags.extend(self._diagnose_cisms(doc))
self.publish_diagnostics(doc.uri, diags)
server = SkillLanguageServer("skillls", "v0.2")
@server.feature(TEXT_DOCUMENT_DID_OPEN)
def on_open(ls: SkillLanguageServer, params: DidOpenTextDocumentParams) -> None:
doc = server.workspace.get_text_document(params.text_document.uri)
ls.diagnose(doc)
@server.feature(TEXT_DOCUMENT_DID_CHANGE)
def on_save(ls: SkillLanguageServer, params: DidChangeTextDocumentParams) -> None:
doc = server.workspace.get_text_document(params.text_document.uri)
ls.diagnose(doc)
@server.feature(TEXT_DOCUMENT_INLAY_HINT)
def inlay_hints(ls: SkillLanguageServer, params: InlayHintParams) -> list[InlayHint]:
hints: list[InlayHint] = []
return hints
def main():
server.start_io()