mirror of
https://gitlab.gnome.org/jwestman/blueprint-compiler.git
synced 2025-05-04 15:59:08 -04:00
lsp: Show docs on hover
Also: - Install the script to <prefix>/bin - Fix a bug in XmlReader that would cause "uninteresting" tags to not be fully ignored - Other minor improvements
This commit is contained in:
parent
8fc0efb642
commit
55a117a5b7
7 changed files with 85 additions and 31 deletions
|
@ -77,7 +77,7 @@ class AstNode:
|
|||
for name, attr in self._attrs_by_type(Docs):
|
||||
if attr.token_name:
|
||||
token = self.group.tokens.get(attr.token_name)
|
||||
if token.start <= idx < token.end:
|
||||
if token and token.start <= idx < token.end:
|
||||
return getattr(self, name)
|
||||
else:
|
||||
return getattr(self, name)
|
||||
|
@ -180,6 +180,15 @@ class Template(AstNode):
|
|||
return self.root.gir.get_class(self.parent_class, self.parent_namespace)
|
||||
|
||||
|
||||
@docs("namespace")
|
||||
def namespace_docs(self):
|
||||
return self.root.gir.namespaces[self.namespace].doc
|
||||
|
||||
@docs("class_name")
|
||||
def class_docs(self):
|
||||
return self.gir_class.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
xml.start_tag("template", **{
|
||||
"class": self.name,
|
||||
|
@ -209,7 +218,7 @@ class Object(AstNode):
|
|||
def namespace_docs(self):
|
||||
return self.root.gir.namespaces[self.namespace].doc
|
||||
|
||||
@docs("namespace")
|
||||
@docs("class_name")
|
||||
def class_docs(self):
|
||||
return self.gir_class.doc
|
||||
|
||||
|
@ -293,7 +302,6 @@ class Property(AstNode):
|
|||
else:
|
||||
raise CompilerBugError()
|
||||
|
||||
|
||||
@validate("name")
|
||||
def property_exists(self):
|
||||
if self.gir_class is None:
|
||||
|
@ -313,6 +321,12 @@ class Property(AstNode):
|
|||
)
|
||||
|
||||
|
||||
@docs("name")
|
||||
def property_docs(self):
|
||||
if self.gir_property is not None:
|
||||
return self.gir_property.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
props = {
|
||||
"name": self.name,
|
||||
|
@ -360,7 +374,6 @@ class Signal(AstNode):
|
|||
else:
|
||||
raise CompilerBugError()
|
||||
|
||||
|
||||
@validate("name")
|
||||
def signal_exists(self):
|
||||
if self.gir_class is None:
|
||||
|
@ -374,12 +387,18 @@ class Signal(AstNode):
|
|||
return
|
||||
|
||||
if self.gir_signal is None:
|
||||
print(self.gir_class.signals.keys())
|
||||
raise CompileError(
|
||||
f"Class {self.gir_class.full_name} does not contain a signal called {self.name}",
|
||||
did_you_mean=(self.name, self.gir_class.signals.keys())
|
||||
)
|
||||
|
||||
|
||||
@docs("name")
|
||||
def signal_docs(self):
|
||||
if self.gir_signal is not None:
|
||||
return self.gir_signal.doc
|
||||
|
||||
|
||||
def emit_xml(self, xml: XmlEmitter):
|
||||
name = self.name
|
||||
if self.detail_name:
|
||||
|
|
|
@ -80,6 +80,11 @@ class Docs:
|
|||
self.func = func
|
||||
self.token_name = token_name
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
return self.func(instance)
|
||||
|
||||
|
||||
def docs(*args, **kwargs):
|
||||
""" Decorator for functions that return documentation for tokens. """
|
||||
|
|
|
@ -72,10 +72,10 @@ class GirNode:
|
|||
|
||||
@lazy_prop
|
||||
def doc(self) -> str:
|
||||
el = self.xml.find("doc")
|
||||
if el is None:
|
||||
el = self.xml.get_elements("doc")
|
||||
if len(el) != 1:
|
||||
return None
|
||||
return el.cdata
|
||||
return el[0].cdata.strip()
|
||||
|
||||
|
||||
class Property(GirNode):
|
||||
|
|
|
@ -22,7 +22,7 @@ import json, sys, traceback
|
|||
|
||||
from .errors import PrintableError, CompileError, MultipleErrors
|
||||
from .lsp_utils import *
|
||||
from . import tokenizer, parser, utils
|
||||
from . import tokenizer, parser, utils, xml_reader
|
||||
|
||||
|
||||
def command(json_method):
|
||||
|
@ -40,6 +40,10 @@ class LanguageServer:
|
|||
self._open_files: {str: OpenFile} = {}
|
||||
|
||||
def run(self):
|
||||
# Read <doc> tags from gir files. During normal compilation these are
|
||||
# ignored.
|
||||
xml_reader.PARSE_GIR.add("doc")
|
||||
|
||||
try:
|
||||
while True:
|
||||
line = ""
|
||||
|
@ -72,7 +76,10 @@ class LanguageServer:
|
|||
sys.stdout.flush()
|
||||
|
||||
def _log(self, msg):
|
||||
pass
|
||||
if self.logfile is not None:
|
||||
self.logfile.write(str(msg))
|
||||
self.logfile.write("\n")
|
||||
self.logfile.flush()
|
||||
|
||||
def _send_response(self, id, result):
|
||||
self._send({
|
||||
|
@ -94,8 +101,9 @@ class LanguageServer:
|
|||
"capabilities": {
|
||||
"textDocumentSync": {
|
||||
"openClose": True,
|
||||
"change": TextDocumentSyncKind.Incremental,
|
||||
}
|
||||
"change": 2, # incremental
|
||||
},
|
||||
"hoverProvider": True,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -112,19 +120,33 @@ class LanguageServer:
|
|||
|
||||
@command("textDocument/didChange")
|
||||
def didChange(self, id, params):
|
||||
open_file = self._open_files[params.textDocument.uri]
|
||||
|
||||
open_file.apply_changes(params.contentChanges)
|
||||
self._send_file_updates(open_file)
|
||||
if params is not None:
|
||||
open_file = self._open_files[params["textDocument"]["uri"]]
|
||||
open_file.apply_changes(params["contentChanges"])
|
||||
self._send_file_updates(open_file)
|
||||
|
||||
@command("textDocument/didClose")
|
||||
def didClose(self, id, params):
|
||||
del self._open_files[params.textDocument.uri]
|
||||
del self._open_files[params["textDocument"]["uri"]]
|
||||
|
||||
@command("textDocument/hover")
|
||||
def hover(self, id, params):
|
||||
open_file = self._open_files[params["textDocument"]["uri"]]
|
||||
docs = open_file.ast.get_docs(utils.pos_to_idx(params["position"]["line"], params["position"]["character"], open_file.text))
|
||||
if docs is not None:
|
||||
self._send_response(id, {
|
||||
"contents": {
|
||||
"kind": "markdown",
|
||||
"value": docs,
|
||||
}
|
||||
})
|
||||
else:
|
||||
self._send_response(id, None)
|
||||
|
||||
|
||||
def _send_file_updates(self, open_file: OpenFile):
|
||||
self._send_notification("textDocument/publishDiagnostics", {
|
||||
"uri": uri,
|
||||
"uri": open_file.uri,
|
||||
"diagnostics": [self._create_diagnostic(open_file.text, err) for err in open_file.diagnostics],
|
||||
})
|
||||
|
||||
|
|
|
@ -20,22 +20,17 @@
|
|||
|
||||
from enum import Enum
|
||||
|
||||
from . import tokenizer, parser
|
||||
from .errors import *
|
||||
from .utils import *
|
||||
|
||||
|
||||
class TextDocumentSyncKind(Enum):
|
||||
None_ = 0,
|
||||
Full = 1,
|
||||
Incremental = 2,
|
||||
|
||||
|
||||
class OpenFile:
|
||||
def __init__(self, uri, text, version):
|
||||
self.uri = uri
|
||||
self.text = text
|
||||
self.version = version
|
||||
|
||||
self.diagnostics = []
|
||||
self._update()
|
||||
|
||||
def apply_changes(self, changes):
|
||||
|
@ -50,8 +45,8 @@ class OpenFile:
|
|||
try:
|
||||
self.tokens = tokenizer.tokenize(self.text)
|
||||
self.ast = parser.parse(self.tokens)
|
||||
self.diagnostics = [self._create_diagnostic(text, err) for err in list(ast.errors)]
|
||||
self.diagnostics += self.ast.errors
|
||||
except MultipleErrors as e:
|
||||
self.diagnostics += [self._create_diagnostic(text, err) for err in e.errors]
|
||||
self.diagnostics += e.errors
|
||||
except CompileError as e:
|
||||
self.diagnostics += [self._create_diagnostic(text, e)]
|
||||
self.diagnostics += e
|
||||
|
|
|
@ -24,9 +24,10 @@ from xml import sax
|
|||
from .utils import lazy_prop
|
||||
|
||||
|
||||
# To speed up parsing, we ignore all tags except these
|
||||
PARSE_GIR = set([
|
||||
"repository", "namespace", "class", "interface", "property", "glib:signal",
|
||||
"include", "implements",
|
||||
"include", "implements"
|
||||
])
|
||||
|
||||
|
||||
|
@ -52,10 +53,13 @@ class Handler(sax.handler.ContentHandler):
|
|||
def __init__(self, parse_type):
|
||||
self.root = None
|
||||
self.stack = []
|
||||
self.skipping = 0
|
||||
self._interesting_elements = parse_type
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
if name not in self._interesting_elements:
|
||||
self.skipping += 1
|
||||
if self.skipping > 0:
|
||||
return
|
||||
|
||||
element = Element(name, attrs.copy())
|
||||
|
@ -70,11 +74,14 @@ class Handler(sax.handler.ContentHandler):
|
|||
|
||||
|
||||
def endElement(self, name):
|
||||
if name in self._interesting_elements:
|
||||
if self.skipping == 0:
|
||||
self.stack.pop()
|
||||
if name not in self._interesting_elements:
|
||||
self.skipping -= 1
|
||||
|
||||
def characters(self, content):
|
||||
self.stack[-1].cdata_chunks.append(content)
|
||||
if not self.skipping:
|
||||
self.stack[-1].cdata_chunks.append(content)
|
||||
|
||||
|
||||
def parse(filename, parse_type):
|
||||
|
|
|
@ -15,6 +15,12 @@ configure_file(
|
|||
install_dir: join_paths(libdir, 'pkgconfig'),
|
||||
)
|
||||
|
||||
install_data(
|
||||
'gtk-blueprint-tool.py',
|
||||
install_dir: get_option('bindir'),
|
||||
rename: 'gtk-blueprint-tool',
|
||||
)
|
||||
|
||||
meson.override_find_program('gtk-blueprint-tool', find_program('gtk-blueprint-tool.py'))
|
||||
|
||||
if not meson.is_subproject()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue