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) else: tok = "" yield Diagnostic( Range( Position(row, col - len(tok)), Position(row, col + 1), ), 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()