Add vim/emacs modeline support (#44210)

Many editors such as vim and emacs support "modelines", a comment at the
beginning of the file that allows the file type to be explicitly
specified along with per-file specific settings

- The amount of configurations, style and settings mapping cannot be
handled in one go, so this opens up a lot of potential improvements.
- I left out the possiblity to have "zed" specific modelines for now,
but this could be potentially interesting.
- Mapping the mode or filetype to zed language names isn't obvious
either. We may want to make it configurable.

This is my first contribution to zed, be kind. I struggled a bit to find
the right place to add those settings. I use a similar approach as done
with editorconfig (merge_with_editorconfig). There might be better ways.

Closes #4762

Release Notes:

- Add basic emacs/vim modeline support.

Supersedes #41899, changes:
- limit reading to the first and last 1kb
- add documentation
- more variables handled
- add Arc around ModelineSettings to avoid extra cloning
- changed the way mode -> language mapping is done, thanks to
`modeline_aliases` language config
- drop vim ex: support
- made "Local Variables:" handling a separate commit, so we can drop it
easily
- various code style improvements

---------

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-01-23 07:55:39 +00:00
1 parent a1f4329dee
commit a3174dab48
57 files changed
+1509 -522

No files matched your search

+13 -12
View File
@@ -4,10 +4,10 @@ use async_trait::async_trait;
use collections::HashMap;
use futures::lock::OwnedMutexGuard;
use futures::{AsyncBufReadExt, StreamExt as _};
use gpui::{App, AsyncApp, SharedString, Task};
use gpui::{App, AsyncApp, Entity, SharedString, Task};
use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release};
use language::language_settings::language_settings;
use language::{ContextLocation, DynLspInstaller, LanguageToolchainStore, LspInstaller};
use language::language_settings::LanguageSettings;
use language::{Buffer, ContextLocation, DynLspInstaller, LanguageToolchainStore, LspInstaller};
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata};
@@ -783,11 +783,10 @@ impl ContextProvider for PythonContextProvider {
toolchains: Arc<dyn LanguageToolchainStore>,
cx: &mut gpui::App,
) -> Task<Result<task::TaskVariables>> {
let test_target =
match selected_test_runner(location.file_location.buffer.read(cx).file(), cx) {
TestRunner::UNITTEST => self.build_unittest_target(variables),
TestRunner::PYTEST => self.build_pytest_target(variables),
};
let test_target = match selected_test_runner(Some(&location.file_location.buffer), cx) {
TestRunner::UNITTEST => self.build_unittest_target(variables),
TestRunner::PYTEST => self.build_pytest_target(variables),
};
let module_target = self.build_module_target(variables);
let location_file = location.file_location.buffer.read(cx).file().cloned();
@@ -825,10 +824,10 @@ impl ContextProvider for PythonContextProvider {
fn associated_tasks(
&self,
file: Option<Arc<dyn language::File>>,
buffer: Option<Entity<Buffer>>,
cx: &App,
) -> Task<Option<TaskTemplates>> {
let test_runner = selected_test_runner(file.as_ref(), cx);
let test_runner = selected_test_runner(buffer.as_ref(), cx);
let mut tasks = vec![
// Execute a selection
@@ -935,9 +934,11 @@ impl ContextProvider for PythonContextProvider {
}
}
fn selected_test_runner(location: Option<&Arc<dyn language::File>>, cx: &App) -> TestRunner {
fn selected_test_runner(location: Option<&Entity<Buffer>>, cx: &App) -> TestRunner {
const TEST_RUNNER_VARIABLE: &str = "TEST_RUNNER";
language_settings(Some(LanguageName::new_static("Python")), location, cx)
let language = LanguageName::new_static("Python");
let settings = LanguageSettings::resolve(location.map(|b| b.read(cx)), Some(&language), cx);
settings
.tasks
.variables
.get(TEST_RUNNER_VARIABLE)