Skip to content

Commit

Permalink
impl textDocument/rename; close #114
Browse files Browse the repository at this point in the history
  • Loading branch information
ul committed Nov 8, 2018
1 parent ce77202 commit 84d9a17
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 86 deletions.
6 changes: 6 additions & 0 deletions README.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ quick installation of language servers supported by kak-lsp out of the box.

== Usage

NOTE: Contents below corresponds to the master branch HEAD and could be slightly out-of-sync with
the version installed from pre-built binaries. The most common case is new commands being in a
pre-release testing stage. Please refer README.asciidoc revision tagged with version you use or
README.asciidoc from the release archive (included starting from version 5.10.0).

To enable LSP support for configured languages (see the next section) just add following commands to
your `kakrc`:

Expand Down Expand Up @@ -111,6 +116,7 @@ hook global WinSetOption filetype=rust %{
}
----

* `lsp-rename <new_name>` and `lsp-rename-prompt` commands to rename the symbol under the main cursor.
* `lsp_diagnostic_count` option which contains number of diagnostics published for the current buffer. For example, you can put it into your modeline to see at a glance if there are errors in the current file
* starting new kak-lsp session when Kakoune session begins and stopping it when Kakoune session ends

Expand Down
1 change: 1 addition & 0 deletions ci/before_deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ $ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET

Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\kak-lsp.exe" '.\'
Copy-Item "$SRC_DIR\kak-lsp.toml" '.\'
Copy-Item "$SRC_DIR\README.asciidoc" '.\'

7z a "$ZIP" *

Expand Down
1 change: 1 addition & 0 deletions ci/before_deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ main() {

cp target/$TARGET/release/kak-lsp $stage/
cp kak-lsp.toml $stage/
cp README.asciidoc $stage/

case $TRAVIS_OS_NAME in
linux)
Expand Down
22 changes: 21 additions & 1 deletion rc/lsp.kak
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,26 @@ character = %d
' "${kak_session}" "${kak_client}" "${kak_buffile}" "${kak_timestamp}" $((${kak_cursor_line} - 1)) $((${kak_cursor_column} - 1)) | ${kak_opt_lsp_cmd}) > /dev/null 2>&1 < /dev/null & }
}

def lsp-rename -params 1 -docstring "Rename symbol under the main cursor" %{
lsp-did-change
nop %sh{ (printf '
session = "%s"
client = "%s"
buffile = "%s"
version = %d
method = "textDocument/rename"
[params]
newName = "%s"
[params.position]
line = %d
character = %d
' "${kak_session}" "${kak_client}" "${kak_buffile}" "${kak_timestamp}" "$1" $((${kak_cursor_line} - 1)) $((${kak_cursor_column} - 1)) | ${kak_opt_lsp_cmd}) > /dev/null 2>&1 < /dev/null & }
}

def lsp-rename-prompt -docstring "Rename symbol under the main cursor (prompt for a new name)" %{
prompt 'New name: ' %{ lsp-rename %val{text} }
}

def lsp-signature-help -docstring "Request signature help for the main cursor position" %{
lsp-did-change
nop %sh{ (printf '
Expand Down Expand Up @@ -688,7 +708,7 @@ def lsp -params 1.. %sh{
fi
} %{
for cmd in start hover definition references signature-help diagnostics document-symbol\
workspace-symbol workspace-symbol-incr\
workspace-symbol workspace-symbol-incr rename rename-prompt\
capabilities stop formatting formatting-sync highlight-references\
inline-diagnostics-enable inline-diagnostics-disable\
diagnostic-lines-enable diagnostics-lines-disable auto-hover-enable auto-hover-disable\
Expand Down
6 changes: 6 additions & 0 deletions src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ fn dispatch_editor_request(request: EditorRequest, mut ctx: &mut Context) {
request::WorkspaceSymbol::METHOD => {
workspace::workspace_symbol(meta, params, &mut ctx);
}
request::Rename::METHOD => {
rename::text_document_rename(meta, params, &mut ctx);
}
"textDocument/diagnostics" => {
diagnostics::editor_diagnostics(meta, &mut ctx);
}
Expand Down Expand Up @@ -310,6 +313,9 @@ fn dispatch_server_response(
request::WorkspaceSymbol::METHOD => {
workspace::editor_workspace_symbol(meta, response, &mut ctx);
}
request::Rename::METHOD => {
rename::editor_rename(meta, params, response, &mut ctx);
}
"textDocument/referencesHighlight" => {
references::editor_references_highlight(meta, response, &mut ctx);
}
Expand Down
22 changes: 21 additions & 1 deletion src/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ use util::*;
pub fn initialize(root_path: &str, options: Option<Value>, meta: &EditorMeta, ctx: &mut Context) {
let params = InitializeParams {
capabilities: ClientCapabilities {
workspace: Some(WorkspaceClientCapabilities::default()),
workspace: Some(WorkspaceClientCapabilities {
workspace_edit: Some(WorkspaceEditCapability {
document_changes: Some(true),
resource_operations: Some(vec![
ResourceOperationKind::Create,
ResourceOperationKind::Delete,
ResourceOperationKind::Rename,
]),
..WorkspaceEditCapability::default()
}),
..WorkspaceClientCapabilities::default()
}),
text_document: Some(TextDocumentClientCapabilities {
completion: Some(CompletionCapability {
completion_item: Some(CompletionItemCapability {
Expand Down Expand Up @@ -89,6 +100,15 @@ pub fn capabilities(meta: &EditorMeta, ctx: &mut Context) {
features.push("lsp-formatting");
}

if let Some(ref rename_provider) = server_capabilities.rename_provider {
match rename_provider {
RenameProviderCapability::Simple(true) | RenameProviderCapability::Options(_) => {
features.push("lsp-rename")
}
_ => (),
}
}

features.push("lsp-diagnostics");

let command = format!(
Expand Down
85 changes: 1 addition & 84 deletions src/language_features/formatting.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use context::*;
use itertools::Itertools;
use languageserver_types::request::Request;
use languageserver_types::*;
use serde::Deserialize;
Expand Down Expand Up @@ -43,89 +42,7 @@ pub fn editor_formatting(
return;
}
TextEditResponse::Array(text_edits) => {
// empty text edits processed as a special case because Kakoune's `select` command
// doesn't support empty arguments list
if text_edits.is_empty() {
// nothing to do, but sending command back to the editor is required to handle case when
// editor is blocked waiting for response via fifo
ctx.exec(meta.clone(), "nop".to_string());
return;
}
let edits = text_edits
.iter()
.map(|text_edit| {
let TextEdit { range, new_text } = text_edit;
// LSP ranges are 0-based, but Kakoune's 1-based.
// LSP ranges are exclusive, but Kakoune's are inclusive.
// Also from LSP spec: If you want to specify a range that contains a line including
// the line ending character(s) then use an end position denoting the start of the next
// line.
let mut start_line = range.start.line;
let mut start_char = range.start.character;
let mut end_line = range.end.line;
let mut end_char = range.end.character;

if start_line == end_line && start_char == end_char && start_char == 0 {
start_char = 1_000_000;
} else {
start_line += 1;
start_char += 1;
}

if end_char > 0 {
end_line += 1;
} else {
end_char = 1_000_000;
}

let insert = start_line == end_line && start_char - 1 == end_char;

(
format!(
"{}.{}",
start_line,
if !insert { start_char } else { end_char }
),
format!("{}.{}", end_line, end_char),
new_text,
insert,
)
}).collect::<Vec<_>>();

let select_edits = edits
.iter()
.map(|(start, end, _, _)| format!("{},{}", start, end))
.join(" ");

let apply_edits = edits
.iter()
.enumerate()
.map(|(i, (_, _, content, insert))| {
format!(
"exec 'z{}<space>'
{} {}",
if i > 0 {
format!("{})", i)
} else {
"".to_string()
},
if *insert {
"lsp-insert-after-selection"
} else {
"lsp-replace-selection"
},
editor_quote(&content)
)
}).join("\n");

let command = format!(
"select {}
exec -save-regs '' Z
{}",
select_edits, apply_edits
);
let command = format!("eval -draft -save-regs '^' {}", editor_quote(&command));
ctx.exec(meta.clone(), command);
apply_text_edits(None, &text_edits, meta, ctx);
}
}
}
1 change: 1 addition & 0 deletions src/language_features/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pub mod document_symbol;
pub mod formatting;
pub mod hover;
pub mod references;
pub mod rename;
pub mod signature_help;
128 changes: 128 additions & 0 deletions src/language_features/rename.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use context::*;
use languageserver_types::request::Request;
use languageserver_types::*;
use serde::Deserialize;
use serde_json::{self, Value};
use std::fs;
use types::*;
use url::Url;
use util::*;

pub fn text_document_rename(meta: &EditorMeta, params: EditorParams, ctx: &mut Context) {
let options = TextDocumentRenameParams::deserialize(params.clone());
if options.is_err() {
error!("Params should follow TextDocumentRenameParams structure");
}
let options = options.unwrap();
let req_params = RenameParams {
text_document: TextDocumentIdentifier {
uri: Url::from_file_path(&meta.buffile).unwrap(),
},
position: options.position,
new_name: options.new_name,
};
let id = ctx.next_request_id();
ctx.response_waitlist.insert(
id.clone(),
(meta.clone(), request::Rename::METHOD.into(), params),
);
ctx.call(id, request::Rename::METHOD.into(), req_params);
}

pub fn editor_rename(meta: &EditorMeta, _params: EditorParams, result: Value, ctx: &mut Context) {
let result: Option<WorkspaceEdit> =
serde_json::from_value(result).expect("Failed to parse formatting response");
if result.is_none() {
return;
}
let result = result.unwrap();
if let Some(document_changes) = result.document_changes {
match document_changes {
DocumentChanges::Edits(edits) => {
for edit in edits {
// TODO handle version
apply_text_edits(Some(&edit.text_document.uri), &edit.edits, meta, ctx);
}
}
DocumentChanges::Operations(ops) => {
for op in ops {
match op {
DocumentChangeOperation::Edit(edit) => {
apply_text_edits(Some(&edit.text_document.uri), &edit.edits, meta, ctx)
}
DocumentChangeOperation::Op(op) => match op {
ResourceOp::Create(op) => {
let path = Url::parse(&op.uri).unwrap().to_file_path().unwrap();
let ignore_if_exists = if let Some(options) = op.options {
!options.overwrite.unwrap_or(false)
&& options.ignore_if_exists.unwrap_or(false)
} else {
false
};
if !(ignore_if_exists && path.exists())
&& fs::write(&path, []).is_err()
{
error!(
"Failed to create file: {}",
path.to_str().unwrap_or("")
);
}
}
ResourceOp::Delete(op) => {
let path = Url::parse(&op.uri).unwrap().to_file_path().unwrap();
if path.is_dir() {
let recursive = if let Some(options) = op.options {
options.recursive.unwrap_or(false)
} else {
false
};
if recursive {
if fs::remove_dir_all(&path).is_err() {
error!(
"Failed to delete directory: {}",
path.to_str().unwrap_or("")
);
}
} else if fs::remove_dir(&path).is_err() {
error!(
"Failed to delete directory: {}",
path.to_str().unwrap_or("")
);
}
} else if path.is_file() && fs::remove_file(&path).is_err() {
error!(
"Failed to delete file: {}",
path.to_str().unwrap_or("")
);
}
}
ResourceOp::Rename(op) => {
let from = Url::parse(&op.old_uri).unwrap().to_file_path().unwrap();
let to = Url::parse(&op.new_uri).unwrap().to_file_path().unwrap();
let ignore_if_exists = if let Some(options) = op.options {
!options.overwrite.unwrap_or(false)
&& options.ignore_if_exists.unwrap_or(false)
} else {
false
};
if !(ignore_if_exists && to.exists())
&& fs::rename(&from, &to).is_err()
{
error!(
"Failed to rename file: {} -> {}",
from.to_str().unwrap_or(""),
to.to_str().unwrap_or("")
);
}
}
},
}
}
}
}
} else if let Some(changes) = result.changes {
for (uri, change) in &changes {
apply_text_edits(Some(uri), &change, meta, ctx);
}
}
}
7 changes: 7 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ pub struct PositionParams {
pub position: Position,
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TextDocumentRenameParams {
pub position: Position,
pub new_name: String,
}

// Language Server

// XXX serde(untagged) ?
Expand Down
Loading

0 comments on commit 84d9a17

Please sign in to comment.