Adjust lib/ide_service
This commit is contained in:
parent
b87414e38d
commit
dabd12f5cf
7
lib/ide_service/__init__.py
Normal file
7
lib/ide_service/__init__.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from .service import IDEService
|
||||||
|
from .types import * # noqa: F403
|
||||||
|
from .types import __all__ as types_all
|
||||||
|
|
||||||
|
__all__ = types_all + [
|
||||||
|
"IDEService",
|
||||||
|
]
|
15
lib/ide_service/idea_service.py
Normal file
15
lib/ide_service/idea_service.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
from .rpc import rpc_method
|
||||||
|
from .types import LocationWithText
|
||||||
|
|
||||||
|
|
||||||
|
class IdeaIDEService:
|
||||||
|
def __init__(self):
|
||||||
|
self._result = None
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def get_visible_range(self) -> LocationWithText:
|
||||||
|
return LocationWithText.parse_obj(self._result)
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def get_selected_range(self) -> LocationWithText:
|
||||||
|
return LocationWithText.parse_obj(self._result)
|
76
lib/ide_service/rpc.py
Normal file
76
lib/ide_service/rpc.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import os
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
BASE_SERVER_URL = os.environ.get("DEVCHAT_IDE_SERVICE_URL", "http://localhost:3000")
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_call(f):
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if os.environ.get("DEVCHAT_IDE_SERVICE_URL", "") == "":
|
||||||
|
# maybe in a test, user don't want to mock services functions
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
function_name = f.__name__
|
||||||
|
url = f"{BASE_SERVER_URL}/{function_name}"
|
||||||
|
|
||||||
|
data = dict(zip(f.__code__.co_varnames, args))
|
||||||
|
data.update(kwargs)
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Server error: {response.status_code}")
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
if "error" in response_data:
|
||||||
|
raise Exception(f"Server returned an error: {response_data['error']}")
|
||||||
|
return response_data.get("result", None)
|
||||||
|
except ConnectionError as err:
|
||||||
|
# TODO
|
||||||
|
raise err
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def rpc_method(f):
|
||||||
|
"""
|
||||||
|
Decorator for Service methods
|
||||||
|
"""
|
||||||
|
|
||||||
|
@wraps(f)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
if os.environ.get("DEVCHAT_IDE_SERVICE_URL", "") == "":
|
||||||
|
# maybe in a test, user don't want to mock services functions
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
function_name = f.__name__
|
||||||
|
url = f"{BASE_SERVER_URL}/{function_name}"
|
||||||
|
|
||||||
|
data = dict(zip(f.__code__.co_varnames[1:], args)) # Exclude "self"
|
||||||
|
data.update(kwargs)
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise Exception(f"Server error: {response.status_code}")
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
if "error" in response_data:
|
||||||
|
raise Exception(f"Server returned an error: {response_data['error']}")
|
||||||
|
|
||||||
|
# Store the result in the _result attribute of the instance
|
||||||
|
self._result = response_data.get("result", None)
|
||||||
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
|
except ConnectionError as err:
|
||||||
|
# TODO
|
||||||
|
raise err
|
||||||
|
|
||||||
|
return wrapper
|
154
lib/ide_service/service.py
Normal file
154
lib/ide_service/service.py
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
from .idea_service import IdeaIDEService
|
||||||
|
from .rpc import rpc_method
|
||||||
|
from .types import Location, LocationWithText, SymbolNode
|
||||||
|
from .vscode_service import selected_range, visible_range
|
||||||
|
|
||||||
|
|
||||||
|
class IDEService:
|
||||||
|
"""
|
||||||
|
Client for IDE service
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
client = IDEService()
|
||||||
|
res = client.ide_language()
|
||||||
|
res = client.ide_logging("info", "some message")
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._result = None
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def get_lsp_brige_port(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the LSP bridge port.
|
||||||
|
|
||||||
|
:return: str
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def install_python_env(self, command_name: str, requirements_file: str) -> str:
|
||||||
|
"""
|
||||||
|
A method to install a Python environment with the provided command name
|
||||||
|
and requirements file, returning python path installed.
|
||||||
|
Command name is the name of the environment to be installed.
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def update_slash_commands(self) -> bool:
|
||||||
|
"""
|
||||||
|
Update the slash commands and return a boolean indicating the success of the operation.
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def ide_language(self) -> str:
|
||||||
|
"""
|
||||||
|
Returns the current IDE language setting for the user.
|
||||||
|
- zh: Chinese
|
||||||
|
- en: English
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def ide_logging(self, level: str, message: str) -> bool:
|
||||||
|
"""
|
||||||
|
Logs a message to the IDE.
|
||||||
|
level: "info" | "warn" | "error" | "debug"
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def get_document_symbols(self, abspath: str) -> List[SymbolNode]:
|
||||||
|
"""
|
||||||
|
Retrieves the document symbols for a given file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
abspath: The absolute path to the file whose symbols are to be retrieved.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of SymbolNode objects representing the symbols found in the document.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return [SymbolNode.parse_obj(node) for node in self._result]
|
||||||
|
except Exception:
|
||||||
|
# TODO: logging ide service error
|
||||||
|
return []
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def find_type_def_locations(self, abspath: str, line: int, character: int) -> List[Location]:
|
||||||
|
"""
|
||||||
|
Finds the location of type definitions within a file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
abspath: The absolute path to the file to be searched.
|
||||||
|
line: The line number within the file to begin the search.
|
||||||
|
character: The character position within the line to begin the search.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of Location objects representing the locations of type definitions found.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return [Location.parse_obj(loc) for loc in self._result]
|
||||||
|
except Exception:
|
||||||
|
# TODO: logging ide service error
|
||||||
|
return []
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def find_def_locations(self, abspath: str, line: int, character: int) -> List[Location]:
|
||||||
|
try:
|
||||||
|
return [Location.parse_obj(loc) for loc in self._result]
|
||||||
|
except Exception:
|
||||||
|
# TODO: logging ide service error
|
||||||
|
return []
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def ide_name(self) -> str:
|
||||||
|
"""Returns the name of the IDE.
|
||||||
|
|
||||||
|
This method is a remote procedure call (RPC) that fetches the name of the IDE being used.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The name of the IDE as a string. For example, "vscode" or "pycharm".
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
@rpc_method
|
||||||
|
def diff_apply(self, filepath, content) -> bool:
|
||||||
|
"""
|
||||||
|
Applies a given diff to a file.
|
||||||
|
|
||||||
|
This method uses the content provided to apply changes to the file
|
||||||
|
specified by the filepath. It's an RPC method that achieves file synchronization
|
||||||
|
by updating the local version of the file with the changes described in the
|
||||||
|
content parameter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filepath: The path to the file that needs to be updated.
|
||||||
|
content: A string containing the new code that should be applied to the file.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A boolean indicating if the diff was successfully applied.
|
||||||
|
"""
|
||||||
|
return self._result
|
||||||
|
|
||||||
|
def get_visible_range(self) -> LocationWithText:
|
||||||
|
"""
|
||||||
|
Determines and returns the visible range of code in the current IDE.
|
||||||
|
"""
|
||||||
|
# TODO: Implement vscode endpoint following the protocol in stead of using python wrapper
|
||||||
|
if self.ide_name() == "vscode":
|
||||||
|
return visible_range()
|
||||||
|
return IdeaIDEService().get_visible_range()
|
||||||
|
|
||||||
|
def get_selected_range(self) -> LocationWithText:
|
||||||
|
"""
|
||||||
|
Retrieves the selected range of code in the current IDE.
|
||||||
|
"""
|
||||||
|
# TODO: Implement vscode endpoint following the protocol in stead of using python wrapper
|
||||||
|
if self.ide_name() == "vscode":
|
||||||
|
return selected_range()
|
||||||
|
return IdeaIDEService().get_selected_range()
|
@ -7,6 +7,7 @@ __all__ = [
|
|||||||
"Range",
|
"Range",
|
||||||
"Location",
|
"Location",
|
||||||
"SymbolNode",
|
"SymbolNode",
|
||||||
|
"LocationWithText",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -48,3 +49,15 @@ class SymbolNode(BaseModel):
|
|||||||
kind: str
|
kind: str
|
||||||
range: Range
|
range: Range
|
||||||
children: List["SymbolNode"]
|
children: List["SymbolNode"]
|
||||||
|
|
||||||
|
|
||||||
|
class LocationWithText(BaseModel):
|
||||||
|
abspath: str
|
||||||
|
range: Range
|
||||||
|
text: str
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"{self.abspath}::{self.range}::{self.text}"
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.__repr__())
|
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
|
|
||||||
from .rpc import rpc_call
|
from .rpc import rpc_call
|
||||||
|
from .types import LocationWithText
|
||||||
|
|
||||||
|
|
||||||
@rpc_call
|
@rpc_call
|
||||||
@ -99,18 +99,36 @@ def visible_lines():
|
|||||||
end_line = active_document["visibleRanges"][0][1]["line"]
|
end_line = active_document["visibleRanges"][0][1]["line"]
|
||||||
|
|
||||||
# read file lines from start_line to end_line
|
# read file lines from start_line to end_line
|
||||||
with open(file_path, "r") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
lines = file.readlines()
|
_lines = file.readlines()
|
||||||
selected_lines = lines[start_line : end_line + 1]
|
_visible_lines = _lines[start_line : end_line + 1]
|
||||||
|
|
||||||
# continue with the rest of the function
|
# continue with the rest of the function
|
||||||
return {
|
return {
|
||||||
"filePath": file_path,
|
"filePath": file_path,
|
||||||
"visibleText": "".join(selected_lines),
|
"visibleText": "".join(_visible_lines),
|
||||||
"visibleRange": [start_line, end_line],
|
"visibleRange": [start_line, end_line],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def visible_range() -> LocationWithText:
|
||||||
|
visible_range_text = visible_lines()
|
||||||
|
return LocationWithText(
|
||||||
|
text=visible_range_text["visibleText"],
|
||||||
|
abspath=visible_range_text["filePath"],
|
||||||
|
range={
|
||||||
|
"start": {
|
||||||
|
"line": visible_range_text["visibleRange"][0],
|
||||||
|
"character": 0,
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": visible_range_text["visibleRange"][1],
|
||||||
|
"character": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def selected_lines():
|
def selected_lines():
|
||||||
active_document = active_text_editor()
|
active_document = active_text_editor()
|
||||||
fail_result = {
|
fail_result = {
|
||||||
@ -131,13 +149,31 @@ def selected_lines():
|
|||||||
end_col = active_document["selection"]["end"]["character"]
|
end_col = active_document["selection"]["end"]["character"]
|
||||||
|
|
||||||
# read file lines from start_line to end_line
|
# read file lines from start_line to end_line
|
||||||
with open(file_path, "r") as file:
|
with open(file_path, "r", encoding="utf-8") as file:
|
||||||
lines = file.readlines()
|
_lines = file.readlines()
|
||||||
selected_lines = lines[start_line : end_line + 1]
|
_selected_lines = _lines[start_line : end_line + 1]
|
||||||
|
|
||||||
# continue with the rest of the function
|
# continue with the rest of the function
|
||||||
return {
|
return {
|
||||||
"filePath": "",
|
"filePath": file_path,
|
||||||
"selectedText": "".join(selected_lines),
|
"selectedText": "".join(_selected_lines),
|
||||||
"selectedRange": [start_line, start_col, end_line, end_col],
|
"selectedRange": [start_line, start_col, end_line, end_col],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def selected_range() -> LocationWithText:
|
||||||
|
selected_range_text = selected_lines()
|
||||||
|
return LocationWithText(
|
||||||
|
text=selected_range_text["selectedText"],
|
||||||
|
abspath=selected_range_text["filePath"],
|
||||||
|
range={
|
||||||
|
"start": {
|
||||||
|
"line": selected_range_text["selectedRange"][0],
|
||||||
|
"character": selected_range_text["selectedRange"][1],
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": selected_range_text["selectedRange"][2],
|
||||||
|
"character": selected_range_text["selectedRange"][3],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
@ -1,6 +0,0 @@
|
|||||||
from .service import IDEService
|
|
||||||
from .types import *
|
|
||||||
|
|
||||||
__all__ = types.__all__ + [
|
|
||||||
"IDEService",
|
|
||||||
]
|
|
@ -1,37 +0,0 @@
|
|||||||
import os
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
BASE_SERVER_URL = os.environ.get("DEVCHAT_IDE_SERVICE_URL", "http://localhost:3000")
|
|
||||||
|
|
||||||
|
|
||||||
def rpc_call(f):
|
|
||||||
@wraps(f)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
if os.environ.get("DEVCHAT_IDE_SERVICE_URL", "") == "":
|
|
||||||
# maybe in a test, user don't want to mock services functions
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
function_name = f.__name__
|
|
||||||
url = f"{BASE_SERVER_URL}/{function_name}"
|
|
||||||
|
|
||||||
data = dict(zip(f.__code__.co_varnames, args))
|
|
||||||
data.update(kwargs)
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
response = requests.post(url, json=data, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f"Server error: {response.status_code}")
|
|
||||||
|
|
||||||
response_data = response.json()
|
|
||||||
if "error" in response_data:
|
|
||||||
raise Exception(f"Server returned an error: {response_data['error']}")
|
|
||||||
return response_data.get("result", None)
|
|
||||||
except ConnectionError as err:
|
|
||||||
# TODO
|
|
||||||
raise err
|
|
||||||
|
|
||||||
return wrapper
|
|
@ -1,109 +0,0 @@
|
|||||||
import os
|
|
||||||
from functools import wraps
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from .types import Location, SymbolNode
|
|
||||||
|
|
||||||
BASE_SERVER_URL = os.environ.get("DEVCHAT_IDE_SERVICE_URL", "http://localhost:3000")
|
|
||||||
|
|
||||||
|
|
||||||
def rpc_method(f):
|
|
||||||
"""
|
|
||||||
Decorator for Service methods
|
|
||||||
"""
|
|
||||||
|
|
||||||
@wraps(f)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
if os.environ.get("DEVCHAT_IDE_SERVICE_URL", "") == "":
|
|
||||||
# maybe in a test, user don't want to mock services functions
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
function_name = f.__name__
|
|
||||||
url = f"{BASE_SERVER_URL}/{function_name}"
|
|
||||||
|
|
||||||
data = dict(zip(f.__code__.co_varnames[1:], args)) # Exclude "self"
|
|
||||||
data.update(kwargs)
|
|
||||||
headers = {"Content-Type": "application/json"}
|
|
||||||
|
|
||||||
response = requests.post(url, json=data, headers=headers)
|
|
||||||
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f"Server error: {response.status_code}")
|
|
||||||
|
|
||||||
response_data = response.json()
|
|
||||||
if "error" in response_data:
|
|
||||||
raise Exception(f"Server returned an error: {response_data['error']}")
|
|
||||||
|
|
||||||
# Store the result in the _result attribute of the instance
|
|
||||||
self._result = response_data.get("result", None)
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
|
|
||||||
except ConnectionError as err:
|
|
||||||
# TODO
|
|
||||||
raise err
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class IDEService:
|
|
||||||
"""
|
|
||||||
Client for IDE service
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
client = IDEService()
|
|
||||||
res = client.ide_language()
|
|
||||||
res = client.ide_logging("info", "some message")
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._result = None
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def get_lsp_brige_port(self) -> str:
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def install_python_env(self, command_name: str, requirements_file: str) -> str:
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def update_slash_commands(self) -> bool:
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def ide_language(self) -> str:
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def ide_logging(self, level: str, message: str) -> bool:
|
|
||||||
"""
|
|
||||||
level: "info" | "warn" | "error" | "debug"
|
|
||||||
"""
|
|
||||||
return self._result
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def get_document_symbols(self, abspath: str) -> List[SymbolNode]:
|
|
||||||
try:
|
|
||||||
return [SymbolNode.parse_obj(node) for node in self._result]
|
|
||||||
except:
|
|
||||||
# TODO: loggging ide service error
|
|
||||||
return []
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def find_type_def_locations(self, abspath: str, line: int, character: int) -> List[Location]:
|
|
||||||
try:
|
|
||||||
return [Location.parse_obj(loc) for loc in self._result]
|
|
||||||
except:
|
|
||||||
# TODO: loggging ide service error
|
|
||||||
return []
|
|
||||||
|
|
||||||
@rpc_method
|
|
||||||
def find_def_locations(self, abspath: str, line: int, character: int) -> List[Location]:
|
|
||||||
try:
|
|
||||||
return [Location.parse_obj(loc) for loc in self._result]
|
|
||||||
except:
|
|
||||||
# TODO: loggging ide service error
|
|
||||||
return []
|
|
Loading…
x
Reference in New Issue
Block a user