on master help sign in
discard outdated workspace edits recived from the LS
b6a4927Pascal Kuthe
helix-term/src/application.rs+7−4
@@ -999,16 +999,19 @@ impl Application {
999 Ok(serde_json::Value::Null)
999 Ok(serde_json::Value::Null)
1000 }
1000 }
1001 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1001 Ok(MethodCall::ApplyWorkspaceEdit(params)) => {
1002− apply_workspace_edit(
1002+ let res = apply_workspace_edit(
1003 &mut self.editor,
1003 &mut self.editor,
1004 helix_lsp::OffsetEncoding::Utf8,
1004 helix_lsp::OffsetEncoding::Utf8,
1005 &params.edit,
1005 &params.edit,
1006 );
1006 );
1007
1007
1008 Ok(json!(lsp::ApplyWorkspaceEditResponse {
1008 Ok(json!(lsp::ApplyWorkspaceEditResponse {
1009− applied: true,
1009+ applied: res.is_ok(),
1010− failure_reason: None,
1010+ failure_reason: res.as_ref().err().map(|err| err.kind.to_string()),
1011− failed_change: None,
1011+ failed_change: res
1012+ .as_ref()
1013+ .err()
1014+ .map(|err| err.failed_change_idx as u32),
1012 }))
1015 }))
1013 }
1016 }
1014 Ok(MethodCall::WorkspaceFolders) => {
1017 Ok(MethodCall::WorkspaceFolders) => {
helix-term/src/commands/lsp.rs+87−30
@@ -651,7 +651,7 @@ pub fn code_action(cx: &mut Context) {
651 log::debug!("code action: {:?}", code_action);
651 log::debug!("code action: {:?}", code_action);
652 if let Some(ref workspace_edit) = code_action.edit {
652 if let Some(ref workspace_edit) = code_action.edit {
653 log::debug!("edit: {:?}", workspace_edit);
653 log::debug!("edit: {:?}", workspace_edit);
654− apply_workspace_edit(editor, offset_encoding, workspace_edit);
654+ let _ = apply_workspace_edit(editor, offset_encoding, workspace_edit);
655 }
655 }
656
656
657 // if code action provides both edit and command first the edit
657 // if code action provides both edit and command first the edit
@@ -757,19 +757,50 @@ pub fn apply_document_resource_op(op: &lsp::ResourceOp) -> std::io::Result<()> {
757 }
757 }
758}
758}
759
759
760+#[derive(Debug)]
761+pub struct ApplyEditError {
762+ pub kind: ApplyEditErrorKind,
763+ pub failed_change_idx: usize,
764+}
765+
766+#[derive(Debug)]
767+pub enum ApplyEditErrorKind {
768+ DocumentChanged,
769+ FileNotFound,
770+ UnknownURISchema,
771+ IoError(std::io::Error),
772+ // TODO: check edits before applying and propagate failure
773+ // InvalidEdit,
774+}
775+
776+impl ToString for ApplyEditErrorKind {
777+ fn to_string(&self) -> String {
778+ match self {
779+ ApplyEditErrorKind::DocumentChanged => "document has changed".to_string(),
780+ ApplyEditErrorKind::FileNotFound => "file not found".to_string(),
781+ ApplyEditErrorKind::UnknownURISchema => "URI schema not supported".to_string(),
782+ ApplyEditErrorKind::IoError(err) => err.to_string(),
783+ }
784+ }
785+}
786+
787+///TODO make this transactional (and set failureMode to transactional)
760pub fn apply_workspace_edit(
788pub fn apply_workspace_edit(
761 editor: &mut Editor,
789 editor: &mut Editor,
762 offset_encoding: OffsetEncoding,
790 offset_encoding: OffsetEncoding,
763 workspace_edit: &lsp::WorkspaceEdit,
791 workspace_edit: &lsp::WorkspaceEdit,
764−) {
792+) -> Result<(), ApplyEditError> {
765− let mut apply_edits = |uri: &helix_lsp::Url, text_edits: Vec<lsp::TextEdit>| {
793+ let mut apply_edits = |uri: &helix_lsp::Url,
794+ version: Option<i32>,
795+ text_edits: Vec<lsp::TextEdit>|
796+ -> Result<(), ApplyEditErrorKind> {
766 let path = match uri.to_file_path() {
797 let path = match uri.to_file_path() {
767 Ok(path) => path,
798 Ok(path) => path,
768 Err(_) => {
799 Err(_) => {
769 let err = format!("unable to convert URI to filepath: {}", uri);
800 let err = format!("unable to convert URI to filepath: {}", uri);
770 log::error!("{}", err);
801 log::error!("{}", err);
771 editor.set_error(err);
802 editor.set_error(err);
772− return;
803+ return Err(ApplyEditErrorKind::UnknownURISchema);
773 }
804 }
774 };
805 };
775
806
@@ -780,11 +811,19 @@ pub fn apply_workspace_edit(
780 let err = format!("failed to open document: {}: {}", uri, err);
811 let err = format!("failed to open document: {}: {}", uri, err);
781 log::error!("{}", err);
812 log::error!("{}", err);
782 editor.set_error(err);
813 editor.set_error(err);
783− return;
814+ return Err(ApplyEditErrorKind::FileNotFound);
784 }
815 }
785 };
816 };
786
817
787 let doc = doc_mut!(editor, &doc_id);
818 let doc = doc_mut!(editor, &doc_id);
819+ if let Some(version) = version {
820+ if version != doc.version() {
821+ let err = format!("outdated workspace edit for {path:?}");
822+ log::error!("{err}, expected {} but got {version}", doc.version());
823+ editor.set_error(err);
824+ return Err(ApplyEditErrorKind::DocumentChanged);
825+ }
826+ }
788
827
789 // Need to determine a view for apply/append_changes_to_history
828 // Need to determine a view for apply/append_changes_to_history
790 let selections = doc.selections();
829 let selections = doc.selections();
@@ -808,31 +847,13 @@ pub fn apply_workspace_edit(
808 let view = view_mut!(editor, view_id);
847 let view = view_mut!(editor, view_id);
809 doc.apply(&transaction, view.id);
848 doc.apply(&transaction, view.id);
810 doc.append_changes_to_history(view);
849 doc.append_changes_to_history(view);
850+ Ok(())
811 };
851 };
812
852
813− if let Some(ref changes) = workspace_edit.changes {
814− log::debug!("workspace changes: {:?}", changes);
815− for (uri, text_edits) in changes {
816− let text_edits = text_edits.to_vec();
817− apply_edits(uri, text_edits)
818− }
819− return;
820− // 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
821− // TODO: find some example that uses workspace changes, and test it
822− // for (url, edits) in changes.iter() {
823− // let file_path = url.origin().ascii_serialization();
824− // let file_path = std::path::PathBuf::from(file_path);
825− // let file = std::fs::File::open(file_path).unwrap();
826− // let mut text = Rope::from_reader(file).unwrap();
827− // let transaction = edits_to_changes(&text, edits);
828− // transaction.apply(&mut text);
829− // }
830− }
831−
832 if let Some(ref document_changes) = workspace_edit.document_changes {
853 if let Some(ref document_changes) = workspace_edit.document_changes {
833 match document_changes {
854 match document_changes {
834 lsp::DocumentChanges::Edits(document_edits) => {
855 lsp::DocumentChanges::Edits(document_edits) => {
835− for document_edit in document_edits {
856+ for (i, document_edit) in document_edits.iter().enumerate() {
836 let edits = document_edit
857 let edits = document_edit
837 .edits
858 .edits
838 .iter()
859 .iter()
@@ -844,15 +865,26 @@ pub fn apply_workspace_edit(
844 })
865 })
845 .cloned()
866 .cloned()
846 .collect();
867 .collect();
868+ apply_edits(
847− apply_edits(&document_edit.text_document.uri, edits);
869+ &document_edit.text_document.uri,
870+ document_edit.text_document.version,
871+ edits,
872+ )
873+ .map_err(|kind| ApplyEditError {
874+ kind,
875+ failed_change_idx: i,
876+ })?;
848 }
877 }
849 }
878 }
850 lsp::DocumentChanges::Operations(operations) => {
879 lsp::DocumentChanges::Operations(operations) => {
851 log::debug!("document changes - operations: {:?}", operations);
880 log::debug!("document changes - operations: {:?}", operations);
852− for operation in operations {
881+ for (i, operation) in operations.iter().enumerate() {
853 match operation {
882 match operation {
854 lsp::DocumentChangeOperation::Op(op) => {
883 lsp::DocumentChangeOperation::Op(op) => {
855− apply_document_resource_op(op).unwrap();
884+ apply_document_resource_op(op).map_err(|io| ApplyEditError {
885+ kind: ApplyEditErrorKind::IoError(io),
886+ failed_change_idx: i,
887+ })?;
856 }
888 }
857
889
858 lsp::DocumentChangeOperation::Edit(document_edit) => {
890 lsp::DocumentChangeOperation::Edit(document_edit) => {
@@ -867,13 +899,36 @@ pub fn apply_workspace_edit(
867 })
899 })
868 .cloned()
900 .cloned()
869 .collect();
901 .collect();
902+ apply_edits(
870− apply_edits(&document_edit.text_document.uri, edits);
903+ &document_edit.text_document.uri,
904+ document_edit.text_document.version,
905+ edits,
906+ )
907+ .map_err(|kind| ApplyEditError {
908+ kind,
909+ failed_change_idx: i,
910+ })?;
871 }
911 }
872 }
912 }
873 }
913 }
874 }
914 }
875 }
915 }
916+
917+ return Ok(());
876 }
918 }
919+
920+ if let Some(ref changes) = workspace_edit.changes {
921+ log::debug!("workspace changes: {:?}", changes);
922+ for (i, (uri, text_edits)) in changes.iter().enumerate() {
923+ let text_edits = text_edits.to_vec();
924+ apply_edits(uri, None, text_edits).map_err(|kind| ApplyEditError {
925+ kind,
926+ failed_change_idx: i,
927+ })?;
928+ }
929+ }
930+
931+ Ok(())
877}
932}
878
933
879fn goto_impl(
934fn goto_impl(
@@ -1297,7 +1352,9 @@ pub fn rename_symbol(cx: &mut Context) {
1297 }
1352 }
1298 };
1353 };
1299 match block_on(future) {
1354 match block_on(future) {
1355+ Ok(edits) => {
1300− Ok(edits) => apply_workspace_edit(cx.editor, offset_encoding, &edits),
1356+ let _ = apply_workspace_edit(cx.editor, offset_encoding, &edits);
1357+ }
1301 Err(err) => cx.editor.set_error(err.to_string()),
1358 Err(err) => cx.editor.set_error(err.to_string()),
1302 }
1359 }
1303 },
1360 },