diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index f45a38cc..a4e6d990 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -16,7 +16,7 @@ use slotmap::{DefaultKey as LayerId, HopSlotMap};
 use std::{
     borrow::Cow,
     cell::RefCell,
-    collections::{HashMap, VecDeque},
+    collections::{HashMap, HashSet, VecDeque},
     fmt::{self, Display},
     hash::{Hash, Hasher},
     mem::{replace, transmute},
@@ -26,7 +26,7 @@ use std::{
 };
 
 use once_cell::sync::{Lazy, OnceCell};
-use serde::{Deserialize, Serialize};
+use serde::{ser::SerializeSeq, Deserialize, Serialize};
 
 use helix_loader::grammar::{get_language, load_runtime_file};
 
@@ -110,8 +110,13 @@ pub struct LanguageConfiguration {
     #[serde(skip)]
     pub(crate) highlight_config: OnceCell<Option<Arc<HighlightConfiguration>>>,
     // tags_config OnceCell<> https://github.com/tree-sitter/tree-sitter/pull/583
-    #[serde(default, skip_serializing_if = "Vec::is_empty")]
-    pub language_servers: Vec<LanguageServerFeatureConfiguration>,
+    #[serde(
+        default,
+        skip_serializing_if = "HashMap::is_empty",
+        serialize_with = "serialize_lang_features",
+        deserialize_with = "deserialize_lang_features"
+    )]
+    pub language_servers: HashMap<String, LanguageServerFeatures>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub indent: Option<IndentationConfiguration>,
 
@@ -211,7 +216,7 @@ impl<'de> Deserialize<'de> for FileType {
     }
 }
 
-#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
 #[serde(rename_all = "kebab-case")]
 pub enum LanguageServerFeature {
     Format,
@@ -261,18 +266,81 @@ impl Display for LanguageServerFeature {
 
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(untagged, rename_all = "kebab-case", deny_unknown_fields)]
-pub enum LanguageServerFeatureConfiguration {
+enum LanguageServerFeatureConfiguration {
     #[serde(rename_all = "kebab-case")]
     Features {
-        #[serde(default, skip_serializing_if = "Vec::is_empty")]
-        only_features: Vec<LanguageServerFeature>,
-        #[serde(default, skip_serializing_if = "Vec::is_empty")]
-        except_features: Vec<LanguageServerFeature>,
+        #[serde(default, skip_serializing_if = "HashSet::is_empty")]
+        only_features: HashSet<LanguageServerFeature>,
+        #[serde(default, skip_serializing_if = "HashSet::is_empty")]
+        except_features: HashSet<LanguageServerFeature>,
         name: String,
     },
     Simple(String),
 }
 
+#[derive(Debug, Default)]
+pub struct LanguageServerFeatures {
+    pub only: HashSet<LanguageServerFeature>,
+    pub excluded: HashSet<LanguageServerFeature>,
+}
+
+impl LanguageServerFeatures {
+    pub fn has_feature(&self, feature: LanguageServerFeature) -> bool {
+        self.only.is_empty() || self.only.contains(&feature) && !self.excluded.contains(&feature)
+    }
+}
+
+fn deserialize_lang_features<'de, D>(
+    deserializer: D,
+) -> Result<HashMap<String, LanguageServerFeatures>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    let raw: Vec<LanguageServerFeatureConfiguration> = Deserialize::deserialize(deserializer)?;
+    let res = raw
+        .into_iter()
+        .map(|config| match config {
+            LanguageServerFeatureConfiguration::Simple(name) => {
+                (name, LanguageServerFeatures::default())
+            }
+            LanguageServerFeatureConfiguration::Features {
+                only_features,
+                except_features,
+                name,
+            } => (
+                name,
+                LanguageServerFeatures {
+                    only: only_features,
+                    excluded: except_features,
+                },
+            ),
+        })
+        .collect();
+    Ok(res)
+}
+fn serialize_lang_features<S>(
+    map: &HashMap<String, LanguageServerFeatures>,
+    serializer: S,
+) -> Result<S::Ok, S::Error>
+where
+    S: serde::Serializer,
+{
+    let mut serializer = serializer.serialize_seq(Some(map.len()))?;
+    for (name, features) in map {
+        let features = if features.only.is_empty() && features.excluded.is_empty() {
+            LanguageServerFeatureConfiguration::Simple(name.to_owned())
+        } else {
+            LanguageServerFeatureConfiguration::Features {
+                only_features: features.only.clone(),
+                except_features: features.excluded.clone(),
+                name: name.to_owned(),
+            }
+        };
+        serializer.serialize_element(&features)?;
+    }
+    serializer.end()
+}
+
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "kebab-case")]
 pub struct LanguageServerConfiguration {
@@ -650,15 +718,6 @@ pub struct SoftWrap {
     pub wrap_at_text_width: Option<bool>,
 }
 
-impl LanguageServerFeatureConfiguration {
-    pub fn name(&self) -> &String {
-        match self {
-            LanguageServerFeatureConfiguration::Simple(name) => name,
-            LanguageServerFeatureConfiguration::Features { name, .. } => name,
-        }
-    }
-}
-
 // Expose loader as Lazy<> global since it's always static?
 
 #[derive(Debug)]
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 12e63255..ba0c3fee 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -689,12 +689,10 @@ impl Registry {
     ) -> Result<Vec<Arc<Client>>> {
         language_config
             .language_servers
-            .iter()
-            .filter_map(|config| {
-                let name = config.name().clone();
-
+            .keys()
+            .filter_map(|name| {
                 #[allow(clippy::map_entry)]
-                if self.inner.contains_key(&name) {
+                if self.inner.contains_key(name) {
                     let client = match self.start_client(
                         name.clone(),
                         language_config,
@@ -705,7 +703,10 @@ impl Registry {
                         Ok(client) => client,
                         error => return Some(error),
                     };
-                    let old_clients = self.inner.insert(name, vec![client.clone()]).unwrap();
+                    let old_clients = self
+                        .inner
+                        .insert(name.clone(), vec![client.clone()])
+                        .unwrap();
 
                     // TODO what if there are different language servers for different workspaces,
                     // I think the language servers will be stopped without being restarted, which is not intended
@@ -742,9 +743,8 @@ impl Registry {
     ) -> Result<Vec<Arc<Client>>> {
         language_config
             .language_servers
-            .iter()
-            .map(|features| {
-                let name = features.name();
+            .keys()
+            .map(|name| {
                 if let Some(clients) = self.inner.get_mut(name) {
                     if let Some((_, client)) = clients.iter_mut().enumerate().find(|(i, client)| {
                         client.try_add_doc(&language_config.roots, root_dirs, doc_path, *i == 0)
@@ -759,7 +759,7 @@ impl Registry {
                     root_dirs,
                     enable_snippets,
                 )?;
-                let clients = self.inner.entry(features.name().clone()).or_default();
+                let clients = self.inner.entry(name.clone()).or_default();
                 clients.push(client.clone());
                 Ok(client)
             })
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 728aa46a..83473179 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -699,9 +699,10 @@ impl Application {
                             tokio::spawn(language_server.did_change_configuration(config.clone()));
                         }
 
-                        let docs = self.editor.documents().filter(|doc| {
-                            doc.language_servers().iter().any(|l| l.id() == server_id)
-                        });
+                        let docs = self
+                            .editor
+                            .documents()
+                            .filter(|doc| doc.language_servers().any(|l| l.id() == server_id));
 
                         // trigger textDocument/didOpen for docs that are already open
                         for doc in docs {
@@ -970,7 +971,6 @@ impl Application {
                             .filter_map(|doc| {
                                 if doc
                                     .language_servers()
-                                    .iter()
                                     .any(|server| server.id() == server_id)
                                 {
                                     doc.clear_diagnostics(server_id);
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 14a68490..060c9d83 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -3235,7 +3235,6 @@ pub mod insert {
         let doc = doc_mut!(cx.editor);
         let trigger_completion = doc
             .language_servers_with_feature(LanguageServerFeature::Completion)
-            .iter()
             .any(|ls| {
                 let capabilities = ls.capabilities();
 
@@ -3264,7 +3263,6 @@ pub mod insert {
         // TODO support multiple language servers (not just the first that is found)
         let future = doc
             .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
-            .iter()
             .find_map(|ls| {
                 let capabilities = ls.capabilities();
 
@@ -4067,10 +4065,8 @@ fn format_selections(cx: &mut Context) {
             .set_error("format_selections only supports a single selection for now");
         return;
     }
-
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::Format)
-        .iter()
         .find_map(|language_server| {
             let offset_encoding = language_server.offset_encoding();
             let ranges: Vec<lsp::Range> = doc
@@ -4091,7 +4087,9 @@ fn format_selections(cx: &mut Context) {
                 None,
             )?;
             Some((future, offset_encoding))
-        }) {
+        });
+
+    let (future, offset_encoding) = match future_offset_encoding {
         Some(future_offset_encoding) => future_offset_encoding,
         None => {
             cx.editor
@@ -4247,7 +4245,6 @@ pub fn completion(cx: &mut Context) {
 
     let mut futures: FuturesUnordered<_> = doc
         .language_servers_with_feature(LanguageServerFeature::Completion)
-        .iter()
         // TODO this should probably already been filtered in something like "language_servers_with_feature"
         .filter_map(|language_server| {
             let language_server_id = language_server.id();
diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs
index 25a54aba..6553ce16 100644
--- a/helix-term/src/commands/lsp.rs
+++ b/helix-term/src/commands/lsp.rs
@@ -353,7 +353,6 @@ pub fn symbol_picker(cx: &mut Context) {
 
     let mut futures: FuturesUnordered<_> = doc
         .language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
-        .iter()
         .filter_map(|ls| {
             let request = ls.document_symbols(doc.identifier())?;
             Some((request, ls.offset_encoding(), doc.identifier()))
@@ -420,7 +419,6 @@ pub fn workspace_symbol_picker(cx: &mut Context) {
         let doc = doc!(editor);
         let mut futures: FuturesUnordered<_> = doc
             .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
-            .iter()
             .filter_map(|ls| Some((ls.workspace_symbols(pattern.clone())?, ls.offset_encoding())))
             .map(|(request, offset_encoding)| async move {
                 let json = request.await?;
@@ -581,7 +579,6 @@ pub fn code_action(cx: &mut Context) {
 
     let mut futures: FuturesUnordered<_> = doc
         .language_servers_with_feature(LanguageServerFeature::CodeAction)
-        .iter()
         // TODO this should probably already been filtered in something like "language_servers_with_feature"
         .filter_map(|language_server| {
             let offset_encoding = language_server.offset_encoding();
@@ -1034,15 +1031,15 @@ 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) {
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::GotoDeclaration)
-        .iter()
         .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
@@ -1062,15 +1059,15 @@ pub fn goto_declaration(cx: &mut Context) {
 
 pub fn goto_definition(cx: &mut Context) {
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::GotoDefinition)
-        .iter()
         .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
@@ -1090,15 +1087,15 @@ pub fn goto_definition(cx: &mut Context) {
 
 pub fn goto_type_definition(cx: &mut Context) {
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::GotoTypeDefinition)
-        .iter()
         .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
@@ -1118,15 +1115,15 @@ pub fn goto_type_definition(cx: &mut Context) {
 
 pub fn goto_implementation(cx: &mut Context) {
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::GotoImplementation)
-        .iter()
         .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
@@ -1147,9 +1144,8 @@ pub fn goto_implementation(cx: &mut Context) {
 pub fn goto_reference(cx: &mut Context) {
     let config = cx.editor.config();
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::GotoReference)
-        .iter()
         .find_map(|language_server| {
             let offset_encoding = language_server.offset_encoding();
             let pos = doc.position(view.id, offset_encoding);
@@ -1160,7 +1156,8 @@ pub fn goto_reference(cx: &mut Context) {
                 None,
             )?;
             Some((future, offset_encoding))
-        }) {
+        });
+    let (future, offset_encoding) = match future_offset_encoding {
         Some(future_offset_encoding) => future_offset_encoding,
         None => {
             cx.editor
@@ -1192,13 +1189,14 @@ pub fn signature_help_impl(cx: &mut Context, invoked: SignatureHelpInvoked) {
     let (view, doc) = current!(cx.editor);
 
     // TODO merge multiple language server signature help into one instead of just taking the first language server that supports it
-    let future = match doc
+    let future = doc
         .language_servers_with_feature(LanguageServerFeature::SignatureHelp)
-        .iter()
         .find_map(|language_server| {
             let pos = doc.position(view.id, language_server.offset_encoding());
             language_server.text_document_signature_help(doc.identifier(), pos, None)
-        }) {
+        });
+
+    let future = match future {
         Some(future) => future.boxed(),
         None => {
             // Do not show the message if signature help was invoked
@@ -1328,7 +1326,6 @@ pub fn hover(cx: &mut Context) {
     // TODO: factor out a doc.position_identifier() that returns lsp::TextDocumentPositionIdentifier
     let request = doc
         .language_servers_with_feature(LanguageServerFeature::Hover)
-        .iter()
         .find_map(|language_server| {
             let pos = doc.position(view.id, language_server.offset_encoding());
             language_server.text_document_hover(doc.identifier(), pos, None)
@@ -1436,7 +1433,6 @@ pub fn rename_symbol(cx: &mut Context) {
                 let (view, doc) = current!(cx.editor);
                 let request = doc
                     .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
-                    .iter()
                     .find_map(|language_server| {
                         if let Some(language_server_id) = language_server_id {
                             if language_server.id() != language_server_id {
@@ -1475,7 +1471,6 @@ pub fn rename_symbol(cx: &mut Context) {
 
     let prepare_rename_request = doc
         .language_servers_with_feature(LanguageServerFeature::RenameSymbol)
-        .iter()
         .find_map(|language_server| {
             let offset_encoding = language_server.offset_encoding();
             let pos = doc.position(view.id, offset_encoding);
@@ -1516,17 +1511,17 @@ pub fn rename_symbol(cx: &mut Context) {
 
 pub fn select_references_to_symbol_under_cursor(cx: &mut Context) {
     let (view, doc) = current!(cx.editor);
-    let (future, offset_encoding) = match doc
+    let future_offset_encoding = doc
         .language_servers_with_feature(LanguageServerFeature::DocumentHighlight)
-        .iter()
         .find_map(|language_server| {
             let offset_encoding = language_server.offset_encoding();
             let pos = doc.position(view.id, offset_encoding);
             let future =
                 language_server.text_document_document_highlight(doc.identifier(), pos, None)?;
             Some((future, offset_encoding))
-        }) {
-        Some(future) => future,
+        });
+    let (future, offset_encoding) = match future_offset_encoding {
+        Some(future_offset_encoding) => future_offset_encoding,
         None => {
             cx.editor
                 .set_error("No language server supports document-highlight");
@@ -1587,8 +1582,8 @@ fn compute_inlay_hints_for_view(
     let view_id = view.id;
     let doc_id = view.doc;
 
-    let language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
-    let language_server = language_servers.iter().find(|language_server| {
+    let mut language_servers = doc.language_servers_with_feature(LanguageServerFeature::InlayHints);
+    let language_server = language_servers.find(|language_server| {
         matches!(
             language_server.capabilities().inlay_hint_provider,
             Some(
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index b78de772..38058ed5 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1330,14 +1330,16 @@ fn lsp_workspace_command(
         return Ok(());
     }
     let doc = doc!(cx.editor);
-    let language_servers =
-        doc.language_servers_with_feature(LanguageServerFeature::WorkspaceCommand);
-    let (language_server_id, options) = match language_servers.iter().find_map(|ls| {
-        ls.capabilities()
-            .execute_command_provider
-            .as_ref()
-            .map(|options| (ls.id(), options))
-    }) {
+    let id_options = doc
+        .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
+        .find_map(|ls| {
+            ls.capabilities()
+                .execute_command_provider
+                .as_ref()
+                .map(|options| (ls.id(), options))
+        });
+
+    let (language_server_id, options) = match id_options {
         Some(id_options) => id_options,
         None => {
             cx.editor.set_status(
@@ -1346,6 +1348,7 @@ fn lsp_workspace_command(
             return Ok(());
         }
     };
+
     if args.is_empty() {
         let commands = options
             .commands
@@ -1445,7 +1448,6 @@ fn lsp_stop(
     // I'm not sure if this is really what we want
     let ls_shutdown_names = doc
         .language_servers()
-        .iter()
         .map(|ls| ls.name())
         .collect::<Vec<_>>();
 
@@ -1459,7 +1461,6 @@ fn lsp_stop(
         .filter_map(|doc| {
             let doc_active_ls_ids: Vec<_> = doc
                 .language_servers()
-                .iter()
                 .filter(|ls| !ls_shutdown_names.contains(&ls.name()))
                 .map(|ls| ls.id())
                 .collect();
@@ -1472,7 +1473,7 @@ fn lsp_stop(
                 .map(Clone::clone)
                 .collect();
 
-            if active_clients.len() != doc.language_servers().len() {
+            if active_clients.len() != doc.language_servers().count() {
                 Some((doc.id(), active_clients))
             } else {
                 None
@@ -1485,7 +1486,6 @@ fn lsp_stop(
 
         let stopped_clients: Vec<_> = doc
             .language_servers()
-            .iter()
             .filter(|ls| {
                 !active_clients
                     .iter()
diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs
index 6b9f8517..5b22ea55 100644
--- a/helix-term/src/health.rs
+++ b/helix-term/src/health.rs
@@ -194,10 +194,10 @@ pub fn languages_all() -> std::io::Result<()> {
 
         // TODO multiple language servers (check binary for each supported language server, not just the first)
 
-        let lsp = lang.language_servers.first().and_then(|lsp| {
+        let lsp = lang.language_servers.keys().next().and_then(|ls_name| {
             syn_loader_conf
                 .language_server
-                .get(lsp.name())
+                .get(ls_name)
                 .map(|config| config.command.clone())
         });
         check_binary(lsp);
@@ -271,10 +271,10 @@ pub fn language(lang_str: String) -> std::io::Result<()> {
     // TODO multiple language servers
     probe_protocol(
         "language server",
-        lang.language_servers.first().and_then(|lsp| {
+        lang.language_servers.keys().next().and_then(|ls_name| {
             syn_loader_conf
                 .language_server
-                .get(lsp.name())
+                .get(ls_name)
                 .map(|config| config.command.clone())
         }),
     )?;
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 118836c0..6f7ed174 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -394,13 +394,11 @@ pub mod completers {
     pub fn lsp_workspace_command(editor: &Editor, input: &str) -> Vec<Completion> {
         let matcher = Matcher::default();
 
-        let language_servers =
-            doc!(editor).language_servers_with_feature(LanguageServerFeature::WorkspaceCommand);
-        let options = match language_servers
-            .into_iter()
+        let options = match doc!(editor)
+            .language_servers_with_feature(LanguageServerFeature::WorkspaceCommand)
             .find_map(|ls| ls.capabilities().execute_command_provider.as_ref())
         {
-            Some(id_options) => id_options,
+            Some(options) => options,
             None => {
                 return vec![];
             }
diff --git a/helix-term/src/ui/statusline.rs b/helix-term/src/ui/statusline.rs
index 60997956..4aa64634 100644
--- a/helix-term/src/ui/statusline.rs
+++ b/helix-term/src/ui/statusline.rs
@@ -202,11 +202,10 @@ fn render_lsp_spinner<F>(context: &mut RenderContext, write: F)
 where
     F: Fn(&mut RenderContext, String, Option<Style>) + Copy,
 {
-    let language_servers = context.doc.language_servers();
+    let language_server = context.doc.language_servers().next();
     write(
         context,
-        language_servers
-            .first()
+        language_server
             .and_then(|srv| {
                 context
                     .spinners
diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs
index 37ddc2b6..4b075293 100644
--- a/helix-view/src/document.rs
+++ b/helix-view/src/document.rs
@@ -6,7 +6,7 @@ use futures_util::FutureExt;
 use helix_core::auto_pairs::AutoPairs;
 use helix_core::doc_formatter::TextFormat;
 use helix_core::encoding::Encoding;
-use helix_core::syntax::{Highlight, LanguageServerFeature, LanguageServerFeatureConfiguration};
+use helix_core::syntax::{Highlight, LanguageServerFeature};
 use helix_core::text_annotations::{InlineAnnotation, TextAnnotations};
 use helix_core::Range;
 use helix_vcs::{DiffHandle, DiffProviderRegistry};
@@ -734,7 +734,6 @@ impl Document {
         // finds first language server that supports formatting and then formats
         let (offset_encoding, request) = self
             .language_servers_with_feature(LanguageServerFeature::Format)
-            .iter()
             .find_map(|language_server| {
                 let offset_encoding = language_server.offset_encoding();
                 let request = language_server.text_document_formatting(
@@ -1437,54 +1436,24 @@ impl Document {
         self.version
     }
 
-    /// Language servers that have been initialized.
-    pub fn language_servers(&self) -> Vec<&helix_lsp::Client> {
+    pub fn language_servers(&self) -> impl Iterator<Item = &helix_lsp::Client> {
         self.language_servers
             .iter()
             .filter_map(|l| if l.is_initialized() { Some(&**l) } else { None })
-            .collect()
     }
 
     // TODO filter also based on LSP capabilities?
     pub fn language_servers_with_feature(
         &self,
         feature: LanguageServerFeature,
-    ) -> Vec<&helix_lsp::Client> {
-        let language_servers = self.language_servers();
-
-        let language_config = match self.language_config() {
-            Some(language_config) => language_config,
-            None => return Vec::new(),
-        };
-
-        // O(n^2) but since language_servers will be of very small length,
-        // I don't see the necessity to optimize
-        language_config
-            .language_servers
-            .iter()
-            .filter_map(|c| match c {
-                LanguageServerFeatureConfiguration::Simple(name) => language_servers
-                    .iter()
-                    .find(|ls| ls.name() == name)
-                    .copied(),
-                LanguageServerFeatureConfiguration::Features {
-                    only_features,
-                    except_features,
-                    name,
-                } => {
-                    if (only_features.is_empty() || only_features.contains(&feature))
-                        && !except_features.contains(&feature)
-                    {
-                        language_servers
-                            .iter()
-                            .find(|ls| ls.name() == name)
-                            .copied()
-                    } else {
-                        None
-                    }
-                }
-            })
-            .collect()
+    ) -> impl Iterator<Item = &helix_lsp::Client> {
+        self.language_servers().filter(move |server| {
+            self.language_config()
+                .and_then(|config| config.language_servers.get(server.name()))
+                .map_or(false, |server_features| {
+                    server_features.has_feature(feature)
+                })
+        })
     }
 
     pub fn diff_handle(&self) -> Option<&DiffHandle> {
@@ -1610,7 +1579,6 @@ impl Document {
     pub fn shown_diagnostics(&self) -> impl Iterator<Item = &Diagnostic> + DoubleEndedIterator {
         let ls_ids: HashSet<_> = self
             .language_servers_with_feature(LanguageServerFeature::Diagnostics)
-            .iter()
             .map(|ls| ls.id())
             .collect();
         self.diagnostics
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 366cc01c..bca97815 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -689,7 +689,7 @@ pub struct WhitespaceCharacters {
 impl Default for WhitespaceCharacters {
     fn default() -> Self {
         Self {
-            space: '·',    // U+00B7
+            space: '·',   // U+00B7
             nbsp: '⍽',    // U+237D
             tab: '→',     // U+2192
             newline: '⏎', // U+23CE
@@ -1129,7 +1129,8 @@ impl Editor {
 
         if let Some(language_servers) = language_servers {
             // only spawn new lang servers if the servers aren't the same
-            let doc_language_servers = doc.language_servers();
+            // TODO simplify?
+            let doc_language_servers = doc.language_servers().collect::<Vec<_>>();
             let spawn_new_servers = language_servers.len() != doc_language_servers.len()
                 || language_servers
                     .iter()