-
Notifications
You must be signed in to change notification settings - Fork 60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP / DO NOT MERGE] Initial LSP implementation #1573
base: main
Are you sure you want to change the base?
Changes from all commits
31adce8
bfccf3d
e751e01
3ac753a
40ca4b3
49d9ec8
467992d
91002ed
4b090ed
8b05531
e3ce127
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,53 @@ | ||||||
# Copyright (c) 2024 Snowflake Inc. | ||||||
# | ||||||
# 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 __future__ import annotations | ||||||
|
||||||
from pygls.server import LanguageServer | ||||||
from snowflake.cli.api.cli_global_context import get_cli_context | ||||||
from snowflake.cli.api.output.types import MessageResult | ||||||
from snowflake.cli.api.project.definition_manager import DefinitionManager | ||||||
from snowflake.cli.plugins.lsp.server import lsp_plugin | ||||||
from snowflake.cli.plugins.nativeapp.manager import NativeAppManagern | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. broken import, typo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
|
||||||
@lsp_plugin( | ||||||
name="nativeapp", | ||||||
capabilities={ | ||||||
"openApplication": True, | ||||||
}, | ||||||
) | ||||||
def nade_lsp_plugin(server: LanguageServer): | ||||||
# FIXME: can't parametrize iter_lsp_plugins() if this is top-level ? | ||||||
from snowflake.cli.plugins.lsp.interface import workspace_command | ||||||
|
||||||
@workspace_command(server, "openApplication") | ||||||
def open_app() -> MessageResult: | ||||||
|
||||||
ctx = get_cli_context() | ||||||
|
||||||
dm = DefinitionManager(ctx.project_root) | ||||||
project_definition = getattr(dm.project_definition, "native_app", None) | ||||||
project_root = dm.project_root | ||||||
manager = NativeAppManager( | ||||||
project_definition=project_definition, | ||||||
project_root=project_root, | ||||||
connection=ctx.connection, | ||||||
) | ||||||
if manager.get_existing_app_info(): | ||||||
url = manager.get_snowsight_url() | ||||||
return MessageResult(f"{url}") | ||||||
else: | ||||||
return MessageResult( | ||||||
'Snowflake Native App not yet deployed! Please run "runApplication" first.' | ||||||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Copyright (c) 2024 Snowflake Inc. | ||
# | ||
# 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. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Copyright (c) 2024 Snowflake Inc. | ||
# | ||
# 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 __future__ import annotations | ||
|
||
import logging | ||
|
||
from snowflake.cli.api.commands.snow_typer import SnowTyper | ||
from snowflake.cli.api.output.types import CommandResult, MessageResult | ||
from snowflake.cli.plugins.lsp.server import ( | ||
start_lsp_server, | ||
) | ||
|
||
app = SnowTyper(name="lsp", help="Manages a Snowflake LSP server.", hidden=True) | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
@app.command("start") | ||
def lsp_start( | ||
**options, | ||
) -> CommandResult: | ||
""" | ||
Starts the LSP language server in the foreground. | ||
""" | ||
start_lsp_server() | ||
return MessageResult(f"LSP server process ended.") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
# Copyright (c) 2024 Snowflake Inc. | ||
# | ||
# 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. | ||
|
||
import inspect | ||
from typing import Any, Callable, Dict, List, Optional, Union, get_type_hints, is_typeddict | ||
from pydantic import TypeAdapter, ValidationError | ||
from typing_extensions import TypedDict, NotRequired | ||
from snowflake.cli.api.cli_global_context import CliContextArguments, fork_cli_context | ||
|
||
from pygls.server import LanguageServer | ||
|
||
|
||
ORIGINAL_FUNCTION_KEY = "__lsp_original_function__" | ||
|
||
|
||
TypeDef = Dict[str, Union[str, 'TypeDef']] | ||
|
||
class CommandArguments(CliContextArguments): | ||
""" | ||
The arguments that can be passed to a workspace command. | ||
|
||
""" | ||
args: NotRequired[List[Any]] | ||
kwargs: NotRequired[Dict[str, Any]] | ||
|
||
|
||
def workspace_command( | ||
server: LanguageServer, | ||
name: str, | ||
requires_connection: bool = False, | ||
requires_project: bool = False, | ||
): | ||
""" | ||
Wrap a function with pygls' @server.command. | ||
Ensures that the command invocation provides a valid connection / project context | ||
(if required) as well as ensuring arguments are in the required format. | ||
""" | ||
def _decorator(func): | ||
@server.command(name) | ||
def wrapper(params: List[CommandArguments]): | ||
if len(params) > 1: | ||
raise ValueError("Expected exactly one CommandArguments object") | ||
|
||
try: | ||
args = TypeAdapter(CommandArguments).validate_python(params[0] if len(params) == 1 else {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would explicitly handle unexpected args, at least I would log the event, silently continuing could make it very hard to debug something unexpected |
||
|
||
if requires_connection and "connection" not in args: | ||
raise ValueError("connection missing, but requires_connection=True") | ||
|
||
if requires_project and "project_path" not in args: | ||
raise ValueError("project_path missing, but requires_connection=True") | ||
|
||
# TODO: validation of args.args / args.kwargs based on shape of actual command... | ||
|
||
with fork_cli_context(**args): | ||
return func(*args.get("args", []), **args.get("kwargs", {})) | ||
|
||
except ValidationError as exc: | ||
raise ValueError(f"ERROR: Invalid schema: {exc}") | ||
|
||
|
||
setattr(wrapper, ORIGINAL_FUNCTION_KEY, func) | ||
return wrapper | ||
|
||
return _decorator |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Copyright (c) 2024 Snowflake Inc. | ||
# | ||
# 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 snowflake.cli.api.plugins.command import ( | ||
SNOWCLI_ROOT_COMMAND_PATH, | ||
CommandSpec, | ||
CommandType, | ||
plugin_hook_impl, | ||
) | ||
from snowflake.cli.plugins.lsp import commands | ||
|
||
|
||
@plugin_hook_impl | ||
def command_spec(): | ||
return CommandSpec( | ||
parent_command_path=SNOWCLI_ROOT_COMMAND_PATH, | ||
command_type=CommandType.COMMAND_GROUP, | ||
typer_instance=commands.app, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
broken import,
lsp_plugin
does not exist