139 lines
4.3 KiB
Python
139 lines
4.3 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)
|
|
else:
|
|
tok = "<token>"
|
|
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()
|