diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 6553ce16..9eaead95 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -6,7 +6,7 @@ use helix_lsp::{
         NumberOrString,
     },
     util::{diagnostic_to_lsp_diagnostic, lsp_range_to_range, range_to_lsp_range},
-    OffsetEncoding,
+    Client, OffsetEncoding,
 };
 use serde_json::Value;
 use tokio_stream::StreamExt;
@@ -1028,151 +1028,90 @@ fn to_locations(definitions: Option<lsp::GotoDefinitionResponse>) -> Vec<lsp::Lo
     }
 }
 
-// TODO find a way to reduce boilerplate of all the goto functions, without unnecessary complexity...
-pub fn goto_declaration(cx: &mut Context) {
+fn goto_single_impl<P, F>(cx: &mut Context, feature: LanguageServerFeature, request_provider: P)
+where
+    P: Fn(&Client, lsp::Position, lsp::TextDocumentIdentifier) -> Option<F>,
+    F: Future<Output = helix_lsp::Result<serde_json::Value>> + 'static + Send,
+{
     let (view, doc) = current!(cx.editor);
-    let future_offset_encoding = doc
-        .language_servers_with_feature(LanguageServerFeature::GotoDeclaration)
-        .find_map(|language_server| {
-            let offset_encoding = language_server.offset_encoding();
-            let pos = doc.position(view.id, offset_encoding);
-            let future = language_server.goto_declaration(doc.identifier(), pos, None)?;
-            Some((future, offset_encoding))
-        });
-    let (future, offset_encoding) = match future_offset_encoding {
-        Some(future_offset_encoding) => future_offset_encoding,
-        None => {
-            cx.editor
-                .set_error("No language server supports goto-declaration");
-            return;
-        }
-    };
+    if let Some((future, offset_encoding)) =
+        doc.run_on_first_supported_language_server(view.id, feature, |ls, encoding, pos, doc_id| {
+            Some((request_provider(ls, pos, doc_id)?, encoding))
+        })
+    {
+        cx.callback(
+            future,
+            move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
+                let items = to_locations(response);
+                goto_impl(editor, compositor, items, offset_encoding);
+            },
+        );
+    } else {
+        cx.editor.set_error("No language server supports {feature}");
+    }
+}
 
-    cx.callback(
-        future,
-        move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
-            let items = to_locations(response);
-            goto_impl(editor, compositor, items, offset_encoding);
-        },
+pub fn goto_declaration(cx: &mut Context) {
+    goto_single_impl(
+        cx,
+        LanguageServerFeature::GotoDeclaration,
+        |ls, pos, doc_id| ls.goto_declaration(doc_id, pos, None),
     );
 }
 
 pub fn goto_definition(cx: &mut Context) {
-    let (view, doc) = current!(cx.editor);
-    let future_offset_encoding = doc
-        .language_servers_with_feature(LanguageServerFeature::GotoDefinition)
-        .find_map(|language_server| {
-            let offset_encoding = language_server.offset_encoding();
-            let pos = doc.position(view.id, offset_encoding);
-            let future = language_server.goto_definition(doc.identifier(), pos, None)?;
-            Some((future, offset_encoding))
-        });
-    let (future, offset_encoding) = match future_offset_encoding {
-        Some(future_offset_encoding) => future_offset_encoding,
-        None => {
-            cx.editor
-                .set_error("No language server supports goto-definition");
-            return;
-        }
-    };
-
-    cx.callback(
-        future,
-        move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
-            let items = to_locations(response);
-            goto_impl(editor, compositor, items, offset_encoding);
-        },
+    goto_single_impl(
+        cx,
+        LanguageServerFeature::GotoDefinition,
+        |ls, pos, doc_id| ls.goto_definition(doc_id, pos, None),
     );
 }
 
 pub fn goto_type_definition(cx: &mut Context) {
-    let (view, doc) = current!(cx.editor);
-    let future_offset_encoding = doc
-        .language_servers_with_feature(LanguageServerFeature::GotoTypeDefinition)
-        .find_map(|language_server| {
-            let offset_encoding = language_server.offset_encoding();
-            let pos = doc.position(view.id, offset_encoding);
-            let future = language_server.goto_type_definition(doc.identifier(), pos, None)?;
-            Some((future, offset_encoding))
-        });
-    let (future, offset_encoding) = match future_offset_encoding {
-        Some(future_offset_encoding) => future_offset_encoding,
-        None => {
-            cx.editor
-                .set_error("No language server supports goto-type-definition");
-            return;
-        }
-    };
-
-    cx.callback(
-        future,
-        move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
-            let items = to_locations(response);
-            goto_impl(editor, compositor, items, offset_encoding);
-        },
+    goto_single_impl(
+        cx,
+        LanguageServerFeature::GotoTypeDefinition,
+        |ls, pos, doc_id| ls.goto_type_definition(doc_id, pos, None),
     );
 }
 
 pub fn goto_implementation(cx: &mut Context) {
-    let (view, doc) = current!(cx.editor);
-    let future_offset_encoding = doc
-        .language_servers_with_feature(LanguageServerFeature::GotoImplementation)
-        .find_map(|language_server| {
-            let offset_encoding = language_server.offset_encoding();
-            let pos = doc.position(view.id, offset_encoding);
-            let future = language_server.goto_implementation(doc.identifier(), pos, None)?;
-            Some((future, offset_encoding))
-        });
-    let (future, offset_encoding) = match future_offset_encoding {
-        Some(future_offset_encoding) => future_offset_encoding,
-        None => {
-            cx.editor
-                .set_error("No language server supports goto-implementation");
-            return;
-        }
-    };
-
-    cx.callback(
-        future,
-        move |editor, compositor, response: Option<lsp::GotoDefinitionResponse>| {
-            let items = to_locations(response);
-            goto_impl(editor, compositor, items, offset_encoding);
-        },
+    goto_single_impl(
+        cx,
+        LanguageServerFeature::GotoImplementation,
+        |ls, pos, doc_id| ls.goto_implementation(doc_id, pos, None),
     );
 }
 
 pub fn goto_reference(cx: &mut Context) {
     let config = cx.editor.config();
     let (view, doc) = current!(cx.editor);
-    let future_offset_encoding = doc
-        .language_servers_with_feature(LanguageServerFeature::GotoReference)
-        .find_map(|language_server| {
-            let offset_encoding = language_server.offset_encoding();
-            let pos = doc.position(view.id, offset_encoding);
-            let future = language_server.goto_reference(
-                doc.identifier(),
-                pos,
-                config.lsp.goto_reference_include_declaration,
-                None,
-            )?;
-            Some((future, offset_encoding))
-        });
-    let (future, offset_encoding) = match future_offset_encoding {
-        Some(future_offset_encoding) => future_offset_encoding,
-        None => {
-            cx.editor
-                .set_error("No language server supports goto-reference");
-            return;
-        }
-    };
-
-    cx.callback(
-        future,
-        move |editor, compositor, response: Option<Vec<lsp::Location>>| {
-            let items = response.unwrap_or_default();
-            goto_impl(editor, compositor, items, offset_encoding);
+    if let Some((future, offset_encoding)) = doc.run_on_first_supported_language_server(
+        view.id,
+        LanguageServerFeature::GotoReference,
+        |ls, encoding, pos, doc_id| {
+            Some((
+                ls.goto_reference(
+                    doc_id,
+                    pos,
+                    config.lsp.goto_reference_include_declaration,
+                    None,
+                )?,
+                encoding,
+            ))
         },
-    );
+    ) {
+        cx.callback(
+            future,
+            move |editor, compositor, response: Option<Vec<lsp::Location>>| {
+                let items = response.unwrap_or_default();
+                goto_impl(editor, compositor, items, offset_encoding);
+            },
+        );
+    } else {
+        cx.editor
+            .set_error("No language server supports goto-reference");
+    }
 }
 
 #[derive(PartialEq, Eq, Clone, Copy)]
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 0f800209..3fd271eb 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -580,7 +580,7 @@ where
     *mut_ref = f(mem::take(mut_ref));
 }
 
-use helix_lsp::lsp;
+use helix_lsp::{lsp, Client, OffsetEncoding};
 use url::Url;
 
 impl Document {
@@ -1460,6 +1460,23 @@ impl Document {
         self.language_servers().any(|l| l.id() == id)
     }
 
+    pub fn run_on_first_supported_language_server<T, P>(
+        &self,
+        view_id: ViewId,
+        feature: LanguageServerFeature,
+        request_provider: P,
+    ) -> Option<T>
+    where
+        P: Fn(&Client, OffsetEncoding, lsp::Position, lsp::TextDocumentIdentifier) -> Option<T>,
+    {
+        self.language_servers_with_feature(feature)
+            .find_map(|language_server| {
+                let offset_encoding = language_server.offset_encoding();
+                let pos = self.position(view_id, offset_encoding);
+                request_provider(language_server, offset_encoding, pos, self.identifier())
+            })
+    }
+
     pub fn diff_handle(&self) -> Option<&DiffHandle> {
         self.diff_handle.as_ref()
     }