-
Notifications
You must be signed in to change notification settings - Fork 409
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
12b3d2a
commit f3e30f3
Showing
6 changed files
with
429 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# | ||
# This source file is part of the EdgeDB open source project. | ||
# | ||
# Copyright 2008-present MagicStack Inc. and the EdgeDB authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# | ||
# This source file is part of the EdgeDB open source project. | ||
# | ||
# Copyright 2008-present MagicStack Inc. and the EdgeDB authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from lsprotocol import types as lsp_types | ||
|
||
|
||
from edb.edgeql import parser as qlparser | ||
|
||
from . import parsing as ls_parsing | ||
from . import server as ls_server | ||
|
||
|
||
def main(): | ||
ls = ls_server.EdgeDBLanguageServer() | ||
|
||
@ls.feature( | ||
lsp_types.INITIALIZE, | ||
) | ||
def init(_params: lsp_types.InitializeParams): | ||
ls.show_message_log('Starting') | ||
qlparser.preload_spec() | ||
ls.show_message_log('Started') | ||
|
||
@ls.feature(lsp_types.TEXT_DOCUMENT_DID_OPEN) | ||
def text_document_did_open(params: lsp_types.DidOpenTextDocumentParams): | ||
document_updated(ls, params.text_document.uri) | ||
|
||
@ls.feature(lsp_types.TEXT_DOCUMENT_DID_CHANGE) | ||
def text_document_did_change(params: lsp_types.DidChangeTextDocumentParams): | ||
document_updated(ls, params.text_document.uri) | ||
|
||
@ls.feature( | ||
lsp_types.TEXT_DOCUMENT_COMPLETION, | ||
lsp_types.CompletionOptions(trigger_characters=[',']), | ||
) | ||
def completions(params: lsp_types.CompletionParams): | ||
items = [] | ||
|
||
document = ls.workspace.get_text_document(params.text_document.uri) | ||
|
||
if item := ls_parsing.parse_and_suggest(document, params.position): | ||
items.append(item) | ||
|
||
return lsp_types.CompletionList(is_incomplete=False, items=items) | ||
|
||
ls.start_io() | ||
|
||
|
||
def document_updated(ls: ls_server.EdgeDBLanguageServer, doc_uri: str): | ||
# each call to this function should yield in exactly one publish_diagnostics | ||
# for this document | ||
|
||
document = ls.workspace.get_text_document(doc_uri) | ||
ql_ast = ls_parsing.parse(document, ls) | ||
if diagnostics := ql_ast.error: | ||
ls.publish_diagnostics(document.uri, diagnostics, document.version) | ||
return | ||
assert ql_ast.ok | ||
|
||
try: | ||
if isinstance(ql_ast.ok, list): | ||
diagnostics = ls_server.compile(ls, ql_ast.ok) | ||
ls.publish_diagnostics(document.uri, diagnostics, document.version) | ||
else: | ||
ls.publish_diagnostics(document.uri, [], document.version) | ||
except BaseException as e: | ||
ls.show_message_log(f'Internal error: {e}') | ||
ls.publish_diagnostics(document.uri, [], document.version) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
# | ||
# This source file is part of the EdgeDB open source project. | ||
# | ||
# Copyright 2008-present MagicStack Inc. and the EdgeDB authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
from typing import Any, List, Tuple, Optional, TypeVar, Generic | ||
from dataclasses import dataclass | ||
|
||
from pygls.server import LanguageServer | ||
from pygls.workspace import TextDocument | ||
from lsprotocol import types as lsp_types | ||
|
||
|
||
from edb.edgeql import ast as qlast | ||
from edb.edgeql import tokenizer | ||
from edb.edgeql import parser as qlparser | ||
from edb.edgeql.parser.grammar import tokens as qltokens | ||
import edb._edgeql_parser as rust_parser | ||
|
||
|
||
T = TypeVar('T', covariant=True) | ||
E = TypeVar('E', covariant=True) | ||
|
||
|
||
@dataclass(kw_only=True, slots=True) | ||
class Result(Generic[T, E]): | ||
ok: Optional[T] = None | ||
error: Optional[E] = None | ||
|
||
|
||
def parse( | ||
doc: TextDocument, ls: LanguageServer | ||
) -> Result[List[qlast.Base] | qlast.Schema, List[lsp_types.Diagnostic]]: | ||
sdl = doc.filename.endswith('.esdl') if doc.filename else False | ||
|
||
source, result, productions = _parse_inner(doc.source, sdl) | ||
|
||
if result.errors: | ||
diagnostics = [] | ||
for error in result.errors: | ||
message, span, hint, details = error | ||
|
||
if details: | ||
message += f"\n{details}" | ||
if hint: | ||
message += f"\nHint: {hint}" | ||
(start, end) = tokenizer.inflate_span(source.text(), span) | ||
assert end | ||
|
||
diagnostics.append( | ||
lsp_types.Diagnostic( | ||
range=lsp_types.Range( | ||
start=lsp_types.Position( | ||
line=start.line - 1, | ||
character=start.column - 1, | ||
), | ||
end=lsp_types.Position( | ||
line=end.line - 1, | ||
character=end.column - 1, | ||
), | ||
), | ||
severity=lsp_types.DiagnosticSeverity.Error, | ||
message=message, | ||
) | ||
) | ||
|
||
return Result(error=diagnostics) | ||
|
||
# parsing successful | ||
assert isinstance(result.out, rust_parser.CSTNode) | ||
|
||
ast = qlparser._cst_to_ast( | ||
result.out, productions, source, doc.filename | ||
).val | ||
if sdl: | ||
assert isinstance(ast, qlast.Schema), ast | ||
else: | ||
assert isinstance(ast, list), ast | ||
return Result(ok=ast) | ||
|
||
|
||
def parse_and_suggest( | ||
doc: TextDocument, position: lsp_types.Position | ||
) -> Optional[lsp_types.CompletionItem]: | ||
sdl = doc.filename.endswith('.esdl') if doc.filename else False | ||
|
||
source, result, _productions = _parse_inner(doc.source, sdl) | ||
for error in result.errors: | ||
message: str | ||
message, span, _hint, _details = error | ||
if not message.startswith('Missing keyword '): | ||
continue | ||
(start, end) = tokenizer.inflate_span(source.text(), span) | ||
|
||
if not _position_in_span(position, (start, end)): | ||
continue | ||
|
||
keyword = message.removeprefix('Missing keyword \'')[:-1] | ||
|
||
return lsp_types.CompletionItem( | ||
label=keyword, | ||
kind=lsp_types.CompletionItemKind.Keyword, | ||
) | ||
return None | ||
|
||
|
||
def _position_in_span(pos: lsp_types.Position, span: Tuple[Any, Any]): | ||
start, end = span | ||
|
||
if pos.line < start.line - 1: | ||
return False | ||
if pos.line > end.line - 1: | ||
return False | ||
if pos.line == start.line - 1 and pos.character < start.column - 1: | ||
return False | ||
if pos.line == end.line - 1 and pos.character > end.column - 1: | ||
return False | ||
return True | ||
|
||
|
||
def _parse_inner( | ||
source_str: str, sdl: bool | ||
) -> Tuple[tokenizer.Source, rust_parser.ParserResult, Any]: | ||
try: | ||
source = tokenizer.Source.from_string(source_str) | ||
except Exception as e: | ||
# TODO | ||
print(e) | ||
raise AssertionError(e) | ||
|
||
start_t = qltokens.T_STARTSDLDOCUMENT if sdl else qltokens.T_STARTBLOCK | ||
start_t_name = start_t.__name__[2:] | ||
tokens = source.tokens() | ||
|
||
result, productions = rust_parser.parse(start_t_name, tokens) | ||
return source, result, productions |
Oops, something went wrong.