on master help sign in
Move most LSP specific commmands to commands::lsp
commit504d5ce8bd71486fc897ecdb33ff12c1c3a1e6b3 authorBlaž Hrastnik <blaz@mxxn.io> dateFri 18 Feb 2022, 13:58 (5 years ago) parent7b1d682
2 files changed+785−769
helix-term/src/commands.rs+9−769 helix-term/src/commands/lsp.rs+776
helix-term/src/commands.rs+9−769
@@ -1,6 +1,8 @@
11pub(crate) mod dap;
2+pub(crate) mod lsp;
23
34pub use dap::*;
5+pub use lsp::*;
46
57use helix_core::{
68 comment, coords_at_pos, find_first_non_whitespace_char, find_root, graphemes,
@@ -33,11 +35,6 @@ use helix_view::{
3335
3436use anyhow::{anyhow, bail, ensure, Context as _};
3537use fuzzy_matcher::FuzzyMatcher;
36−use helix_lsp::{
37− block_on, lsp,
38− util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
39− OffsetEncoding,
40−};
4138use insert::*;
4239use movement::Movement;
4340
@@ -2754,7 +2751,7 @@ pub mod cmd {
27542751 // TODO: support no frame_id
27552752
27562753 let frame_id = debugger.stack_frames[&thread_id][frame].id;
2757− let response = block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
2754+ let response = helix_lsp::block_on(debugger.eval(args.join(" "), Some(frame_id)))?;
27582755 cx.editor.set_status(response.result);
27592756 }
27602757 Ok(())
@@ -3474,217 +3471,6 @@ fn buffer_picker(cx: &mut Context) {
34743471 cx.push_layer(Box::new(overlayed(picker)));
34753472}
34763473
3477−fn sym_picker(
3478− symbols: Vec<lsp::SymbolInformation>,
3479− current_path: Option<lsp::Url>,
3480− offset_encoding: OffsetEncoding,
3481−) -> FilePicker<lsp::SymbolInformation> {
3482− // TODO: drop current_path comparison and instead use workspace: bool flag?
3483− let current_path2 = current_path.clone();
3484− let mut picker = FilePicker::new(
3485− symbols,
3486− move |symbol| {
3487− if current_path.as_ref() == Some(&symbol.location.uri) {
3488− symbol.name.as_str().into()
3489− } else {
3490− let path = symbol.location.uri.to_file_path().unwrap();
3491− let relative_path = helix_core::path::get_relative_path(path.as_path())
3492− .to_string_lossy()
3493− .into_owned();
3494− format!("{} ({})", &symbol.name, relative_path).into()
3495− }
3496− },
3497− move |cx, symbol, action| {
3498− if current_path2.as_ref() == Some(&symbol.location.uri) {
3499− push_jump(cx.editor);
3500− } else {
3501− let path = symbol.location.uri.to_file_path().unwrap();
3502− cx.editor.open(path, action).expect("editor.open failed");
3503− }
3504−
3505− let (view, doc) = current!(cx.editor);
3506−
3507− if let Some(range) =
3508− lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
3509− {
3510− // we flip the range so that the cursor sits on the start of the symbol
3511− // (for example start of the function).
3512− doc.set_selection(view.id, Selection::single(range.head, range.anchor));
3513− align_view(doc, view, Align::Center);
3514− }
3515− },
3516− move |_editor, symbol| {
3517− let path = symbol.location.uri.to_file_path().unwrap();
3518− let line = Some((
3519− symbol.location.range.start.line as usize,
3520− symbol.location.range.end.line as usize,
3521− ));
3522− Some((path, line))
3523− },
3524− );
3525− picker.truncate_start = false;
3526− picker
3527−}
3528−
3529−fn symbol_picker(cx: &mut Context) {
3530− fn nested_to_flat(
3531− list: &mut Vec<lsp::SymbolInformation>,
3532− file: &lsp::TextDocumentIdentifier,
3533− symbol: lsp::DocumentSymbol,
3534− ) {
3535− #[allow(deprecated)]
3536− list.push(lsp::SymbolInformation {
3537− name: symbol.name,
3538− kind: symbol.kind,
3539− tags: symbol.tags,
3540− deprecated: symbol.deprecated,
3541− location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
3542− container_name: None,
3543− });
3544− for child in symbol.children.into_iter().flatten() {
3545− nested_to_flat(list, file, child);
3546− }
3547− }
3548− let doc = doc!(cx.editor);
3549−
3550− let language_server = match doc.language_server() {
3551− Some(language_server) => language_server,
3552− None => return,
3553− };
3554− let current_url = doc.url();
3555− let offset_encoding = language_server.offset_encoding();
3556−
3557− let future = language_server.document_symbols(doc.identifier());
3558−
3559− cx.callback(
3560− future,
3561− move |editor: &mut Editor,
3562− compositor: &mut Compositor,
3563− response: Option<lsp::DocumentSymbolResponse>| {
3564− if let Some(symbols) = response {
3565− // lsp has two ways to represent symbols (flat/nested)
3566− // convert the nested variant to flat, so that we have a homogeneous list
3567− let symbols = match symbols {
3568− lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
3569− lsp::DocumentSymbolResponse::Nested(symbols) => {
3570− let doc = doc!(editor);
3571− let mut flat_symbols = Vec::new();
3572− for symbol in symbols {
3573− nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
3574− }
3575− flat_symbols
3576− }
3577− };
3578−
3579− let picker = sym_picker(symbols, current_url, offset_encoding);
3580− compositor.push(Box::new(overlayed(picker)))
3581− }
3582− },
3583− )
3584−}
3585−
3586−fn workspace_symbol_picker(cx: &mut Context) {
3587− let doc = doc!(cx.editor);
3588− let current_url = doc.url();
3589− let language_server = match doc.language_server() {
3590− Some(language_server) => language_server,
3591− None => return,
3592− };
3593− let offset_encoding = language_server.offset_encoding();
3594− let future = language_server.workspace_symbols("".to_string());
3595−
3596− cx.callback(
3597− future,
3598− move |_editor: &mut Editor,
3599− compositor: &mut Compositor,
3600− response: Option<Vec<lsp::SymbolInformation>>| {
3601− if let Some(symbols) = response {
3602− let picker = sym_picker(symbols, current_url, offset_encoding);
3603− compositor.push(Box::new(overlayed(picker)))
3604− }
3605− },
3606− )
3607−}
3608−
3609−impl ui::menu::Item for lsp::CodeActionOrCommand {
3610− fn label(&self) -> &str {
3611− match self {
3612− lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str(),
3613− lsp::CodeActionOrCommand::Command(command) => command.title.as_str(),
3614− }
3615− }
3616−}
3617−
3618−pub fn code_action(cx: &mut Context) {
3619− let (view, doc) = current!(cx.editor);
3620−
3621− let language_server = match doc.language_server() {
3622− Some(language_server) => language_server,
3623− None => return,
3624− };
3625−
3626− let range = range_to_lsp_range(
3627− doc.text(),
3628− doc.selection(view.id).primary(),
3629− language_server.offset_encoding(),
3630− );
3631−
3632− let future = language_server.code_actions(doc.identifier(), range);
3633− let offset_encoding = language_server.offset_encoding();
3634−
3635− cx.callback(
3636− future,
3637− move |editor: &mut Editor,
3638− compositor: &mut Compositor,
3639− response: Option<lsp::CodeActionResponse>| {
3640− let actions = match response {
3641− Some(a) => a,
3642− None => return,
3643− };
3644− if actions.is_empty() {
3645− editor.set_status("No code actions available");
3646− return;
3647− }
3648−
3649− let mut picker = ui::Menu::new(actions, move |editor, code_action, event| {
3650− if event != PromptEvent::Validate {
3651− return;
3652− }
3653−
3654− // always present here
3655− let code_action = code_action.unwrap();
3656−
3657− match code_action {
3658− lsp::CodeActionOrCommand::Command(command) => {
3659− log::debug!("code action command: {:?}", command);
3660− execute_lsp_command(editor, command.clone());
3661− }
3662− lsp::CodeActionOrCommand::CodeAction(code_action) => {
3663− log::debug!("code action: {:?}", code_action);
3664− if let Some(ref workspace_edit) = code_action.edit {
3665− log::debug!("edit: {:?}", workspace_edit);
3666− apply_workspace_edit(editor, offset_encoding, workspace_edit);
3667− }
3668−
3669− // if code action provides both edit and command first the edit
3670− // should be applied and then the command
3671− if let Some(command) = &code_action.command {
3672− execute_lsp_command(editor, command.clone());
3673− }
3674− }
3675− }
3676− });
3677− picker.move_down(); // pre-select the first item
3678−
3679− let popup = Popup::new("code-action", picker).margin(helix_view::graphics::Margin {
3680− vertical: 1,
3681− horizontal: 1,
3682− });
3683− compositor.replace_or_push("code-action", Box::new(popup));
3684− },
3685− )
3686−}
3687−
36883474pub fn command_palette(cx: &mut Context) {
36893475 cx.callback = Some(Box::new(
36903476 move |compositor: &mut Compositor, cx: &mut compositor::Context| {
@@ -3748,180 +3534,6 @@ pub fn command_palette(cx: &mut Context) {
37483534 ));
37493535}
37503536
3751−pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
3752− let doc = doc!(editor);
3753− let language_server = match doc.language_server() {
3754− Some(language_server) => language_server,
3755− None => return,
3756− };
3757−
3758− // the command is executed on the server and communicated back
3759− // to the client asynchronously using workspace edits
3760− let command_future = language_server.command(cmd);
3761− tokio::spawn(async move {
3762− let res = command_future.await;
3763−
3764− if let Err(e) = res {
3765− log::error!("execute LSP command: {}", e);
3766− }
3767− });
3768−}
3769−
3770−pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
3771− use lsp::ResourceOp;
3772− use std::fs;
3773− match op {
3774− ResourceOp::Create(op) => {
3775− let path = op.uri.to_file_path().unwrap();
3776− let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
3777− !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
3778− });
3779− if ignore_if_exists && path.exists() {
3780− Ok(())
3781− } else {
3782− fs::write(&path, [])
3783− }
3784− }
3785− ResourceOp::Delete(op) => {
3786− let path = op.uri.to_file_path().unwrap();
3787− if path.is_dir() {
3788− let recursive = op
3789− .options
3790− .as_ref()
3791− .and_then(|options| options.recursive)
3792− .unwrap_or(false);
3793−
3794− if recursive {
3795− fs::remove_dir_all(&path)
3796− } else {
3797− fs::remove_dir(&path)
3798− }
3799− } else if path.is_file() {
3800− fs::remove_file(&path)
3801− } else {
3802− Ok(())
3803− }
3804− }
3805− ResourceOp::Rename(op) => {
3806− let from = op.old_uri.to_file_path().unwrap();
3807− let to = op.new_uri.to_file_path().unwrap();
3808− let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
3809− !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
3810− });
3811− if ignore_if_exists && to.exists() {
3812− Ok(())
3813− } else {
3814− fs::rename(&from, &to)
3815− }
3816− }
3817− }
3818−}
3819−
3820−pub fn apply_workspace_edit(
3821− editor: &mut Editor,
3822− offset_encoding: OffsetEncoding,
3823− workspace_edit: &lsp::WorkspaceEdit,
3824−) {
3825− let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
3826− let path = uri
3827− .to_file_path()
3828− .expect("unable to convert URI to filepath");
3829−
3830− let current_view_id = view!(editor).id;
3831− let doc_id = editor.open(path, Action::Load).unwrap();
3832− let doc = editor
3833− .document_mut(doc_id)
3834− .expect("Document for document_changes not found");
3835−
3836− // Need to determine a view for apply/append_changes_to_history
3837− let selections = doc.selections();
3838− let view_id = if selections.contains_key(&current_view_id) {
3839− // use current if possible
3840− current_view_id
3841− } else {
3842− // Hack: we take the first available view_id
3843− selections
3844− .keys()
3845− .next()
3846− .copied()
3847− .expect("No view_id available")
3848− };
3849−
3850− let transaction = helix_lsp::util::generate_transaction_from_edits(
3851− doc.text(),
3852− text_edits,
3853− offset_encoding,
3854− );
3855− doc.apply(&transaction, view_id);
3856− doc.append_changes_to_history(view_id);
3857− };
3858−
3859− if let Some(ref changes) = workspace_edit.changes {
3860− log::debug!("workspace changes: {:?}", changes);
3861− for (uri, text_edits) in changes {
3862− let text_edits = text_edits.to_vec();
3863− apply_edits(uri, text_edits);
3864− }
3865− return;
3866− // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
3867− // TODO: find some example that uses workspace changes, and test it
3868− // for (url, edits) in changes.iter() {
3869− // let file_path = url.origin().ascii_serialization();
3870− // let file_path = std::path::PathBuf::from(file_path);
3871− // let file = std::fs::File::open(file_path).unwrap();
3872− // let mut text = Rope::from_reader(file).unwrap();
3873− // let transaction = edits_to_changes(&text, edits);
3874− // transaction.apply(&mut text);
3875− // }
3876− }
3877−
3878− if let Some(ref document_changes) = workspace_edit.document_changes {
3879− match document_changes {
3880− lsp::DocumentChanges::Edits(document_edits) => {
3881− for document_edit in document_edits {
3882− let edits = document_edit
3883− .edits
3884− .iter()
3885− .map(|edit| match edit {
3886− lsp::OneOf::Left(text_edit) => text_edit,
3887− lsp::OneOf::Right(annotated_text_edit) => {
3888− &annotated_text_edit.text_edit
3889− }
3890− })
3891− .cloned()
3892− .collect();
3893− apply_edits(&document_edit.text_document.uri, edits);
3894− }
3895− }
3896− lsp::DocumentChanges::Operations(operations) => {
3897− log::debug!("document changes - operations: {:?}", operations);
3898− for operateion in operations {
3899− match operateion {
3900− lsp::DocumentChangeOperation::Op(op) => {
3901− apply_document_resource_op(op).unwrap();
3902− }
3903−
3904− lsp::DocumentChangeOperation::Edit(document_edit) => {
3905− let edits = document_edit
3906− .edits
3907− .iter()
3908− .map(|edit| match edit {
3909− lsp::OneOf::Left(text_edit) => text_edit,
3910− lsp::OneOf::Right(annotated_text_edit) => {
3911− &annotated_text_edit.text_edit
3912− }
3913− })
3914− .cloned()
3915− .collect();
3916− apply_edits(&document_edit.text_document.uri, edits);
3917− }
3918− }
3919− }
3920− }
3921− }
3922− }
3923−}
3924−
39253537fn last_picker(cx: &mut Context) {
39263538 // TODO: last picker does not seem to work well with buffer_picker
39273539 cx.callback = Some(Box::new(|compositor: &mut Compositor, _| {
@@ -4250,247 +3862,6 @@ fn exit_select_mode(cx: &mut Context) {
42503862 }
42513863}
42523864
4253−fn goto_impl(
4254− editor: &mut Editor,
4255− compositor: &mut Compositor,
4256− locations: Vec<lsp::Location>,
4257− offset_encoding: OffsetEncoding,
4258−) {
4259− push_jump(editor);
4260−
4261− // TODO: share with symbol picker(symbol.location)
4262− fn jump_to(
4263− editor: &mut Editor,
4264− location: &lsp::Location,
4265− offset_encoding: OffsetEncoding,
4266− action: Action,
4267− ) {
4268− let path = location
4269− .uri
4270− .to_file_path()
4271− .expect("unable to convert URI to filepath");
4272− let _id = editor.open(path, action).expect("editor.open failed");
4273− let (view, doc) = current!(editor);
4274− let definition_pos = location.range.start;
4275− // TODO: convert inside server
4276− let new_pos =
4277− if let Some(new_pos) = lsp_pos_to_pos(doc.text(), definition_pos, offset_encoding) {
4278− new_pos
4279− } else {
4280− return;
4281− };
4282− doc.set_selection(view.id, Selection::point(new_pos));
4283− align_view(doc, view, Align::Center);
4284− }
4285−
4286− let cwdir = std::env::current_dir().expect("couldn't determine current directory");
4287−
4288− match locations.as_slice() {
4289− [location] => {
4290− jump_to(editor, location, offset_encoding, Action::Replace);
4291− }
4292− [] => {
4293− editor.set_error("No definition found.");
4294− }
4295− _locations => {
4296− let picker = FilePicker::new(
4297− locations,
4298− move |location| {
4299− let file: Cow<'_, str> = (location.uri.scheme() == "file")
4300− .then(|| {
4301− location
4302− .uri
4303− .to_file_path()
4304− .map(|path| {
4305− // strip root prefix
4306− path.strip_prefix(&cwdir)
4307− .map(|path| path.to_path_buf())
4308− .unwrap_or(path)
4309− })
4310− .map(|path| Cow::from(path.to_string_lossy().into_owned()))
4311− .ok()
4312− })
4313− .flatten()
4314− .unwrap_or_else(|| location.uri.as_str().into());
4315− let line = location.range.start.line;
4316− format!("{}:{}", file, line).into()
4317− },
4318− move |cx, location, action| jump_to(cx.editor, location, offset_encoding, action),
4319− |_editor, location| {
4320− // TODO: share code for symbol.location and location
4321− let path = location.uri.to_file_path().unwrap();
4322− let line = Some((
4323− location.range.start.line as usize,
4324− location.range.end.line as usize,
4325− ));
4326− Some((path, line))
4327− },
4328− );
4329− compositor.push(Box::new(overlayed(picker)));
4330− }
4331− }
4332−}
4333−
4334−fn goto_definition(cx: &mut Context) {
4335− let (view, doc) = current!(cx.editor);
4336− let language_server = match doc.language_server() {
4337− Some(language_server) => language_server,
4338− None => return,
4339− };
4340−
4341− let offset_encoding = language_server.offset_encoding();
4342−
4343− let pos = pos_to_lsp_pos(
4344− doc.text(),
4345− doc.selection(view.id)
4346− .primary()
4347− .cursor(doc.text().slice(..)),
4348− offset_encoding,
4349− );
4350−
4351− let future = language_server.goto_definition(doc.identifier(), pos, None);
4352−
4353− cx.callback(
4354− future,
4355− move |editor: &mut Editor,
4356− compositor: &mut Compositor,
4357− response: Option<lsp::GotoDefinitionResponse>| {
4358− let items = match response {
4359− Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
4360− Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
4361− Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
4362− .into_iter()
4363− .map(|location_link| lsp::Location {
4364− uri: location_link.target_uri,
4365− range: location_link.target_range,
4366− })
4367− .collect(),
4368− None => Vec::new(),
4369− };
4370−
4371− goto_impl(editor, compositor, items, offset_encoding);
4372− },
4373− );
4374−}
4375−
4376−fn goto_type_definition(cx: &mut Context) {
4377− let (view, doc) = current!(cx.editor);
4378− let language_server = match doc.language_server() {
4379− Some(language_server) => language_server,
4380− None => return,
4381− };
4382−
4383− let offset_encoding = language_server.offset_encoding();
4384−
4385− let pos = pos_to_lsp_pos(
4386− doc.text(),
4387− doc.selection(view.id)
4388− .primary()
4389− .cursor(doc.text().slice(..)),
4390− offset_encoding,
4391− );
4392−
4393− let future = language_server.goto_type_definition(doc.identifier(), pos, None);
4394−
4395− cx.callback(
4396− future,
4397− move |editor: &mut Editor,
4398− compositor: &mut Compositor,
4399− response: Option<lsp::GotoDefinitionResponse>| {
4400− let items = match response {
4401− Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
4402− Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
4403− Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
4404− .into_iter()
4405− .map(|location_link| lsp::Location {
4406− uri: location_link.target_uri,
4407− range: location_link.target_range,
4408− })
4409− .collect(),
4410− None => Vec::new(),
4411− };
4412−
4413− goto_impl(editor, compositor, items, offset_encoding);
4414− },
4415− );
4416−}
4417−
4418−fn goto_implementation(cx: &mut Context) {
4419− let (view, doc) = current!(cx.editor);
4420− let language_server = match doc.language_server() {
4421− Some(language_server) => language_server,
4422− None => return,
4423− };
4424−
4425− let offset_encoding = language_server.offset_encoding();
4426−
4427− let pos = pos_to_lsp_pos(
4428− doc.text(),
4429− doc.selection(view.id)
4430− .primary()
4431− .cursor(doc.text().slice(..)),
4432− offset_encoding,
4433− );
4434−
4435− let future = language_server.goto_implementation(doc.identifier(), pos, None);
4436−
4437− cx.callback(
4438− future,
4439− move |editor: &mut Editor,
4440− compositor: &mut Compositor,
4441− response: Option<lsp::GotoDefinitionResponse>| {
4442− let items = match response {
4443− Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
4444− Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
4445− Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
4446− .into_iter()
4447− .map(|location_link| lsp::Location {
4448− uri: location_link.target_uri,
4449− range: location_link.target_range,
4450− })
4451− .collect(),
4452− None => Vec::new(),
4453− };
4454−
4455− goto_impl(editor, compositor, items, offset_encoding);
4456− },
4457− );
4458−}
4459−
4460−fn goto_reference(cx: &mut Context) {
4461− let (view, doc) = current!(cx.editor);
4462− let language_server = match doc.language_server() {
4463− Some(language_server) => language_server,
4464− None => return,
4465− };
4466−
4467− let offset_encoding = language_server.offset_encoding();
4468−
4469− let pos = pos_to_lsp_pos(
4470− doc.text(),
4471− doc.selection(view.id)
4472− .primary()
4473− .cursor(doc.text().slice(..)),
4474− offset_encoding,
4475− );
4476−
4477− let future = language_server.goto_reference(doc.identifier(), pos, None);
4478−
4479− cx.callback(
4480− future,
4481− move |editor: &mut Editor,
4482− compositor: &mut Compositor,
4483− items: Option<Vec<lsp::Location>>| {
4484− goto_impl(
4485− editor,
4486− compositor,
4487− items.unwrap_or_default(),
4488− offset_encoding,
4489− );
4490− },
4491− );
4492−}
4493−
44943865fn goto_pos(editor: &mut Editor, pos: usize) {
44953866 push_jump(editor);
44963867
@@ -4565,46 +3936,6 @@ fn goto_prev_diag(cx: &mut Context) {
45653936 goto_pos(editor, pos);
45663937}
45673938
4568−fn signature_help(cx: &mut Context) {
4569− let (view, doc) = current!(cx.editor);
4570−
4571− let language_server = match doc.language_server() {
4572− Some(language_server) => language_server,
4573− None => return,
4574− };
4575−
4576− let pos = pos_to_lsp_pos(
4577− doc.text(),
4578− doc.selection(view.id)
4579− .primary()
4580− .cursor(doc.text().slice(..)),
4581− language_server.offset_encoding(),
4582− );
4583−
4584− let future = language_server.text_document_signature_help(doc.identifier(), pos, None);
4585−
4586− cx.callback(
4587− future,
4588− move |_editor: &mut Editor,
4589− _compositor: &mut Compositor,
4590− response: Option<lsp::SignatureHelp>| {
4591− if let Some(signature_help) = response {
4592− log::info!("{:?}", signature_help);
4593− // signatures
4594− // active_signature
4595− // active_parameter
4596− // render as:
4597−
4598− // signature
4599− // ----------
4600− // doc
4601−
4602− // with active param highlighted
4603− }
4604− },
4605− );
4606−}
4607−
46083939pub mod insert {
46093940 use super::*;
46103941 pub type Hook = fn(&Rope, &Selection, char) -> Option<Transaction>;
@@ -4630,6 +3961,7 @@ pub mod insert {
46303961 }
46313962
46323963 fn language_server_completion(cx: &mut Context, ch: char) {
3964+ use helix_lsp::lsp;
46333965 // if ch matches completion char, trigger completion
46343966 let doc = doc_mut!(cx.editor);
46353967 let language_server = match doc.language_server() {
@@ -4653,6 +3985,7 @@ pub mod insert {
46533985 }
46543986
46553987 fn signature_help(cx: &mut Context, ch: char) {
3988+ use helix_lsp::lsp;
46563989 // if ch matches signature_help char, trigger
46573990 let doc = doc_mut!(cx.editor);
46583991 let language_server = match doc.language_server() {
@@ -5360,6 +4693,8 @@ fn unindent(cx: &mut Context) {
53604693}
53614694
53624695fn format_selections(cx: &mut Context) {
4696+ use helix_lsp::{lsp, util::range_to_lsp_range};
4697+
53634698 let (view, doc) = current!(cx.editor);
53644699
53654700 // via lsp if available
@@ -5504,6 +4839,8 @@ fn remove_primary_selection(cx: &mut Context) {
55044839}
55054840
55064841pub fn completion(cx: &mut Context) {
4842+ use helix_lsp::{lsp, util::pos_to_lsp_pos};
4843+
55074844 // trigger on trigger char, or if user calls it
55084845 // (or on word char typing??)
55094846 // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
@@ -5618,66 +4955,6 @@ pub fn completion(cx: &mut Context) {
56184955 );
56194956}
56204957
5621−fn hover(cx: &mut Context) {
5622− let (view, doc) = current!(cx.editor);
5623−
5624− let language_server = match doc.language_server() {
5625− Some(language_server) => language_server,
5626− None => return,
5627− };
5628−
5629− // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
5630−
5631− let pos = pos_to_lsp_pos(
5632− doc.text(),
5633− doc.selection(view.id)
5634− .primary()
5635− .cursor(doc.text().slice(..)),
5636− language_server.offset_encoding(),
5637− );
5638−
5639− let future = language_server.text_document_hover(doc.identifier(), pos, None);
5640−
5641− cx.callback(
5642− future,
5643− move |editor: &mut Editor, compositor: &mut Compositor, response: Option<lsp::Hover>| {
5644− if let Some(hover) = response {
5645− // hover.contents / .range <- used for visualizing
5646−
5647− fn marked_string_to_markdown(contents: lsp::MarkedString) -> String {
5648− match contents {
5649− lsp::MarkedString::String(contents) => contents,
5650− lsp::MarkedString::LanguageString(string) => {
5651− if string.language == "markdown" {
5652− string.value
5653− } else {
5654− format!("```{}\n{}\n```", string.language, string.value)
5655− }
5656− }
5657− }
5658− }
5659−
5660− let contents = match hover.contents {
5661− lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents),
5662− lsp::HoverContents::Array(contents) => contents
5663− .into_iter()
5664− .map(marked_string_to_markdown)
5665− .collect::<Vec<_>>()
5666− .join("\n\n"),
5667− lsp::HoverContents::Markup(contents) => contents.value,
5668− };
5669−
5670− // skip if contents empty
5671−
5672− let contents =
5673− ui::Markdown::new(contents, editor.syn_loader.clone()).style_group("hover");
5674− let popup = Popup::new("hover", contents);
5675− compositor.replace_or_push("hover", Box::new(popup));
5676− }
5677− },
5678− );
5679−}
5680−
56814958// comments
56824959fn toggle_comments(cx: &mut Context) {
56834960 let (view, doc) = current!(cx.editor);
@@ -6406,43 +5683,6 @@ fn add_newline_impl(cx: &mut Context, open: Open) {
64065683 doc.apply(&transaction, view.id);
64075684}
64085685
6409−fn rename_symbol(cx: &mut Context) {
6410− let prompt = Prompt::new(
6411− "rename-to:".into(),
6412− None,
6413− ui::completers::none,
6414− move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
6415− if event != PromptEvent::Validate {
6416− return;
6417− }
6418−
6419− log::debug!("renaming to: {:?}", input);
6420−
6421− let (view, doc) = current!(cx.editor);
6422− let language_server = match doc.language_server() {
6423− Some(language_server) => language_server,
6424− None => return,
6425− };
6426−
6427− let offset_encoding = language_server.offset_encoding();
6428−
6429− let pos = pos_to_lsp_pos(
6430− doc.text(),
6431− doc.selection(view.id)
6432− .primary()
6433− .cursor(doc.text().slice(..)),
6434− offset_encoding,
6435− );
6436−
6437− let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
6438− let edits = block_on(task).unwrap_or_default();
6439− log::debug!("Edits from LSP: {:?}", edits);
6440− apply_workspace_edit(cx.editor, offset_encoding, &edits);
6441− },
6442− );
6443− cx.push_layer(Box::new(prompt));
6444−}
6445−
64465686/// Increment object under cursor by count.
64475687fn increment(cx: &mut Context) {
64485688 increment_impl(cx, cx.count() as i64);
helix-term/src/commands/lsp.rsnew file+776
@@ -0,0 +1,776 @@
1+use helix_lsp::{
2+ block_on, lsp,
3+ util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
4+ OffsetEncoding,
5+};
6+
7+use super::{align_view, push_jump, Align, Context, Editor};
8+
9+use helix_core::Selection;
10+use helix_view::editor::Action;
11+
12+use crate::{
13+ compositor::{self, Compositor},
14+ ui::{self, overlay::overlayed, FilePicker, Popup, Prompt, PromptEvent},
15+};
16+
17+use std::borrow::Cow;
18+
19+fn sym_picker(
20+ symbols: Vec<lsp::SymbolInformation>,
21+ current_path: Option<lsp::Url>,
22+ offset_encoding: OffsetEncoding,
23+) -> FilePicker<lsp::SymbolInformation> {
24+ // TODO: drop current_path comparison and instead use workspace: bool flag?
25+ let current_path2 = current_path.clone();
26+ let mut picker = FilePicker::new(
27+ symbols,
28+ move |symbol| {
29+ if current_path.as_ref() == Some(&symbol.location.uri) {
30+ symbol.name.as_str().into()
31+ } else {
32+ let path = symbol.location.uri.to_file_path().unwrap();
33+ let relative_path = helix_core::path::get_relative_path(path.as_path())
34+ .to_string_lossy()
35+ .into_owned();
36+ format!("{} ({})", &symbol.name, relative_path).into()
37+ }
38+ },
39+ move |cx, symbol, action| {
40+ if current_path2.as_ref() == Some(&symbol.location.uri) {
41+ push_jump(cx.editor);
42+ } else {
43+ let path = symbol.location.uri.to_file_path().unwrap();
44+ cx.editor.open(path, action).expect("editor.open failed");
45+ }
46+
47+ let (view, doc) = current!(cx.editor);
48+
49+ if let Some(range) =
50+ lsp_range_to_range(doc.text(), symbol.location.range, offset_encoding)
51+ {
52+ // we flip the range so that the cursor sits on the start of the symbol
53+ // (for example start of the function).
54+ doc.set_selection(view.id, Selection::single(range.head, range.anchor));
55+ align_view(doc, view, Align::Center);
56+ }
57+ },
58+ move |_editor, symbol| {
59+ let path = symbol.location.uri.to_file_path().unwrap();
60+ let line = Some((
61+ symbol.location.range.start.line as usize,
62+ symbol.location.range.end.line as usize,
63+ ));
64+ Some((path, line))
65+ },
66+ );
67+ picker.truncate_start = false;
68+ picker
69+}
70+
71+pub fn symbol_picker(cx: &mut Context) {
72+ fn nested_to_flat(
73+ list: &mut Vec<lsp::SymbolInformation>,
74+ file: &lsp::TextDocumentIdentifier,
75+ symbol: lsp::DocumentSymbol,
76+ ) {
77+ #[allow(deprecated)]
78+ list.push(lsp::SymbolInformation {
79+ name: symbol.name,
80+ kind: symbol.kind,
81+ tags: symbol.tags,
82+ deprecated: symbol.deprecated,
83+ location: lsp::Location::new(file.uri.clone(), symbol.selection_range),
84+ container_name: None,
85+ });
86+ for child in symbol.children.into_iter().flatten() {
87+ nested_to_flat(list, file, child);
88+ }
89+ }
90+ let doc = doc!(cx.editor);
91+
92+ let language_server = match doc.language_server() {
93+ Some(language_server) => language_server,
94+ None => return,
95+ };
96+ let current_url = doc.url();
97+ let offset_encoding = language_server.offset_encoding();
98+
99+ let future = language_server.document_symbols(doc.identifier());
100+
101+ cx.callback(
102+ future,
103+ move |editor: &mut Editor,
104+ compositor: &mut Compositor,
105+ response: Option<lsp::DocumentSymbolResponse>| {
106+ if let Some(symbols) = response {
107+ // lsp has two ways to represent symbols (flat/nested)
108+ // convert the nested variant to flat, so that we have a homogeneous list
109+ let symbols = match symbols {
110+ lsp::DocumentSymbolResponse::Flat(symbols) => symbols,
111+ lsp::DocumentSymbolResponse::Nested(symbols) => {
112+ let doc = doc!(editor);
113+ let mut flat_symbols = Vec::new();
114+ for symbol in symbols {
115+ nested_to_flat(&mut flat_symbols, &doc.identifier(), symbol)
116+ }
117+ flat_symbols
118+ }
119+ };
120+
121+ let picker = sym_picker(symbols, current_url, offset_encoding);
122+ compositor.push(Box::new(overlayed(picker)))
123+ }
124+ },
125+ )
126+}
127+
128+pub fn workspace_symbol_picker(cx: &mut Context) {
129+ let doc = doc!(cx.editor);
130+ let current_url = doc.url();
131+ let language_server = match doc.language_server() {
132+ Some(language_server) => language_server,
133+ None => return,
134+ };
135+ let offset_encoding = language_server.offset_encoding();
136+ let future = language_server.workspace_symbols("".to_string());
137+
138+ cx.callback(
139+ future,
140+ move |_editor: &mut Editor,
141+ compositor: &mut Compositor,
142+ response: Option<Vec<lsp::SymbolInformation>>| {
143+ if let Some(symbols) = response {
144+ let picker = sym_picker(symbols, current_url, offset_encoding);
145+ compositor.push(Box::new(overlayed(picker)))
146+ }
147+ },
148+ )
149+}
150+
151+impl ui::menu::Item for lsp::CodeActionOrCommand {
152+ fn label(&self) -> &str {
153+ match self {
154+ lsp::CodeActionOrCommand::CodeAction(action) => action.title.as_str(),
155+ lsp::CodeActionOrCommand::Command(command) => command.title.as_str(),
156+ }
157+ }
158+}
159+
160+pub fn code_action(cx: &mut Context) {
161+ let (view, doc) = current!(cx.editor);
162+
163+ let language_server = match doc.language_server() {
164+ Some(language_server) => language_server,
165+ None => return,
166+ };
167+
168+ let range = range_to_lsp_range(
169+ doc.text(),
170+ doc.selection(view.id).primary(),
171+ language_server.offset_encoding(),
172+ );
173+
174+ let future = language_server.code_actions(doc.identifier(), range);
175+ let offset_encoding = language_server.offset_encoding();
176+
177+ cx.callback(
178+ future,
179+ move |editor: &mut Editor,
180+ compositor: &mut Compositor,
181+ response: Option<lsp::CodeActionResponse>| {
182+ let actions = match response {
183+ Some(a) => a,
184+ None => return,
185+ };
186+ if actions.is_empty() {
187+ editor.set_status("No code actions available");
188+ return;
189+ }
190+
191+ let mut picker = ui::Menu::new(actions, move |editor, code_action, event| {
192+ if event != PromptEvent::Validate {
193+ return;
194+ }
195+
196+ // always present here
197+ let code_action = code_action.unwrap();
198+
199+ match code_action {
200+ lsp::CodeActionOrCommand::Command(command) => {
201+ log::debug!("code action command: {:?}", command);
202+ execute_lsp_command(editor, command.clone());
203+ }
204+ lsp::CodeActionOrCommand::CodeAction(code_action) => {
205+ log::debug!("code action: {:?}", code_action);
206+ if let Some(ref workspace_edit) = code_action.edit {
207+ log::debug!("edit: {:?}", workspace_edit);
208+ apply_workspace_edit(editor, offset_encoding, workspace_edit);
209+ }
210+
211+ // if code action provides both edit and command first the edit
212+ // should be applied and then the command
213+ if let Some(command) = &code_action.command {
214+ execute_lsp_command(editor, command.clone());
215+ }
216+ }
217+ }
218+ });
219+ picker.move_down(); // pre-select the first item
220+
221+ let popup = Popup::new("code-action", picker).margin(helix_view::graphics::Margin {
222+ vertical: 1,
223+ horizontal: 1,
224+ });
225+ compositor.replace_or_push("code-action", Box::new(popup));
226+ },
227+ )
228+}
229+pub fn execute_lsp_command(editor: &mut Editor, cmd: lsp::Command) {
230+ let doc = doc!(editor);
231+ let language_server = match doc.language_server() {
232+ Some(language_server) => language_server,
233+ None => return,
234+ };
235+
236+ // the command is executed on the server and communicated back
237+ // to the client asynchronously using workspace edits
238+ let command_future = language_server.command(cmd);
239+ tokio::spawn(async move {
240+ let res = command_future.await;
241+
242+ if let Err(e) = res {
243+ log::error!("execute LSP command: {}", e);
244+ }
245+ });
246+}
247+
248+pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
249+ use lsp::ResourceOp;
250+ use std::fs;
251+ match op {
252+ ResourceOp::Create(op) => {
253+ let path = op.uri.to_file_path().unwrap();
254+ let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
255+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
256+ });
257+ if ignore_if_exists && path.exists() {
258+ Ok(())
259+ } else {
260+ fs::write(&path, [])
261+ }
262+ }
263+ ResourceOp::Delete(op) => {
264+ let path = op.uri.to_file_path().unwrap();
265+ if path.is_dir() {
266+ let recursive = op
267+ .options
268+ .as_ref()
269+ .and_then(|options| options.recursive)
270+ .unwrap_or(false);
271+
272+ if recursive {
273+ fs::remove_dir_all(&path)
274+ } else {
275+ fs::remove_dir(&path)
276+ }
277+ } else if path.is_file() {
278+ fs::remove_file(&path)
279+ } else {
280+ Ok(())
281+ }
282+ }
283+ ResourceOp::Rename(op) => {
284+ let from = op.old_uri.to_file_path().unwrap();
285+ let to = op.new_uri.to_file_path().unwrap();
286+ let ignore_if_exists = op.options.as_ref().map_or(false, |options| {
287+ !options.overwrite.unwrap_or(false) && options.ignore_if_exists.unwrap_or(false)
288+ });
289+ if ignore_if_exists && to.exists() {
290+ Ok(())
291+ } else {
292+ fs::rename(&from, &to)
293+ }
294+ }
295+ }
296+}
297+
298+pub fn apply_workspace_edit(
299+ editor: &mut Editor,
300+ offset_encoding: OffsetEncoding,
301+ workspace_edit: &lsp::WorkspaceEdit,
302+) {
303+ let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
304+ let path = uri
305+ .to_file_path()
306+ .expect("unable to convert URI to filepath");
307+
308+ let current_view_id = view!(editor).id;
309+ let doc_id = editor.open(path, Action::Load).unwrap();
310+ let doc = editor
311+ .document_mut(doc_id)
312+ .expect("Document for document_changes not found");
313+
314+ // Need to determine a view for apply/append_changes_to_history
315+ let selections = doc.selections();
316+ let view_id = if selections.contains_key(&current_view_id) {
317+ // use current if possible
318+ current_view_id
319+ } else {
320+ // Hack: we take the first available view_id
321+ selections
322+ .keys()
323+ .next()
324+ .copied()
325+ .expect("No view_id available")
326+ };
327+
328+ let transaction = helix_lsp::util::generate_transaction_from_edits(
329+ doc.text(),
330+ text_edits,
331+ offset_encoding,
332+ );
333+ doc.apply(&transaction, view_id);
334+ doc.append_changes_to_history(view_id);
335+ };
336+
337+ if let Some(ref changes) = workspace_edit.changes {
338+ log::debug!("workspace changes: {:?}", changes);
339+ for (uri, text_edits) in changes {
340+ let text_edits = text_edits.to_vec();
341+ apply_edits(uri, text_edits);
342+ }
343+ return;
344+ // Not sure if it works properly, it'll be safer to just panic here to avoid breaking some parts of code on which code actions will be used
345+ // TODO: find some example that uses workspace changes, and test it
346+ // for (url, edits) in changes.iter() {
347+ // let file_path = url.origin().ascii_serialization();
348+ // let file_path = std::path::PathBuf::from(file_path);
349+ // let file = std::fs::File::open(file_path).unwrap();
350+ // let mut text = Rope::from_reader(file).unwrap();
351+ // let transaction = edits_to_changes(&text, edits);
352+ // transaction.apply(&mut text);
353+ // }
354+ }
355+
356+ if let Some(ref document_changes) = workspace_edit.document_changes {
357+ match document_changes {
358+ lsp::DocumentChanges::Edits(document_edits) => {
359+ for document_edit in document_edits {
360+ let edits = document_edit
361+ .edits
362+ .iter()
363+ .map(|edit| match edit {
364+ lsp::OneOf::Left(text_edit) => text_edit,
365+ lsp::OneOf::Right(annotated_text_edit) => {
366+ &annotated_text_edit.text_edit
367+ }
368+ })
369+ .cloned()
370+ .collect();
371+ apply_edits(&document_edit.text_document.uri, edits);
372+ }
373+ }
374+ lsp::DocumentChanges::Operations(operations) => {
375+ log::debug!("document changes - operations: {:?}", operations);
376+ for operateion in operations {
377+ match operateion {
378+ lsp::DocumentChangeOperation::Op(op) => {
379+ apply_document_resource_op(op).unwrap();
380+ }
381+
382+ lsp::DocumentChangeOperation::Edit(document_edit) => {
383+ let edits = document_edit
384+ .edits
385+ .iter()
386+ .map(|edit| match edit {
387+ lsp::OneOf::Left(text_edit) => text_edit,
388+ lsp::OneOf::Right(annotated_text_edit) => {
389+ &annotated_text_edit.text_edit
390+ }
391+ })
392+ .cloned()
393+ .collect();
394+ apply_edits(&document_edit.text_document.uri, edits);
395+ }
396+ }
397+ }
398+ }
399+ }
400+ }
401+}
402+fn goto_impl(
403+ editor: &mut Editor,
404+ compositor: &mut Compositor,
405+ locations: Vec<lsp::Location>,
406+ offset_encoding: OffsetEncoding,
407+) {
408+ push_jump(editor);
409+
410+ // TODO: share with symbol picker(symbol.location)
411+ fn jump_to(
412+ editor: &mut Editor,
413+ location: &lsp::Location,
414+ offset_encoding: OffsetEncoding,
415+ action: Action,
416+ ) {
417+ let path = location
418+ .uri
419+ .to_file_path()
420+ .expect("unable to convert URI to filepath");
421+ let _id = editor.open(path, action).expect("editor.open failed");
422+ let (view, doc) = current!(editor);
423+ let definition_pos = location.range.start;
424+ // TODO: convert inside server
425+ let new_pos =
426+ if let Some(new_pos) = lsp_pos_to_pos(doc.text(), definition_pos, offset_encoding) {
427+ new_pos
428+ } else {
429+ return;
430+ };
431+ doc.set_selection(view.id, Selection::point(new_pos));
432+ align_view(doc, view, Align::Center);
433+ }
434+
435+ let cwdir = std::env::current_dir().expect("couldn't determine current directory");
436+
437+ match locations.as_slice() {
438+ [location] => {
439+ jump_to(editor, location, offset_encoding, Action::Replace);
440+ }
441+ [] => {
442+ editor.set_error("No definition found.");
443+ }
444+ _locations => {
445+ let picker = FilePicker::new(
446+ locations,
447+ move |location| {
448+ let file: Cow<'_, str> = (location.uri.scheme() == "file")
449+ .then(|| {
450+ location
451+ .uri
452+ .to_file_path()
453+ .map(|path| {
454+ // strip root prefix
455+ path.strip_prefix(&cwdir)
456+ .map(|path| path.to_path_buf())
457+ .unwrap_or(path)
458+ })
459+ .map(|path| Cow::from(path.to_string_lossy().into_owned()))
460+ .ok()
461+ })
462+ .flatten()
463+ .unwrap_or_else(|| location.uri.as_str().into());
464+ let line = location.range.start.line;
465+ format!("{}:{}", file, line).into()
466+ },
467+ move |cx, location, action| jump_to(cx.editor, location, offset_encoding, action),
468+ |_editor, location| {
469+ // TODO: share code for symbol.location and location
470+ let path = location.uri.to_file_path().unwrap();
471+ let line = Some((
472+ location.range.start.line as usize,
473+ location.range.end.line as usize,
474+ ));
475+ Some((path, line))
476+ },
477+ );
478+ compositor.push(Box::new(overlayed(picker)));
479+ }
480+ }
481+}
482+
483+pub fn goto_definition(cx: &mut Context) {
484+ let (view, doc) = current!(cx.editor);
485+ let language_server = match doc.language_server() {
486+ Some(language_server) => language_server,
487+ None => return,
488+ };
489+
490+ let offset_encoding = language_server.offset_encoding();
491+
492+ let pos = pos_to_lsp_pos(
493+ doc.text(),
494+ doc.selection(view.id)
495+ .primary()
496+ .cursor(doc.text().slice(..)),
497+ offset_encoding,
498+ );
499+
500+ let future = language_server.goto_definition(doc.identifier(), pos, None);
501+
502+ cx.callback(
503+ future,
504+ move |editor: &mut Editor,
505+ compositor: &mut Compositor,
506+ response: Option<lsp::GotoDefinitionResponse>| {
507+ let items = match response {
508+ Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
509+ Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
510+ Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
511+ .into_iter()
512+ .map(|location_link| lsp::Location {
513+ uri: location_link.target_uri,
514+ range: location_link.target_range,
515+ })
516+ .collect(),
517+ None => Vec::new(),
518+ };
519+
520+ goto_impl(editor, compositor, items, offset_encoding);
521+ },
522+ );
523+}
524+
525+pub fn goto_type_definition(cx: &mut Context) {
526+ let (view, doc) = current!(cx.editor);
527+ let language_server = match doc.language_server() {
528+ Some(language_server) => language_server,
529+ None => return,
530+ };
531+
532+ let offset_encoding = language_server.offset_encoding();
533+
534+ let pos = pos_to_lsp_pos(
535+ doc.text(),
536+ doc.selection(view.id)
537+ .primary()
538+ .cursor(doc.text().slice(..)),
539+ offset_encoding,
540+ );
541+
542+ let future = language_server.goto_type_definition(doc.identifier(), pos, None);
543+
544+ cx.callback(
545+ future,
546+ move |editor: &mut Editor,
547+ compositor: &mut Compositor,
548+ response: Option<lsp::GotoDefinitionResponse>| {
549+ let items = match response {
550+ Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
551+ Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
552+ Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
553+ .into_iter()
554+ .map(|location_link| lsp::Location {
555+ uri: location_link.target_uri,
556+ range: location_link.target_range,
557+ })
558+ .collect(),
559+ None => Vec::new(),
560+ };
561+
562+ goto_impl(editor, compositor, items, offset_encoding);
563+ },
564+ );
565+}
566+
567+pub fn goto_implementation(cx: &mut Context) {
568+ let (view, doc) = current!(cx.editor);
569+ let language_server = match doc.language_server() {
570+ Some(language_server) => language_server,
571+ None => return,
572+ };
573+
574+ let offset_encoding = language_server.offset_encoding();
575+
576+ let pos = pos_to_lsp_pos(
577+ doc.text(),
578+ doc.selection(view.id)
579+ .primary()
580+ .cursor(doc.text().slice(..)),
581+ offset_encoding,
582+ );
583+
584+ let future = language_server.goto_implementation(doc.identifier(), pos, None);
585+
586+ cx.callback(
587+ future,
588+ move |editor: &mut Editor,
589+ compositor: &mut Compositor,
590+ response: Option<lsp::GotoDefinitionResponse>| {
591+ let items = match response {
592+ Some(lsp::GotoDefinitionResponse::Scalar(location)) => vec![location],
593+ Some(lsp::GotoDefinitionResponse::Array(locations)) => locations,
594+ Some(lsp::GotoDefinitionResponse::Link(locations)) => locations
595+ .into_iter()
596+ .map(|location_link| lsp::Location {
597+ uri: location_link.target_uri,
598+ range: location_link.target_range,
599+ })
600+ .collect(),
601+ None => Vec::new(),
602+ };
603+
604+ goto_impl(editor, compositor, items, offset_encoding);
605+ },
606+ );
607+}
608+
609+pub fn goto_reference(cx: &mut Context) {
610+ let (view, doc) = current!(cx.editor);
611+ let language_server = match doc.language_server() {
612+ Some(language_server) => language_server,
613+ None => return,
614+ };
615+
616+ let offset_encoding = language_server.offset_encoding();
617+
618+ let pos = pos_to_lsp_pos(
619+ doc.text(),
620+ doc.selection(view.id)
621+ .primary()
622+ .cursor(doc.text().slice(..)),
623+ offset_encoding,
624+ );
625+
626+ let future = language_server.goto_reference(doc.identifier(), pos, None);
627+
628+ cx.callback(
629+ future,
630+ move |editor: &mut Editor,
631+ compositor: &mut Compositor,
632+ items: Option<Vec<lsp::Location>>| {
633+ goto_impl(
634+ editor,
635+ compositor,
636+ items.unwrap_or_default(),
637+ offset_encoding,
638+ );
639+ },
640+ );
641+}
642+
643+pub fn signature_help(cx: &mut Context) {
644+ let (view, doc) = current!(cx.editor);
645+
646+ let language_server = match doc.language_server() {
647+ Some(language_server) => language_server,
648+ None => return,
649+ };
650+
651+ let pos = pos_to_lsp_pos(
652+ doc.text(),
653+ doc.selection(view.id)
654+ .primary()
655+ .cursor(doc.text().slice(..)),
656+ language_server.offset_encoding(),
657+ );
658+
659+ let future = language_server.text_document_signature_help(doc.identifier(), pos, None);
660+
661+ cx.callback(
662+ future,
663+ move |_editor: &mut Editor,
664+ _compositor: &mut Compositor,
665+ response: Option<lsp::SignatureHelp>| {
666+ if let Some(signature_help) = response {
667+ log::info!("{:?}", signature_help);
668+ // signatures
669+ // active_signature
670+ // active_parameter
671+ // render as:
672+
673+ // signature
674+ // ----------
675+ // doc
676+
677+ // with active param highlighted
678+ }
679+ },
680+ );
681+}
682+pub fn hover(cx: &mut Context) {
683+ let (view, doc) = current!(cx.editor);
684+
685+ let language_server = match doc.language_server() {
686+ Some(language_server) => language_server,
687+ None => return,
688+ };
689+
690+ // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
691+
692+ let pos = pos_to_lsp_pos(
693+ doc.text(),
694+ doc.selection(view.id)
695+ .primary()
696+ .cursor(doc.text().slice(..)),
697+ language_server.offset_encoding(),
698+ );
699+
700+ let future = language_server.text_document_hover(doc.identifier(), pos, None);
701+
702+ cx.callback(
703+ future,
704+ move |editor: &mut Editor, compositor: &mut Compositor, response: Option<lsp::Hover>| {
705+ if let Some(hover) = response {
706+ // hover.contents / .range <- used for visualizing
707+
708+ fn marked_string_to_markdown(contents: lsp::MarkedString) -> String {
709+ match contents {
710+ lsp::MarkedString::String(contents) => contents,
711+ lsp::MarkedString::LanguageString(string) => {
712+ if string.language == "markdown" {
713+ string.value
714+ } else {
715+ format!("```{}\n{}\n```", string.language, string.value)
716+ }
717+ }
718+ }
719+ }
720+
721+ let contents = match hover.contents {
722+ lsp::HoverContents::Scalar(contents) => marked_string_to_markdown(contents),
723+ lsp::HoverContents::Array(contents) => contents
724+ .into_iter()
725+ .map(marked_string_to_markdown)
726+ .collect::<Vec<_>>()
727+ .join("\n\n"),
728+ lsp::HoverContents::Markup(contents) => contents.value,
729+ };
730+
731+ // skip if contents empty
732+
733+ let contents =
734+ ui::Markdown::new(contents, editor.syn_loader.clone()).style_group("hover");
735+ let popup = Popup::new("hover", contents);
736+ compositor.replace_or_push("hover", Box::new(popup));
737+ }
738+ },
739+ );
740+}
741+pub fn rename_symbol(cx: &mut Context) {
742+ let prompt = Prompt::new(
743+ "rename-to:".into(),
744+ None,
745+ ui::completers::none,
746+ move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
747+ if event != PromptEvent::Validate {
748+ return;
749+ }
750+
751+ log::debug!("renaming to: {:?}", input);
752+
753+ let (view, doc) = current!(cx.editor);
754+ let language_server = match doc.language_server() {
755+ Some(language_server) => language_server,
756+ None => return,
757+ };
758+
759+ let offset_encoding = language_server.offset_encoding();
760+
761+ let pos = pos_to_lsp_pos(
762+ doc.text(),
763+ doc.selection(view.id)
764+ .primary()
765+ .cursor(doc.text().slice(..)),
766+ offset_encoding,
767+ );
768+
769+ let task = language_server.rename_symbol(doc.identifier(), pos, input.to_string());
770+ let edits = block_on(task).unwrap_or_default();
771+ log::debug!("Edits from LSP: {:?}", edits);
772+ apply_workspace_edit(cx.editor, offset_encoding, &edits);
773+ },
774+ );
775+ cx.push_layer(Box::new(prompt));
776+}