Skip to content
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

Add willRename and didRename fileOperations #2498

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f5ec6c5
add willRename and didRename fileOperations
predragnikolic Jun 25, 2024
e26a48e
always enable
predragnikolic Jun 25, 2024
e6304c2
add renaming from the sidebar
predragnikolic Jun 25, 2024
e5e0722
changes
predragnikolic Jun 25, 2024
5c91301
fix styles
predragnikolic Jun 25, 2024
075e50e
add new line
predragnikolic Jun 25, 2024
814b0bb
hande if new path directory doesn't exist
predragnikolic Jun 25, 2024
fb42ffc
Merge branch 'main' into add-will-rename-and-did-rename
predragnikolic Jun 25, 2024
d488b06
handle renaming buffers
predragnikolic Jun 26, 2024
d1a10cf
ahh...
predragnikolic Jun 26, 2024
08e3af1
add initial_selection
predragnikolic Jun 26, 2024
6f65462
improve initial_selection and rename file_name to path
predragnikolic Jun 26, 2024
c9e8772
Split "Lsp: Rename" to "Lsp: Rename Folder" and "Lsp: Rename File"
predragnikolic Jun 27, 2024
e0c9c82
new line
predragnikolic Jun 27, 2024
a52b5c4
when renaming a directory, it would be good to retarget all open view…
predragnikolic Jun 27, 2024
2acceb6
save some lines
predragnikolic Jun 27, 2024
9ab9dc5
remove more lines
predragnikolic Jun 27, 2024
f5c26a6
few less lines
predragnikolic Jun 27, 2024
437659f
simpler conditions
predragnikolic Jun 27, 2024
3647f92
avoid multiple statements on one line
predragnikolic Jun 27, 2024
3389f66
implement FileOperationFilter
predragnikolic Jul 2, 2024
4785d49
remomve few lines
predragnikolic Jul 2, 2024
2f5af31
fix flake
predragnikolic Jul 2, 2024
890667c
fix pyright
predragnikolic Jul 2, 2024
af04c56
remove is_visible code
predragnikolic Jul 2, 2024
178adae
remove LSP: Rename File and Rename folder in favor of LSP: Rename... …
predragnikolic Jul 2, 2024
8ef443c
rename LspRenameFileCommand to LspRenamePathCommand
predragnikolic Jul 2, 2024
d1342b9
remove FileOperationFilterChecker, FileOperationFilterMatcher in favo…
predragnikolic Jul 2, 2024
057eaff
flake8 fixes
predragnikolic Jul 2, 2024
cb82086
cannot use an input handler to rename folder because it always displa…
predragnikolic Jul 2, 2024
728c6ea
Update plugin/core/types.py
predragnikolic Jul 3, 2024
93b024a
remove unnecessary if
predragnikolic Jul 3, 2024
1ff4648
remove LSP: Rename... from sidebar in favor of overriding the existin…
predragnikolic Jul 3, 2024
1ae7ec4
always enable LspRenamePathCommand
predragnikolic Jul 3, 2024
6181644
handle OS errors
predragnikolic Jul 3, 2024
181f17c
except Exception
predragnikolic Jul 4, 2024
18e8549
Remove "LSP: Rename File" commands, instead override Default ST commands
predragnikolic Aug 18, 2024
51962f1
remove unused import
predragnikolic Aug 18, 2024
21f1bf7
Merge branch 'main' into add-will-rename-and-did-rename
predragnikolic Aug 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Default.sublime-commands
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@
"caption": "LSP: Rename",
"command": "lsp_symbol_rename"
},
{
"caption": "LSP: Rename File",
"command": "lsp_rename_file"
},
{
"caption": "LSP: Code Action",
"command": "lsp_code_actions"
Expand Down
14 changes: 14 additions & 0 deletions Side Bar.sublime-menu
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
jwortmann marked this conversation as resolved.
Show resolved Hide resolved
{
"caption": "Lsp: Rename File",
rchl marked this conversation as resolved.
Show resolved Hide resolved
"mnemonic": "l",
"command": "lsp_rename_file",
"args": {"files": []}
},
{
"caption": "Lsp: Rename Folder",
rchl marked this conversation as resolved.
Show resolved Hide resolved
"mnemonic": "l",
"command": "lsp_rename_file",
"args": {"dirs": []}
},
]
2 changes: 2 additions & 0 deletions boot.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
from .plugin.references import LspSymbolReferencesCommand
from .plugin.rename import LspHideRenameButtonsCommand
from .plugin.rename import LspSymbolRenameCommand
from .plugin.rename_file import LspRenameFileCommand
from .plugin.save_command import LspSaveAllCommand
from .plugin.save_command import LspSaveCommand
from .plugin.selection_range import LspExpandSelectionCommand
Expand Down Expand Up @@ -146,6 +147,7 @@
"LspSymbolImplementationCommand",
"LspSymbolReferencesCommand",
"LspSymbolRenameCommand",
"LspRenameFileCommand",
rchl marked this conversation as resolved.
Show resolved Hide resolved
"LspSymbolTypeDefinitionCommand",
"LspToggleCodeLensesCommand",
"LspToggleHoverPopupsCommand",
Expand Down
8 changes: 8 additions & 0 deletions plugin/core/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -6064,6 +6064,10 @@ def colorPresentation(cls, params: ColorPresentationParams, view: sublime.View)
def willSaveWaitUntil(cls, params: WillSaveTextDocumentParams, view: sublime.View) -> Request:
return Request("textDocument/willSaveWaitUntil", params, view)

@classmethod
def willRenameFiles(cls, params: RenameFilesParams) -> Request:
return Request("workspace/willRenameFiles", params)

@classmethod
def documentSymbols(cls, params: DocumentSymbolParams, view: sublime.View) -> Request:
return Request("textDocument/documentSymbol", params, view, progress=True)
Expand Down Expand Up @@ -6256,6 +6260,10 @@ def didSave(cls, params: DidSaveTextDocumentParams) -> Notification:
def didClose(cls, params: DidCloseTextDocumentParams) -> Notification:
return Notification("textDocument/didClose", params)

@classmethod
def didRenameFiles(cls, params: RenameFilesParams) -> Notification:
return Notification("workspace/didRenameFiles", params)

@classmethod
def didChangeConfiguration(cls, params: DidChangeConfigurationParams) -> Notification:
return Notification("workspace/didChangeConfiguration", params)
Expand Down
5 changes: 5 additions & 0 deletions plugin/core/sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,11 @@ def get_initialize_params(variables: dict[str, str], workspace_folders: list[Wor
"codeLens": {
"refreshSupport": True
},
"fileOperations": {
"dynamicRegistration": True,
"willRename": True,
"didRename": True
},
"inlayHint": {
"refreshSupport": True
},
Expand Down
136 changes: 136 additions & 0 deletions plugin/rename_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from __future__ import annotations
from .core.open import open_file_uri
from .core.protocol import Notification, RenameFilesParams, Request, WorkspaceEdit
from .core.registry import LspWindowCommand
from pathlib import Path
from urllib.parse import urljoin
import os
import sublime
import sublime_plugin


class RenameFileInputHandler(sublime_plugin.TextInputHandler):
def want_event(self) -> bool:
return False

def __init__(self, path: str) -> None:
self.path = path

def name(self) -> str:
return "new_name"

def placeholder(self) -> str:
return self.path

def initial_text(self) -> str:
return self.placeholder()

def initial_selection(self) -> list[tuple[int, int]]:
end_point = self.path.rfind('.')
if end_point == -1:
end_point = len(self.path)
return [(0, end_point)]

def validate(self, path: str) -> bool:
return len(path) > 0


class LspRenameFileCommand(LspWindowCommand):
capability = 'workspace.fileOperations.willRename'

def is_enabled(self):
return True

def is_visible(self, dirs=None, files=None):
if dirs is None and files is None:
return True # show 'LSP: Rename File' in command palette
if dirs is not None:
return len(dirs) == 1 # show 'LSP: Rename Folder' in sidebar
if files is not None:
return len(files) == 1 # show 'LSP: Rename File' in sidebar
return False

def want_event(self) -> bool:
return False

def input(self, args: dict) -> sublime_plugin.TextInputHandler | None:
if "new_name" in args:
return None
old_path = self.get_old_path(args.get('dirs'), args.get('files'), self.window.active_view())
return RenameFileInputHandler(Path(old_path).name)

def run(
self,
new_name: str, # new_name can be: FILE_NAME.xy OR ./FILE_NAME.xy OR ../../FILE_NAME.xy
dirs: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename Folder"
files: list[str] | None = None, # exist when invoked from the sidebar with "LSP: Rename File"
) -> None:
session = self.session()
view = self.window.active_view()
old_path = self.get_old_path(dirs, files, view)
new_path = os.path.normpath(Path(old_path).parent / new_name)
if os.path.exists(new_path):
self.window.status_message('Unable to Rename. Already exists')
return
if old_path == '' and view: # handle renaming buffers
view.set_name(Path(new_path).name)
return
rename_file_params: RenameFilesParams = {
"files": [{
"newUri": urljoin("file:", new_path),
"oldUri": urljoin("file:", old_path),
}]
}
if not session:
self.rename_path(old_path, new_path)
self.notify_did_rename(rename_file_params)
return
request = Request.willRenameFiles(rename_file_params)
session.send_request(
request,
lambda res: self.handle(res, session.config.name, old_path, new_path, rename_file_params)
)

def get_old_path(self, dirs: list[str] | None, files: list[str] | None, view: sublime.View | None) -> str:
if dirs:
return dirs[0]
if files:
return files[0]
if view:
return view.file_name() or ""
return ""

def handle(self, res: WorkspaceEdit | None, session_name: str,
old_path: str, new_path: str, rename_file_params: RenameFilesParams) -> None:
session = self.session_by_name(session_name)
if session:
# LSP spec - Apply WorkspaceEdit before the files are renamed
if res:
session.apply_workspace_edit_async(res, is_refactoring=True)
self.rename_path(old_path, new_path)
self.notify_did_rename(rename_file_params)

def rename_path(self, old_path: str, new_path: str) -> None:
old_regions: list[sublime.Region] = []
view = self.window.find_open_file(old_path)
if view:
old_regions = [region for region in view.sel()]
view.close() # LSP spec - send didClose for the old file
new_dir = Path(new_path).parent
if not os.path.exists(new_dir):
os.makedirs(new_dir)
os.rename(old_path, new_path)
if os.path.isfile(new_path):
def restore_regions(v: sublime.View | None) -> None:
if not v:
return
v.sel().clear()
v.sel().add_all(old_regions)

# LSP spec - send didOpen for the new file
open_file_uri(self.window, new_path).then(restore_regions)

def notify_did_rename(self, rename_file_params: RenameFilesParams):
sessions = [s for s in self.sessions() if s.has_capability('workspace.fileOperations.didRename')]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server capability is defined as 'workspace.fileOperations.didRename':

interface ServerCapabilities {
  workspace: {
  //...
    fileOperations:  {
    // ...
    didRename?: FileOperationRegistrationOptions
  }
}

interface FileOperationRegistrationOptions {
	filters: FileOperationFilter[];
}

export interface FileOperationFilter {
	scheme?: string;
	pattern: FileOperationPattern
}

interface FileOperationPattern {
	glob: string;
	matches?: FileOperationPatternKind;
	options?: FileOperationPatternOptions;
}

To implement this properly I would need to respect the provided pattern in FileOperationRegistrationOptions,
but that seem like more work. The current code also works, at least with the server I am testing this feature.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I am unsure if I need to implement FileOperationRegistrationOptions in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should try implementing that. filters is not an optional feature so we won't be complaint otherwise.

for s in sessions:
s.send_notification(Notification.didRenameFiles(rename_file_params))