From 8071dc045c8835d334bbb5065dca7cef04611094 Mon Sep 17 00:00:00 2001 From: James Brownlee Date: Tue, 27 Feb 2024 19:12:27 -0500 Subject: [PATCH] fixed version issues except for the Windows ones --- Default.sublime-keymap | 1 + Main.sublime-menu | 39 +++++++++++++++++++- README.md | 5 ++- __init__.py | 48 +++++++++++++++++++------ src/completion_text.py | 13 ++++++- src/phantom_state.py | 6 ++-- src/refact_lsp.py | 80 ++++++++++++++++++++++++++++++++++-------- src/refact_process.py | 29 +++++---------- src/refact_sessions.py | 30 ++++++++++++++-- src/statusbar.py | 14 +++++++- src/utils.py | 7 ++++ 11 files changed, 216 insertions(+), 56 deletions(-) diff --git a/Default.sublime-keymap b/Default.sublime-keymap index 9a5da6c..3385a62 100644 --- a/Default.sublime-keymap +++ b/Default.sublime-keymap @@ -1,4 +1,5 @@ [ { "keys": ["tab"], "command": "refact_accept_completion", "context": [{"key": "refact.show_completion"}] }, { "keys": ["escape"], "command": "refact_clear_completion", "context": [{"key": "refact.show_completion"}] }, + { "keys": ["ctrl+p"], "command": "refact_pause" } ] diff --git a/Main.sublime-menu b/Main.sublime-menu index 47cdcfe..d5ef9a4 100644 --- a/Main.sublime-menu +++ b/Main.sublime-menu @@ -9,5 +9,42 @@ "args":{} }, ] - } + }, + { + "caption": "Preferences", + "mnemonic": "n", + "id": "preferences", + "children": + [ + { + "caption": "Package Settings", + "mnemonic": "P", + "id": "package-settings", + "children": + [ + { + "caption": "refact", + "children": + [ + { + "command": "open_file", "args": + { + "file": "${packages}/refact/refact.sublime-settings" + }, + "caption": "Settings – Default" + }, + { + "command": "open_file", "args": + { + "file": "${packages}/User/refact.sublime-settings" + }, + "caption": "Settings – User" + }, + { "caption": "-" } + ] + } + ] + } + ] + } ] diff --git a/README.md b/README.md index 76696b0..e48af24 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,10 @@ Refact for VS Code is a free, open-source AI code assistant 4. Move the folder repsository to sublime's "Packages" folder (you can find this by opening the command prompt in sublime and typing "browse packages") 5. rename the folder to "refact" 6. Open refact.sublime-settings and add the API key - + +#Pause +You can pause and unpause refact suggestions by pressing ctrl + p + #File Documentation# #__init__.py diff --git a/__init__.py b/__init__.py index 438ffce..b52d6d6 100644 --- a/__init__.py +++ b/__init__.py @@ -21,7 +21,20 @@ def on_modified(self, view): session = refact_session_manager.get_session(view) session.notify_document_update() session.update_completion() + + def on_close(self, view): + if not start_refact: + return + + session = refact_session_manager.get_session(view) + session.notify_close() + def on_post_save(self, view): + if not start_refact: + return + + session = refact_session_manager.get_session(view) + session.notify_save() def on_query_context(self, view, key, operator, operand, match_all): if start_refact: @@ -52,6 +65,7 @@ def on_text_command(self, view, command_name, args): elif command_name == "hide_popup": session.clear_completion() # elif command_name == "left_delete" or command_name == "right_delete": + # session.clear_completion() elif command_name == "move" or command_name == "move_to": session.clear_completion() @@ -63,17 +77,24 @@ def plugin_loaded(): global start_refact s = sublime.load_settings("refact.sublime-settings") pause_completion = s.get("pause_completion", False) - if not pause_completion: + if pause_completion: + sublime.status_message("⏸️ refact.ai") + else: + refact_start() + +def refact_start(): + global refact_session_manager + global start_refact + if refact_session_manager: + refact_session_manager.start() + else: refact_session_manager = RefactSessionManager() - start_refact= True + start_refact= True class RefactStartCommand(sublime_plugin.TextCommand): def run(self, edit): - global refact_session_manager - global start_refact - refact_session_manager = RefactSessionManager() - start_refact= True - + refact_start() + class RefactStopCommand(sublime_plugin.TextCommand): def run(self, edit): global start_refact @@ -123,11 +144,18 @@ def run(self, edit): global start_refact start_refact= False s = sublime.load_settings("refact.sublime-settings") - s.set("pause_completion", True) + pause_status = s.get("pause_completion", False) + pause_status = not pause_status + s.set("pause_completion", pause_status) sublime.save_settings("refact.sublime-settings") - refact_session_manager.get_session(self.view).clear_completion() + + if not pause_status: + refact_start() + else: + if refact_session_manager: + refact_session_manager.shutdown() class RefactClearCompletion(sublime_plugin.TextCommand): def run(self, edit): refact_session_manager.get_session(self.view).clear_completion() - + diff --git a/src/completion_text.py b/src/completion_text.py index e2198b5..5297b12 100644 --- a/src/completion_text.py +++ b/src/completion_text.py @@ -1,5 +1,6 @@ +from .utils import * -def get_nonwhitespace(s, start): +def get_nonwhitespace(s, start = 0): end = len(s) for i in range(start, end): if not s[i].isspace(): @@ -43,6 +44,16 @@ def collect_space(s, index): return space def get_completion_text(point, text, line, end = None): + if not line or line.isspace(): + s = replace_tab(text) + res_space = get_nonwhitespace(s) + l = replace_tab(line) + diff = res_space - len(l) + if diff > 0: + return s[(res_space - diff):] + else: + return s[res_space:] + diff = find_diff(text, line) end = end or len(line) diff --git a/src/phantom_state.py b/src/phantom_state.py index cdf3e8b..a8754c6 100644 --- a/src/phantom_state.py +++ b/src/phantom_state.py @@ -55,10 +55,7 @@ def __init__(self, view, phantom_block): self.empty = (not self.cursor_phantom or len(self.cursor_phantom.text) == 0) and not self.next_inline_phantom and not self.next_line_phantom def is_cursor_on_line(self, view, line): - if self.cursor_phantom.line <= view.rowcol(line.b)[0]: - phantom_line = self.cursor_phantom.get_line(view) - return phantom_line.intersects(line) - return False + return self.cursor_phantom.line == view.rowcol(line.b)[0] def create_phantom(self, view, position, text): return PhantomSeed(view, position, text) @@ -218,6 +215,7 @@ def get_update_meta(self, seed): line = view.line(cursor_point) if not seed.is_cursor_on_line(view, line): + self.clear_phantoms() return None completion_text = self.get_seed_completion_text(seed) diff --git a/src/refact_lsp.py b/src/refact_lsp.py index aa53074..90ba084 100644 --- a/src/refact_lsp.py +++ b/src/refact_lsp.py @@ -13,16 +13,24 @@ def __init__(self, process, statusbar): self.connect(process) def load_document(self, file_name: str, text: str, version: int = 1, languageId = LANGUAGE_IDENTIFIER.PYTHON): + print("load_document", file_name) + if languageId is None: languageId = LANGUAGE_IDENTIFIER.PYTHON + if file_name is None: + return + uri = pathlib.Path(file_name).as_uri() try: - self.lsp_client.didOpen(TextDocumentItem(uri, languageId, version, text=text)) - except: + self.lsp_client.didOpen(TextDocumentItem(uri, languageId, version=version, text=text)) + except Exception as err: + self.statusbar.handle_err(err) print("lsp didOpen error") def did_change(self, file_name: str, version: int, text: str, languageId = LANGUAGE_IDENTIFIER.PYTHON): + print("did_change file_name", file_name) + if languageId is None: languageId = LANGUAGE_IDENTIFIER.PYTHON @@ -30,12 +38,49 @@ def did_change(self, file_name: str, version: int, text: str, languageId = LANGU return uri = pathlib.Path(file_name).as_uri() - + try: self.lsp_client.didChange(TextDocumentItem(uri, languageId, version, text=text), [TextDocumentContentChangeEvent(None, None, text)]) - except: + except Exception as err: + self.statusbar.handle_err(err) print("lsp didChange error") + def did_save(self, file_name: str, version: int, text: str, languageId = LANGUAGE_IDENTIFIER.PYTHON): + print("did_save file_name", file_name) + + if languageId is None: + languageId = LANGUAGE_IDENTIFIER.PYTHON + + if file_name is None: + return + + uri = pathlib.Path(file_name).as_uri() + + try: + self.lsp_client.lsp_endpoint.send_notification("textDocument/didSave", textDocument=TextDocumentItem(uri, languageId, version, text=text)) + + except Exception as err: + self.statusbar.handle_err(err) + + print("lsp didChange error", str(err)) + + def did_close(self, file_name: str, version: int, text: str, languageId = LANGUAGE_IDENTIFIER.PYTHON): + print("did_close file_name", file_name) + + if languageId is None: + languageId = LANGUAGE_IDENTIFIER.PYTHON + + if file_name is None: + return + + uri = pathlib.Path(file_name).as_uri() + + try: + self.lsp_client.lsp_endpoint.send_notification("textDocument/didClose", textDocument=TextDocumentItem(uri, languageId, version, text=text)) + except Exception as err: + print("lsp did_close error") + self.statusbar.handle_err(err) + def get_completions(self, file_name, pos: Tuple[int, int], multiline: bool = False): self.statusbar.update_statusbar("loading") params = { @@ -43,8 +88,12 @@ def get_completions(self, file_name, pos: Tuple[int, int], multiline: bool = Fal "temperature": 0.1 } + if file_name is None: + return + uri = pathlib.Path(file_name).as_uri() - try: + + try: res = self.lsp_endpoint.call_method( "refact/getCompletions", textDocument=TextDocumentIdentifier(uri), @@ -54,29 +103,30 @@ def get_completions(self, file_name, pos: Tuple[int, int], multiline: bool = Fal self.statusbar.update_statusbar("ok") return res except Exception as err: - self.statusbar.update_statusbar("error", str(type(err))) + self.statusbar.handle_err(err) def shutdown(self): try: self.lsp_client.shutdown() - except: + except Exception as err: + self.statusbar.handle_err(err) + print("lsp error shutdown") + def logMessage(self, args): + print("logMessage", args) + def connect(self, process): capabilities = {} - # s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # s.connect(("127.0.0.1", 8002)) - # pipein, pipeout = s.makefile("wb", buffering=0), s.makefile("rb", buffering=0) - - # json_rpc_endpoint = JsonRpcEndpoint(pipein, pipeout) json_rpc_endpoint = JsonRpcEndpoint(process.stdin, process.stdout) - self.lsp_endpoint = LspEndpoint(json_rpc_endpoint) + self.lsp_endpoint = LspEndpoint(json_rpc_endpoint, notify_callbacks = {"window/logMessage":print}) self.lsp_client = LspClient(self.lsp_endpoint) + try: self.lsp_client.initialize(process.pid, None, None, None, capabilities, "off", None) except Exception as err: - self.statusbar.update_statusbar("error", str(type(err))) - print("lsp initialize error") + self.statusbar.handle_err(err) + print("lsp initialize error", err) def get_language_id(file_type): if file_type and not file_type.isspace(): diff --git a/src/refact_process.py b/src/refact_process.py index 14e121f..5eeaad8 100644 --- a/src/refact_process.py +++ b/src/refact_process.py @@ -12,16 +12,6 @@ def __init__(self): self.active = False self.statusbar = StatusBar() - def process_server_errors(self): - process = self.process - - stderr = self.process.stderr - while process.poll() is None: - line = stderr.readline().decode('utf-8') - print(line) - if "error" in line: - self.statusbar.update_statusbar("error", line) - def get_server_path(self): return os.path.join(sublime.packages_path(), "refact", "server", "refact-lsp") @@ -51,19 +41,16 @@ def get_server_commands(self): def start_server(self): self.active = True server_cmds = self.get_server_commands() - self.process = subprocess.Popen(server_cmds, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr=subprocess.PIPE) - self.poll_server() - t = threading.Thread(target=self.process_server_errors) - t.start() - + self.process = subprocess.Popen(server_cmds, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr=subprocess.PIPE, shell=False) + + self.statusbar.update_statusbar("ok") if not self.connection is None: self.connection.shutdown() self.connection = LSP(self.process, self.statusbar) - def poll_server(self): - didCrash = self.process.poll() - if not didCrash is None: - self.active = False - else: - sublime.set_timeout(self.poll_server, 100) + def stop_server(self): + self.connection.shutdown() + self.process.terminate() + self.statusbar.update_statusbar("pause") + diff --git a/src/refact_sessions.py b/src/refact_sessions.py index d5f409e..17f2b7c 100644 --- a/src/refact_sessions.py +++ b/src/refact_sessions.py @@ -9,6 +9,7 @@ from .refact_lsp import LSP, get_language_id from .refact_process import RefactProcessWrapper from .phantom_state import PhantomState, PhantomInsertion +from .completion_text import get_nonwhitespace class RefactSessionManager: @@ -18,6 +19,15 @@ def __init__(self): self.connection = self.process.start_server() self.views = {} + def start(self): + self.connection = self.process.start_server() + + def shutdown(self): + if self.process and self.process.active: + self.process.stop_server() + for key, session in self.views.items(): + session.clear_completion() + def get_view_id(self, view): if view.element() is None: return view.id() @@ -61,7 +71,17 @@ def notify_document_update(self): if self.is_ui or self.phantom_state.update_step: return self.connection().did_change(self.file_name, self.version, get_text(self.view), self.languageId) - self.version = self.version + 1 + + def notify_close(self): + if self.is_ui or self.phantom_state.update_step: + return + self.connection().did_close(self.file_name, self.version, get_text(self.view), self.languageId) + + def notify_save(self): + if self.is_ui or self.phantom_state.update_step: + return + + self.connection().did_save(self.file_name, self.version, get_text(self.view), self.languageId) def update_completion(self): if self.is_ui : @@ -154,7 +174,13 @@ def show_completions_inner(self, version, prefix, locations, multiline = False): return rc = self.view.rowcol(location) - res = self.connection().get_completions(self.file_name, rc, multiline) + + if not prefix or prefix.isspace(): + pos_arg = (rc[0], 0) + else: + pos_arg = rc + res = self.connection().get_completions(self.file_name, pos_arg, multiline) + if res is None: self.clear_completion_process() return diff --git a/src/statusbar.py b/src/statusbar.py index df7a567..2833c89 100644 --- a/src/statusbar.py +++ b/src/statusbar.py @@ -7,12 +7,14 @@ def __init__(self): self.status_loop() self.icons = [u"◐", u"◓", u"◑", u"◒"] self.current_icon = 0 + self.duration = 0 def status_loop(self): display = "" - if self.status == "error": display = '⛔refact.ai:' + self.msg + elif self.status == "pause": + display ="⏸️ refact.ai" elif self.status == "ok": display = "refact.ai" elif self.status == "loading": @@ -20,8 +22,18 @@ def status_loop(self): self.current_icon = (self.current_icon + 1 ) % len(self.icons) sublime.status_message(display) + if self.duration > 0 or self.status == "loading": + if self.duration > 0: + self.duration = self.duration - 1 sublime.set_timeout(self.status_loop, 100) def update_statusbar(self, status, msg = ""): self.status = status self.msg = msg + self.duration = 5 + self.status_loop() + + def handle_err(self, err): + if not isinstance(err, str) and err.message: + err = err.message + self.update_statusbar("error", msg = str(err)) \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index f7efe4c..3d10aeb 100644 --- a/src/utils.py +++ b/src/utils.py @@ -36,3 +36,10 @@ def filter_none(l): def identity(x): return x + +def replace_tab(text): + s = sublime.load_settings("Preferences.sublime-settings") + tab_size = s.get("tab_size", 4) + return text.replace('\t', ' ' * tab_size) + +