diff --git a/assets/settings/default.json b/assets/settings/default.json index 7af7100648b7e17d938d7c1eb171d8644e1f8a19..2f2cbdd28ab5e8c9c7e53319a8dd3149b55f1aa3 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1327,12 +1327,6 @@ "hard_tabs": false, // How many columns a tab should occupy. "tab_size": 4, - // Number of lines to search for modelines at the beginning and end of files. - // Modelines contain editor directives (e.g., vim/emacs settings) that configure - // the editor behavior for specific files. - // - // A value of 0 disables modelines support. - "modeline_lines": 5, // What debuggers are preferred by default for all languages. "debuggers": [], // Whether to enable word diff highlighting in the editor. diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index ff9fe6ddd02dd044cf5a9dbc389f46ecfcbc1dd9..d67e68b31ea96ac0aaa1304eb15195173309687f 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -2333,8 +2333,11 @@ impl AcpThread { let format_on_save = buffer.update(cx, |buffer, cx| { buffer.edit(edits, None, cx); - let settings = - language::language_settings::LanguageSettings::for_buffer(buffer, cx); + let settings = language::language_settings::language_settings( + buffer.language().map(|l| l.name()), + buffer.file(), + cx, + ); settings.format_on_save != FormatOnSave::Off }); diff --git a/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs b/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs index 85f4f6f4eeae9cd1304d3fbde1bce05e18ddf02c..607daa8ce3a129e0f4bc53a00d1a62f479da3932 100644 --- a/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs +++ b/crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs @@ -550,7 +550,7 @@ impl Default for EditorStyle { } pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle { - let show_background = language_settings::language_settings(cx).get() + let show_background = language_settings::language_settings(None, None, cx) .inlay_hints .show_background; @@ -5989,7 +5989,7 @@ impl Editor { let file = buffer.file(); - if !language_settings(cx).buffer(buffer).get().show_edit_predictions { + if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions { return EditPredictionSettings::Disabled; }; @@ -18800,7 +18800,7 @@ fn choose_completion_range( } = &completion.source { let completion_mode_setting = - language_settings(cx).buffer(buffer).get() + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) .completions .lsp_insert_mode; @@ -19849,7 +19849,7 @@ fn inlay_hint_settings( ) -> InlayHintSettings { let file = snapshot.file_at(location); let language = snapshot.language_at(location).map(|l| l.name()); - language_settings(cx).language(language).file(file).get().inlay_hints + language_settings(language, file, cx).inlay_hints } fn consume_contiguous_rows( diff --git a/crates/agent/src/tools/edit_file_tool.rs b/crates/agent/src/tools/edit_file_tool.rs index 656b91a25dc9d5e048d7b7a024f8dde93a36a00e..bc7e5b5289937d6212c662f97238e43ea185684d 100644 --- a/crates/agent/src/tools/edit_file_tool.rs +++ b/crates/agent/src/tools/edit_file_tool.rs @@ -468,11 +468,14 @@ impl AgentTool for EditFileTool { } // If format_on_save is enabled, format the buffer - let format_on_save_enabled = buffer - .read_with(cx, |buffer, cx| { - let settings = language_settings::LanguageSettings::for_buffer(buffer, cx); - settings.format_on_save != FormatOnSave::Off - }); + let format_on_save_enabled = buffer.read_with(cx, |buffer, cx| { + let settings = language_settings::language_settings( + buffer.language().map(|l| l.name()), + buffer.file(), + cx, + ); + settings.format_on_save != FormatOnSave::Off + }); let edit_agent_output = output.await?; diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index 049e7305e6b7dc38ffce798feedf27944acb7fa3..7b6c03098f5d85a3116670c5acc34cc8cc161df8 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -2,8 +2,8 @@ use futures::channel::oneshot; use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task}; use language::{ - Capability, Diff, DiffOptions, Language, LanguageName, LanguageRegistry, - language_settings::LanguageSettings, word_diff_ranges, + Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry, + language_settings::language_settings, word_diff_ranges, }; use rope::Rope; use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc}; @@ -946,6 +946,7 @@ impl BufferDiffInner { } fn build_diff_options( + file: Option<&Arc>, language: Option, language_scope: Option, cx: &App, @@ -961,7 +962,7 @@ fn build_diff_options( } } - LanguageSettings::resolve(None, language.as_ref(), cx) + language_settings(language, file, cx) .word_diff_enabled .then_some(DiffOptions { language_scope, @@ -1493,6 +1494,7 @@ impl BufferDiff { let base_text_changed = base_text_change.is_some(); let compute_base_text_edits = base_text_change == Some(true); let diff_options = build_diff_options( + None, language.as_ref().map(|l| l.name()), language.as_ref().map(|l| l.default_scope()), cx, diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index b0094455c372d68dc0e8e987379da2c0a6437c3d..ca444024820be6b5b3a4165999d78ea9b642cc5e 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -21,7 +21,7 @@ use gpui::{ App, Rgba, SharedString, TestAppContext, UpdateGlobal, VisualContext, VisualTestContext, }; use indoc::indoc; -use language::{FakeLspAdapter, language_settings::LanguageSettings, rust_lang}; +use language::{FakeLspAdapter, language_settings::language_settings, rust_lang}; use lsp::LSP_REQUEST_TIMEOUT; use pretty_assertions::assert_eq; use project::{ @@ -4020,8 +4020,6 @@ async fn test_collaborating_with_external_editorconfig( .await .unwrap(); - project_a.update(cx_a, |project, _| project.languages().add(rust_lang())); - // Open buffer on client A let buffer_a = project_a .update(cx_a, |p, cx| { @@ -4034,13 +4032,13 @@ async fn test_collaborating_with_external_editorconfig( // Verify client A sees external editorconfig settings cx_a.read(|cx| { - let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx); + let file = buffer_a.read(cx).file(); + let settings = language_settings(Some("Rust".into()), file, cx); assert_eq!(Some(settings.tab_size), NonZeroU32::new(5)); }); // Client B joins the project let project_b = client_b.join_remote_project(project_id, cx_b).await; - project_b.update(cx_b, |project, _| project.languages().add(rust_lang())); let buffer_b = project_b .update(cx_b, |p, cx| { p.open_buffer((worktree_id, rel_path("src/main.rs")), cx) @@ -4052,7 +4050,8 @@ async fn test_collaborating_with_external_editorconfig( // Verify client B also sees external editorconfig settings cx_b.read(|cx| { - let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx); + let file = buffer_b.read(cx).file(); + let settings = language_settings(Some("Rust".into()), file, cx); assert_eq!(Some(settings.tab_size), NonZeroU32::new(5)); }); @@ -4071,13 +4070,15 @@ async fn test_collaborating_with_external_editorconfig( // Verify client A sees updated settings cx_a.read(|cx| { - let settings = LanguageSettings::for_buffer(&buffer_a.read(cx), cx); + let file = buffer_a.read(cx).file(); + let settings = language_settings(Some("Rust".into()), file, cx); assert_eq!(Some(settings.tab_size), NonZeroU32::new(9)); }); // Verify client B also sees updated settings cx_b.read(|cx| { - let settings = LanguageSettings::for_buffer(&buffer_b.read(cx), cx); + let file = buffer_b.read(cx).file(); + let settings = language_settings(Some("Rust".into()), file, cx); assert_eq!(Some(settings.tab_size), NonZeroU32::new(9)); }); } diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index 02a9c3b3b285a2f77b566c3bf5d026e2e6bf1575..090bb9499f94662a5140927105ff1a9ce6c6c000 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -12,7 +12,7 @@ use gpui::{AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as use http_client::BlockedHttpClient; use language::{ FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry, - language_settings::{Formatter, FormatterList, LanguageSettings}, + language_settings::{Formatter, FormatterList, language_settings}, rust_lang, tree_sitter_typescript, }; use node_runtime::NodeRuntime; @@ -89,7 +89,6 @@ async fn test_sharing_an_ssh_remote_project( let remote_http_client = Arc::new(BlockedHttpClient); let node = NodeRuntime::unavailable(); let languages = Arc::new(LanguageRegistry::new(server_cx.executor())); - languages.add(rust_lang()); let _headless_project = server_cx.new(|cx| { HeadlessProject::new( HeadlessAppState { @@ -119,7 +118,6 @@ async fn test_sharing_an_ssh_remote_project( // User B joins the project. let project_b = client_b.join_remote_project(project_id, cx_b).await; - project_b.update(cx_b, |project, _| project.languages().add(rust_lang())); let worktree_b = project_b .update(cx_b, |project, cx| project.worktree_for_id(worktree_id, cx)) .unwrap(); @@ -172,8 +170,9 @@ async fn test_sharing_an_ssh_remote_project( executor.run_until_parked(); cx_b.read(|cx| { + let file = buffer_b.read(cx).file(); assert_eq!( - LanguageSettings::for_buffer(buffer_b.read(cx), cx).language_servers, + language_settings(Some("Rust".into()), file, cx).language_servers, ["override-rust-analyzer".to_string()] ) }); @@ -1028,8 +1027,9 @@ async fn test_ssh_remote_worktree_trust(cx_a: &mut TestAppContext, server_cx: &m let fake_language_server = fake_language_servers.next(); cx_a.read(|cx| { + let file = buffer_before_approval.read(cx).file(); assert_eq!( - LanguageSettings::for_buffer(buffer_before_approval.read(cx), cx).language_servers, + language_settings(Some("Rust".into()), file, cx).language_servers, ["...".to_string()], "remote .zed/settings.json must not sync before trust approval" ) @@ -1056,8 +1056,9 @@ async fn test_ssh_remote_worktree_trust(cx_a: &mut TestAppContext, server_cx: &m cx_a.run_until_parked(); cx_a.read(|cx| { + let file = buffer_before_approval.read(cx).file(); assert_eq!( - LanguageSettings::for_buffer(buffer_before_approval.read(cx), cx).language_servers, + language_settings(Some("Rust".into()), file, cx).language_servers, ["override-rust-analyzer".to_string()], "remote .zed/settings.json should sync after trust approval" ) diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index 451d81fbd2bfc0f6fabd48546c04b0a39d8c0951..862242a3d4fbc16b83f7424c4ccbe2927222424e 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -1333,10 +1333,11 @@ impl PickerDelegate for DebugDelegate { else { return; }; - let buffer = location.buffer.read(cx); - let language = buffer.language(); + let file = location.buffer.read(cx).file(); + let language = location.buffer.read(cx).language(); + let language_name = language.as_ref().map(|l| l.name()); let Some(adapter): Option = - language::language_settings::LanguageSettings::for_buffer(buffer, cx) + language::language_settings::language_settings(language_name, file, cx) .debuggers .first() .map(SharedString::from) diff --git a/crates/edit_prediction_cli/src/format_prompt.rs b/crates/edit_prediction_cli/src/format_prompt.rs index c15c703d56c24370ff107b16543714ba8ec0277c..daea43a7254357e9ccc8411218070a2eb2db9568 100644 --- a/crates/edit_prediction_cli/src/format_prompt.rs +++ b/crates/edit_prediction_cli/src/format_prompt.rs @@ -41,7 +41,6 @@ pub async fn run_format_prompt( prompt_inputs.content.as_str().into(), language, Some(app_state.languages.clone()), - None, cx, ) }); diff --git a/crates/edit_prediction_ui/src/edit_prediction_button.rs b/crates/edit_prediction_ui/src/edit_prediction_button.rs index 67773c67c40f837dc308aeb3263c1cf6cfb79233..d4e6605e6e5ec339d1170fdc89a8c877d2cd291b 100644 --- a/crates/edit_prediction_ui/src/edit_prediction_button.rs +++ b/crates/edit_prediction_ui/src/edit_prediction_button.rs @@ -20,9 +20,7 @@ use gpui::{ use indoc::indoc; use language::{ EditPredictionsMode, File, Language, - language_settings::{ - AllLanguageSettings, EditPredictionProvider, LanguageSettings, all_language_settings, - }, + language_settings::{self, AllLanguageSettings, EditPredictionProvider, all_language_settings}, }; use project::{DisableAiSettings, Project}; use regex::Regex; @@ -670,7 +668,8 @@ impl EditPredictionButton { let language_state = self.language.as_ref().map(|language| { ( language.clone(), - LanguageSettings::resolve(None, Some(&language.name()), cx).show_edit_predictions, + language_settings::language_settings(Some(language.name()), None, cx) + .show_edit_predictions, ) }); diff --git a/crates/editor/src/bracket_colorization.rs b/crates/editor/src/bracket_colorization.rs index 4b31b6d12185e9a8d35937ef9db6baba1373b30c..81d73c6725c4f543946e8394f783c9e7a30cf596 100644 --- a/crates/editor/src/bracket_colorization.rs +++ b/crates/editor/src/bracket_colorization.rs @@ -8,7 +8,7 @@ use crate::Editor; use collections::HashMap; use gpui::{Context, HighlightStyle}; use itertools::Itertools; -use language::language_settings::LanguageSettings; +use language::language_settings; use multi_buffer::{Anchor, ExcerptId}; use ui::{ActiveTheme, utils::ensure_minimum_contrast}; @@ -46,9 +46,14 @@ impl Editor { let bracket_matches_by_accent = self.visible_excerpts(false, cx).into_iter().fold( HashMap::default(), |mut acc, (excerpt_id, (buffer, _, buffer_range))| { - let buffer = buffer.read(cx); - let buffer_snapshot = buffer.snapshot(); - if LanguageSettings::for_buffer(&buffer, cx).colorize_brackets { + let buffer_snapshot = buffer.read(cx).snapshot(); + if language_settings::language_settings( + buffer_snapshot.language().map(|language| language.name()), + buffer_snapshot.file(), + cx, + ) + .colorize_brackets + { let fetched_chunks = self .fetched_tree_sitter_chunks .entry(excerpt_id) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 541fb441a6e9417a555704b0f743ba46c2bbc52f..6106bfaa66b5c955ef0403e72c70ef0fc6c9ef6e 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -97,10 +97,7 @@ use gpui::{ App, Context, Entity, EntityId, Font, HighlightStyle, LineLayout, Pixels, UnderlineStyle, WeakEntity, }; -use language::{ - Point, Subscription as BufferSubscription, - language_settings::{AllLanguageSettings, LanguageSettings}, -}; +use language::{Point, Subscription as BufferSubscription, language_settings::language_settings}; use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptId, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, RowInfo, ToOffset, ToPoint, @@ -108,7 +105,6 @@ use multi_buffer::{ use project::InlayId; use project::project_settings::DiagnosticSeverity; use serde::Deserialize; -use settings::Settings; use sum_tree::{Bias, TreeMap}; use text::{BufferId, LineIndent, Patch}; use ui::{SharedString, px}; @@ -1357,11 +1353,12 @@ impl DisplayMap { #[instrument(skip_all)] fn tab_size(buffer: &Entity, cx: &App) -> NonZeroU32 { - if let Some(buffer) = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx)) { - LanguageSettings::for_buffer(buffer, cx).tab_size - } else { - AllLanguageSettings::get_global(cx).defaults.tab_size - } + let buffer = buffer.read(cx).as_singleton().map(|buffer| buffer.read(cx)); + let language = buffer + .and_then(|buffer| buffer.language()) + .map(|l| l.name()); + let file = buffer.and_then(|buffer| buffer.file()); + language_settings(language, file, cx).tab_size } #[cfg(test)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 37f5bd24fdb3bd6df0303383a8e6cff4fa3b3ad5..232ca9efa3943016065d79380effc7420416c1fd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -128,8 +128,8 @@ use language::{ OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery, language_settings::{ - self, AllLanguageSettings, LanguageSettings, LspInsertMode, RewrapBehavior, - WordsCompletionMode, all_language_settings, + self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode, + all_language_settings, language_settings, }, point_from_lsp, point_to_lsp, text_diff_with_options, }; @@ -581,8 +581,7 @@ impl Default for EditorStyle { } pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle { - let show_background = AllLanguageSettings::get_global(cx) - .defaults + let show_background = language_settings::language_settings(None, None, cx) .inlay_hints .show_background; @@ -5642,7 +5641,14 @@ impl Editor { .read(cx) .text_anchor_for_position(position, cx)?; - let settings = LanguageSettings::for_buffer_at(&buffer.read(cx), buffer_position, cx); + let settings = language_settings::language_settings( + buffer + .read(cx) + .language_at(buffer_position) + .map(|l| l.name()), + buffer.read(cx).file(), + cx, + ); if !settings.use_on_type_format { return None; } @@ -5756,7 +5762,8 @@ impl Editor { let language = buffer_snapshot .language_at(buffer_position.text_anchor) .map(|language| language.name()); - let language_settings = multibuffer_snapshot.language_settings_at(buffer_position, cx); + + let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx); let completion_settings = language_settings.completions.clone(); let show_completions_on_input = self @@ -6641,7 +6648,8 @@ impl Editor { let resolved_tasks = resolved_tasks.as_ref()?; let buffer = buffer.read(cx); let language = buffer.language()?; - let debug_adapter = LanguageSettings::for_buffer(&buffer, cx) + let file = buffer.file(); + let debug_adapter = language_settings(language.name().into(), file, cx) .debuggers .first() .map(SharedString::from) @@ -7668,7 +7676,11 @@ impl Editor { return EditPredictionSettings::Disabled; } - if !LanguageSettings::for_buffer(&buffer.read(cx), cx).show_edit_predictions { + let buffer = buffer.read(cx); + + let file = buffer.file(); + + if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions { return EditPredictionSettings::Disabled; }; @@ -7683,7 +7695,6 @@ impl Editor { .as_ref() .is_some_and(|provider| provider.provider.show_predictions_in_menu()); - let file = buffer.read(cx).file(); let preview_requires_modifier = all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle; @@ -16817,17 +16828,17 @@ impl Editor { runnable: &mut Runnable, cx: &mut App, ) -> Task> { - let (inventory, worktree_id, buffer) = project.read_with(cx, |project, cx| { - let buffer = project.buffer_for_id(runnable.buffer, cx); - let worktree_id = buffer - .as_ref() + let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| { + let (worktree_id, file) = project + .buffer_for_id(runnable.buffer, cx) .and_then(|buffer| buffer.read(cx).file()) - .map(|file| file.worktree_id(cx)); + .map(|file| (file.worktree_id(cx), file.clone())) + .unzip(); ( project.task_store().read(cx).task_inventory().cloned(), worktree_id, - buffer, + file, ) }); @@ -16838,12 +16849,7 @@ impl Editor { if let Some(inventory) = inventory { for RunnableTag(tag) in tags { let new_tasks = inventory.update(cx, |inventory, cx| { - inventory.list_tasks( - buffer.clone(), - Some(language.clone()), - worktree_id, - cx, - ) + inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx) }); templates_with_tags.extend(new_tasks.await.into_iter().filter( move |(_, template)| { @@ -23870,8 +23876,9 @@ impl Editor { |mut acc, buffer| { let buffer = buffer.read(cx); let language = buffer.language().map(|language| language.name()); - if let hash_map::Entry::Vacant(v) = acc.entry(language) { - v.insert(LanguageSettings::for_buffer(&buffer, cx).into_owned()); + if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) { + let file = buffer.file(); + v.insert(language_settings(language, file, cx).into_owned()); } acc }, @@ -25140,9 +25147,10 @@ fn process_completion_for_edit( CompletionIntent::CompleteWithInsert => false, CompletionIntent::CompleteWithReplace => true, CompletionIntent::Complete | CompletionIntent::Compose => { - let insert_mode = LanguageSettings::for_buffer(&buffer, cx) - .completions - .lsp_insert_mode; + let insert_mode = + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) + .completions + .lsp_insert_mode; match insert_mode { LspInsertMode::Insert => false, LspInsertMode::Replace => true, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index b383f547cc560f63666f04ce25ddab2d86b9f56e..811bc865128f17c683f186448f411be95e9a9b32 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -30283,12 +30283,10 @@ async fn test_local_worktree_trust(cx: &mut TestAppContext) { let fake_language_server = fake_language_servers.next(); cx.read(|cx| { + let file = buffer_before_approval.read(cx).file(); assert_eq!( - language::language_settings::LanguageSettings::for_buffer( - buffer_before_approval.read(cx), - cx - ) - .language_servers, + language::language_settings::language_settings(Some("Rust".into()), file, cx) + .language_servers, ["...".to_string()], "local .zed/settings.json must not apply before trust approval" ) @@ -30316,12 +30314,10 @@ async fn test_local_worktree_trust(cx: &mut TestAppContext) { cx.run_until_parked(); cx.read(|cx| { + let file = buffer_before_approval.read(cx).file(); assert_eq!( - language::language_settings::LanguageSettings::for_buffer( - buffer_before_approval.read(cx), - cx - ) - .language_servers, + language::language_settings::language_settings(Some("Rust".into()), file, cx) + .language_servers, ["override-rust-analyzer".to_string()], "local .zed/settings.json should apply after trust approval" ) diff --git a/crates/editor/src/indent_guides.rs b/crates/editor/src/indent_guides.rs index c493876742377288c7423b57ce6d785a5b4925c2..571af03a29a63e4a0ee4e42136d2e2bd6b597c95 100644 --- a/crates/editor/src/indent_guides.rs +++ b/crates/editor/src/indent_guides.rs @@ -2,7 +2,7 @@ use std::{cmp::Ordering, ops::Range, time::Duration}; use collections::HashSet; use gpui::{App, AppContext as _, Context, Task, Window}; -use language::language_settings::LanguageSettings; +use language::language_settings::language_settings; use multi_buffer::{IndentGuide, MultiBufferRow, ToPoint}; use text::{LineIndent, Point}; use util::ResultExt; @@ -37,9 +37,13 @@ impl Editor { ) -> Option> { let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| { if let Some(buffer) = self.buffer().read(cx).as_singleton() { - LanguageSettings::for_buffer(buffer.read(cx), cx) - .indent_guides - .enabled + language_settings( + buffer.read(cx).language().map(|l| l.name()), + buffer.read(cx).file(), + cx, + ) + .indent_guides + .enabled } else { true } diff --git a/crates/editor/src/inlays/inlay_hints.rs b/crates/editor/src/inlays/inlay_hints.rs index 0e17d7455f1164453a4d91eb933b33e523a0a455..4b9d2024bbb2906ceceb381cc61db09b0c9e4ddc 100644 --- a/crates/editor/src/inlays/inlay_hints.rs +++ b/crates/editor/src/inlays/inlay_hints.rs @@ -10,7 +10,7 @@ use futures::future::join_all; use gpui::{App, Entity, Task}; use language::{ BufferRow, - language_settings::{InlayHintKind, InlayHintSettings}, + language_settings::{InlayHintKind, InlayHintSettings, language_settings}, }; use lsp::LanguageServerId; use multi_buffer::{Anchor, ExcerptId, MultiBufferSnapshot}; @@ -36,7 +36,9 @@ pub fn inlay_hint_settings( snapshot: &MultiBufferSnapshot, cx: &mut Context, ) -> InlayHintSettings { - snapshot.language_settings_at(location, cx).inlay_hints + let file = snapshot.file_at(location); + let language = snapshot.language_at(location).map(|l| l.name()); + language_settings(language, file, cx).inlay_hints } #[derive(Debug)] diff --git a/crates/editor/src/jsx_tag_auto_close.rs b/crates/editor/src/jsx_tag_auto_close.rs index 6d99552fd5a3b281f59ae47e0e0d83a05764fcfa..8d8f8d43b1ad1d374b130a9b4bc83297e4a76214 100644 --- a/crates/editor/src/jsx_tag_auto_close.rs +++ b/crates/editor/src/jsx_tag_auto_close.rs @@ -5,7 +5,7 @@ use multi_buffer::{BufferOffset, MultiBuffer, ToOffset}; use std::ops::Range; use util::ResultExt as _; -use language::{BufferSnapshot, JsxTagAutoCloseConfig, Node, language_settings::LanguageSettings}; +use language::{BufferSnapshot, JsxTagAutoCloseConfig, Node}; use text::{Anchor, OffsetRangeExt as _}; use crate::{Editor, SelectionEffects}; @@ -323,10 +323,12 @@ pub(crate) fn refresh_enabled_in_any_buffer( if language.config().jsx_tag_auto_close.is_none() { continue; } - let should_auto_close = - LanguageSettings::resolve(Some(buffer), Some(&language.name()), cx) - .jsx_tag_auto_close; - if should_auto_close { + let language_settings = language::language_settings::language_settings( + Some(language.name()), + snapshot.file(), + cx, + ); + if language_settings.jsx_tag_auto_close { found_enabled = true; } } diff --git a/crates/extension_host/src/extension_store_test.rs b/crates/extension_host/src/extension_store_test.rs index f1a209ca7af19589e897c42e9f5269abaa42725a..fa93709d077d435e9d6b579ece8890885675329d 100644 --- a/crates/extension_host/src/extension_store_test.rs +++ b/crates/extension_host/src/extension_store_test.rs @@ -216,7 +216,6 @@ async fn test_extension_store(cx: &mut TestAppContext) { matcher: LanguageMatcher { path_suffixes: vec!["erb".into()], first_line_pattern: None, - ..LanguageMatcher::default() }, }, ), @@ -230,7 +229,6 @@ async fn test_extension_store(cx: &mut TestAppContext) { matcher: LanguageMatcher { path_suffixes: vec!["rb".into()], first_line_pattern: None, - ..LanguageMatcher::default() }, }, ), diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index d684839ec2ef00af1b50d4000c4d7db79548315e..418abf38f3d8ca4a61403a9a3b3831c2da36c106 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1,10 +1,10 @@ pub mod row_chunk; use crate::{ - DebuggerTextObject, LanguageScope, ModelineSettings, Outline, OutlineConfig, PLAIN_TEXT, - RunnableCapture, RunnableTag, TextObject, TreeSitterOptions, + DebuggerTextObject, LanguageScope, Outline, OutlineConfig, PLAIN_TEXT, RunnableCapture, + RunnableTag, TextObject, TreeSitterOptions, diagnostic_set::{DiagnosticEntry, DiagnosticEntryRef, DiagnosticGroup}, - language_settings::LanguageSettings, + language_settings::{LanguageSettings, language_settings}, outline::OutlineItem, row_chunk::RowChunks, syntax_map::{ @@ -135,7 +135,6 @@ pub struct Buffer { /// The contents of a cell are (self.version, has_changes) at the time of a last call. has_unsaved_edits: Cell<(clock::Global, bool)>, change_bits: Vec>>, - modeline: Option>, _subscriptions: Vec, tree_sitter_data: Arc, encoding: &'static Encoding, @@ -195,7 +194,6 @@ pub struct BufferSnapshot { non_text_state_update_count: usize, tree_sitter_data: Arc, pub capability: Capability, - modeline: Option>, } /// The kind and amount of indentation in a particular line. For now, @@ -1146,7 +1144,6 @@ impl Buffer { deferred_ops: OperationQueue::new(), has_conflict: false, change_bits: Default::default(), - modeline: None, _subscriptions: Vec::new(), encoding: encoding_rs::UTF_8, has_bom: false, @@ -1157,7 +1154,6 @@ impl Buffer { text: Rope, language: Option>, language_registry: Option>, - modeline: Option>, cx: &mut App, ) -> impl Future + use<> { let entity_id = cx.reserve_entity::().entity_id(); @@ -1182,7 +1178,6 @@ impl Buffer { language, non_text_state_update_count: 0, capability: Capability::ReadOnly, - modeline, } } } @@ -1209,7 +1204,6 @@ impl Buffer { language: None, non_text_state_update_count: 0, capability: Capability::ReadOnly, - modeline: None, } } @@ -1240,7 +1234,6 @@ impl Buffer { language, non_text_state_update_count: 0, capability: Capability::ReadOnly, - modeline: None, } } @@ -1268,7 +1261,6 @@ impl Buffer { language: self.language.clone(), non_text_state_update_count: self.non_text_state_update_count, capability: self.capability, - modeline: self.modeline.clone(), } } @@ -1517,21 +1509,6 @@ impl Buffer { ); } - /// Assign the buffer [`ModelineSettings`]. - pub fn set_modeline(&mut self, modeline: Option) -> bool { - if modeline.as_ref() != self.modeline.as_deref() { - self.modeline = modeline.map(Arc::new); - true - } else { - false - } - } - - /// Returns the [`ModelineSettings`]. - pub fn modeline(&self) -> Option<&Arc> { - self.modeline.as_ref() - } - /// Assign the buffer a new [`Capability`]. pub fn set_capability(&mut self, capability: Capability, cx: &mut Context) { if self.capability != capability { @@ -2687,12 +2664,8 @@ impl Buffer { } else { // The auto-indent setting is not present in editorconfigs, hence // we can avoid passing the file here. - let auto_indent = LanguageSettings::resolve( - None, - language.map(|l| l.name()).as_ref(), - cx, - ) - .auto_indent; + let auto_indent = + language_settings(language.map(|l| l.name()), None, cx).auto_indent; previous_setting = Some((language_id, auto_indent)); auto_indent } @@ -3287,7 +3260,11 @@ impl BufferSnapshot { /// Returns [`IndentSize`] for a given position that respects user settings /// and language preferences. pub fn language_indent_size_at(&self, position: T, cx: &App) -> IndentSize { - let settings = self.settings_at(position, cx); + let settings = language_settings( + self.language_at(position).map(|l| l.name()), + self.file(), + cx, + ); if settings.hard_tabs { IndentSize::tab() } else { @@ -3741,11 +3718,6 @@ impl BufferSnapshot { }) } - /// Returns the [`ModelineSettings`]. - pub fn modeline(&self) -> Option<&Arc> { - self.modeline.as_ref() - } - /// Returns the main [`Language`]. pub fn language(&self) -> Option<&Arc> { self.language.as_ref() @@ -3764,7 +3736,11 @@ impl BufferSnapshot { position: D, cx: &'a App, ) -> Cow<'a, LanguageSettings> { - LanguageSettings::for_buffer_snapshot(self, Some(position.to_offset(self)), cx) + language_settings( + self.language_at(position).map(|l| l.name()), + self.file.as_ref(), + cx, + ) } pub fn char_classifier_at(&self, point: T) -> CharClassifier { @@ -5349,7 +5325,6 @@ impl Clone for BufferSnapshot { tree_sitter_data: self.tree_sitter_data.clone(), non_text_state_update_count: self.non_text_state_update_count, capability: self.capability, - modeline: self.modeline.clone(), } } } diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index fa2b1a5ab7678a7f2be606c4cf95336873845166..80af08a53512d1d5624b20d0dad2c231f1b70a7f 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -246,7 +246,6 @@ async fn test_first_line_pattern(cx: &mut TestAppContext) { matcher: LanguageMatcher { path_suffixes: vec!["js".into()], first_line_pattern: Some(Regex::new(r"\bnode\b").unwrap()), - ..LanguageMatcher::default() }, ..Default::default() }); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index f6c3d075ce6b33bde1bff3c99fcb94ab081cee85..a294f1b5ae81a0d1b59ae1d685ab0d1f8fd67b5a 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -12,7 +12,6 @@ mod highlight_map; mod language_registry; pub mod language_settings; mod manifest; -pub mod modeline; mod outline; pub mod proto; mod syntax_map; @@ -41,7 +40,6 @@ use lsp::{ CodeActionKind, InitializeParams, LanguageServerBinary, LanguageServerBinaryOptions, Uri, }; pub use manifest::{ManifestDelegate, ManifestName, ManifestProvider, ManifestQuery}; -pub use modeline::{ModelineSettings, parse_modeline}; use parking_lot::Mutex; use regex::Regex; use schemars::{JsonSchema, SchemaGenerator, json_schema}; @@ -138,7 +136,6 @@ pub static PLAIN_TEXT: LazyLock> = LazyLock::new(|| { matcher: LanguageMatcher { path_suffixes: vec!["txt".to_owned()], first_line_pattern: None, - modeline_aliases: vec!["text".to_owned(), "txt".to_owned()], }, brackets: BracketPairConfig { pairs: vec![ @@ -967,11 +964,6 @@ pub struct LanguageMatcher { )] #[schemars(schema_with = "regex_json_schema")] pub first_line_pattern: Option, - /// Alternative names for this language used in vim/emacs modelines. - /// These are matched case-insensitively against the `mode` (emacs) or - /// `filetype`/`ft` (vim) specified in the modeline. - #[serde(default)] - pub modeline_aliases: Vec, } /// The configuration for JSX tag auto-closing. diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index 2a4fabf63e62ecaa1b87911bf55c3a40a132d2df..53906ab3bcd740191c4cf2abd497e73a5397ac31 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -721,44 +721,6 @@ impl LanguageRegistry { .cloned() } - /// Look up a language by its modeline name (vim filetype or emacs mode). - /// - /// This performs a case-insensitive match against: - /// 1. Explicit modeline aliases defined in the language config - /// 2. The language's grammar name - /// 3. The language name itself - pub fn available_language_for_modeline_name( - self: &Arc, - modeline_name: &str, - ) -> Option { - let modeline_name_lower = modeline_name.to_lowercase(); - let state = self.state.read(); - - state - .available_languages - .iter() - .find(|lang| { - lang.matcher - .modeline_aliases - .iter() - .any(|alias| alias.to_lowercase() == modeline_name_lower) - }) - .or_else(|| { - state.available_languages.iter().find(|lang| { - lang.grammar - .as_ref() - .is_some_and(|g| g.to_lowercase() == modeline_name_lower) - }) - }) - .or_else(|| { - state - .available_languages - .iter() - .find(|lang| lang.name.0.to_lowercase() == modeline_name_lower) - }) - .cloned() - } - pub fn language_for_file( self: &Arc, file: &Arc, diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 0c1c9efc3a27462d69737b3ef83b3484f40d68b6..86e3568045d81e46649589b1252e1343c583c52f 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -1,8 +1,6 @@ //! Provides `language`-related settings. -use crate::{ - Buffer, BufferSnapshot, File, Language, LanguageName, LanguageServerName, ModelineSettings, -}; +use crate::{File, Language, LanguageName, LanguageServerName}; use collections::{FxHashMap, HashMap, HashSet}; use ec4rs::{ Properties as EditorconfigProperties, @@ -18,10 +16,22 @@ pub use settings::{ Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode, RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode, }; -use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore, merge_from::MergeFrom}; +use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore}; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; -use text::ToOffset; + +/// Returns the settings for the specified language from the provided file. +pub fn language_settings<'a>( + language: Option, + file: Option<&'a Arc>, + cx: &'a App, +) -> Cow<'a, LanguageSettings> { + let location = file.map(|f| SettingsLocation { + worktree_id: f.worktree_id(cx), + path: f.path().as_ref(), + }); + AllLanguageSettings::get(location, cx).language(location, language.as_ref(), cx) +} /// Returns the settings for all languages from the provided file. pub fn all_language_settings<'a>( @@ -250,74 +260,6 @@ impl LanguageSettings { /// A token representing the rest of the available language servers. const REST_OF_LANGUAGE_SERVERS: &'static str = "..."; - pub fn for_buffer<'a>(buffer: &'a Buffer, cx: &'a App) -> Cow<'a, LanguageSettings> { - Self::resolve(Some(buffer), None, cx) - } - - pub fn for_buffer_at<'a, D: ToOffset>( - buffer: &'a Buffer, - position: D, - cx: &'a App, - ) -> Cow<'a, LanguageSettings> { - let language = buffer.language_at(position); - Self::resolve(Some(buffer), language.map(|l| l.name()).as_ref(), cx) - } - - pub fn for_buffer_snapshot<'a>( - buffer: &'a BufferSnapshot, - offset: Option, - cx: &'a App, - ) -> Cow<'a, LanguageSettings> { - let location = buffer.file().map(|f| SettingsLocation { - worktree_id: f.worktree_id(cx), - path: f.path().as_ref(), - }); - - let language = if let Some(offset) = offset { - buffer.language_at(offset) - } else { - buffer.language() - }; - - let mut settings = AllLanguageSettings::get(location, cx).language( - location, - language.map(|l| l.name()).as_ref(), - cx, - ); - - if let Some(modeline) = buffer.modeline() { - merge_with_modeline(settings.to_mut(), modeline); - } - - settings - } - - pub fn resolve<'a>( - buffer: Option<&'a Buffer>, - override_language: Option<&LanguageName>, - cx: &'a App, - ) -> Cow<'a, LanguageSettings> { - let Some(buffer) = buffer else { - return AllLanguageSettings::get(None, cx).language(None, override_language, cx); - }; - let location = buffer.file().map(|f| SettingsLocation { - worktree_id: f.worktree_id(cx), - path: f.path().as_ref(), - }); - let all = AllLanguageSettings::get(location, cx); - let mut settings = if override_language.is_none() { - all.language(location, buffer.language().map(|l| l.name()).as_ref(), cx) - } else { - all.language(location, override_language, cx) - }; - - if let Some(modeline) = buffer.modeline() { - merge_with_modeline(settings.to_mut(), modeline); - } - - settings - } - /// Returns the customized list of language servers from the list of /// available language servers. pub fn customized_language_servers( @@ -541,35 +483,6 @@ impl AllLanguageSettings { } } -fn merge_with_modeline(settings: &mut LanguageSettings, modeline: &ModelineSettings) { - let show_whitespaces = modeline.show_trailing_whitespace.and_then(|v| { - if v { - Some(ShowWhitespaceSetting::Trailing) - } else { - None - } - }); - - settings - .tab_size - .merge_from_option(modeline.tab_size.as_ref()); - settings - .hard_tabs - .merge_from_option(modeline.hard_tabs.as_ref()); - settings - .preferred_line_length - .merge_from_option(modeline.preferred_line_length.map(u32::from).as_ref()); - settings - .auto_indent - .merge_from_option(modeline.auto_indent.as_ref()); - settings - .show_whitespaces - .merge_from_option(show_whitespaces.as_ref()); - settings - .ensure_final_newline_on_save - .merge_from_option(modeline.ensure_final_newline.as_ref()); -} - fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) { let preferred_line_length = cfg.get::().ok().and_then(|v| match v { MaxLineLen::Value(u) => Some(u as u32), @@ -597,18 +510,22 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr TrimTrailingWs::Value(b) => b, }) .ok(); - - settings - .preferred_line_length - .merge_from_option(preferred_line_length.as_ref()); - settings.tab_size.merge_from_option(tab_size.as_ref()); - settings.hard_tabs.merge_from_option(hard_tabs.as_ref()); - settings - .remove_trailing_whitespace_on_save - .merge_from_option(remove_trailing_whitespace_on_save.as_ref()); - settings - .ensure_final_newline_on_save - .merge_from_option(ensure_final_newline_on_save.as_ref()); + fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } + } + merge(&mut settings.preferred_line_length, preferred_line_length); + merge(&mut settings.tab_size, tab_size); + merge(&mut settings.hard_tabs, hard_tabs); + merge( + &mut settings.remove_trailing_whitespace_on_save, + remove_trailing_whitespace_on_save, + ); + merge( + &mut settings.ensure_final_newline_on_save, + ensure_final_newline_on_save, + ); } impl settings::Settings for AllLanguageSettings { diff --git a/crates/language/src/modeline.rs b/crates/language/src/modeline.rs deleted file mode 100644 index 8b7e6044492fc64c5291a9d034466e1ec42a5ead..0000000000000000000000000000000000000000 --- a/crates/language/src/modeline.rs +++ /dev/null @@ -1,763 +0,0 @@ -use regex::Regex; -use std::{num::NonZeroU32, sync::LazyLock}; - -/// The settings extracted from an emacs/vim modelines. -/// -/// The parsing tries to best match the modeline directives and -/// variables to Zed, matching LanguageSettings fields. -/// The mode mapping is done later thanks to the LanguageRegistry. -/// -/// It is not exhaustive, but covers the most common settings. -#[derive(Debug, Clone, Default, PartialEq)] -pub struct ModelineSettings { - /// The emacs mode or vim filetype. - pub mode: Option, - /// How many columns a tab should occupy. - pub tab_size: Option, - /// Whether to indent lines using tab characters, as opposed to multiple - /// spaces. - pub hard_tabs: Option, - /// The number of bytes that comprise the indentation. - pub indent_size: Option, - /// Whether to auto-indent lines. - pub auto_indent: Option, - /// The column at which to soft-wrap lines. - pub preferred_line_length: Option, - /// Whether to ensure a final newline at the end of the file. - pub ensure_final_newline: Option, - /// Whether to show trailing whitespace on the editor. - pub show_trailing_whitespace: Option, - - /// Emacs modeline variables that were parsed but not mapped to Zed settings. - /// Stored as (variable-name, value) pairs. - pub emacs_extra_variables: Vec<(String, String)>, - /// Vim modeline options that were parsed but not mapped to Zed settings. - /// Stored as (option-name, value) pairs. - pub vim_extra_variables: Vec<(String, Option)>, -} - -impl ModelineSettings { - fn has_settings(&self) -> bool { - self != &Self::default() - } -} - -/// Parse modelines from file content. -/// -/// Supports: -/// - Emacs modelines: -*- mode: rust; tab-width: 4; indent-tabs-mode: nil; -*- and "Local Variables" -/// - Vim modelines: vim: set ft=rust ts=4 sw=4 et: -pub fn parse_modeline(first_lines: &[&str], last_lines: &[&str]) -> Option { - let mut settings = ModelineSettings::default(); - - parse_modelines(first_lines, &mut settings); - - // Parse Emacs Local Variables in last lines - parse_emacs_local_variables(last_lines, &mut settings); - - // Also check for vim modelines in last lines if we don't have settings yet - if !settings.has_settings() { - parse_vim_modelines(last_lines, &mut settings); - } - - Some(settings).filter(|s| s.has_settings()) -} - -fn parse_modelines(modelines: &[&str], settings: &mut ModelineSettings) { - for line in modelines { - parse_emacs_modeline(line, settings); - // if emacs is set, do not check for vim modelines - if settings.has_settings() { - return; - } - } - - parse_vim_modelines(modelines, settings); -} - -static EMACS_MODELINE_RE: LazyLock = - LazyLock::new(|| Regex::new(r"-\*-\s*(.+?)\s*-\*-").expect("valid regex")); - -/// Parse Emacs-style modelines -/// Format: -*- mode: rust; tab-width: 4; indent-tabs-mode: nil; -*- -/// See Emacs (set-auto-mode) -fn parse_emacs_modeline(line: &str, settings: &mut ModelineSettings) { - let Some(captures) = EMACS_MODELINE_RE.captures(line) else { - return; - }; - let Some(modeline_content) = captures.get(1).map(|m| m.as_str()) else { - return; - }; - for part in modeline_content.split(';') { - parse_emacs_key_value(part, settings, true); - } -} - -/// Parse Emacs-style Local Variables block -/// -/// Emacs supports a "Local Variables" block at the end of files: -/// ```text -/// /* Local Variables: */ -/// /* mode: c */ -/// /* tab-width: 4 */ -/// /* End: */ -/// ``` -/// -/// Emacs related code is hack-local-variables--find-variables in -/// https://cgit.git.savannah.gnu.org/cgit/emacs.git/tree/lisp/files.el#n4346 -fn parse_emacs_local_variables(lines: &[&str], settings: &mut ModelineSettings) { - const LOCAL_VARIABLES: &str = "Local Variables:"; - - let Some((start_idx, prefix, suffix)) = lines.iter().enumerate().find_map(|(i, line)| { - let prefix_len = line.find(LOCAL_VARIABLES)?; - let suffix_start = prefix_len + LOCAL_VARIABLES.len(); - Some((i, line.get(..prefix_len)?, line.get(suffix_start..)?)) - }) else { - return; - }; - - let mut continuation = String::new(); - - for line in &lines[start_idx + 1..] { - let Some(content) = line - .strip_prefix(prefix) - .and_then(|l| l.strip_suffix(suffix)) - .map(str::trim) - else { - return; - }; - - if let Some(continued) = content.strip_suffix('\\') { - continuation.push_str(continued); - continue; - } - - let to_parse = if continuation.is_empty() { - content - } else { - continuation.push_str(content); - &continuation - }; - - if to_parse == "End:" { - return; - } - - parse_emacs_key_value(to_parse, settings, false); - continuation.clear(); - } -} - -fn parse_emacs_key_value(part: &str, settings: &mut ModelineSettings, bare: bool) { - let part = part.trim(); - if part.is_empty() { - return; - } - - if let Some((key, value)) = part.split_once(':') { - let key = key.trim(); - let value = value.trim(); - - match key.to_lowercase().as_str() { - "mode" => { - settings.mode = Some(value.to_string()); - } - "c-basic-offset" | "python-indent-offset" => { - if let Ok(size) = value.parse::() { - settings.indent_size = Some(size); - } - } - "fill-column" => { - if let Ok(size) = value.parse::() { - settings.preferred_line_length = Some(size); - } - } - "tab-width" => { - if let Ok(size) = value.parse::() { - settings.tab_size = Some(size); - } - } - "indent-tabs-mode" => { - settings.hard_tabs = Some(value != "nil"); - } - "electric-indent-mode" => { - settings.auto_indent = Some(value != "nil"); - } - "require-final-newline" => { - settings.ensure_final_newline = Some(value != "nil"); - } - "show-trailing-whitespace" => { - settings.show_trailing_whitespace = Some(value != "nil"); - } - key => settings - .emacs_extra_variables - .push((key.to_string(), value.to_string())), - } - } else if bare { - // Handle bare mode specification (e.g., -*- rust -*-) - settings.mode = Some(part.to_string()); - } -} - -fn parse_vim_modelines(modelines: &[&str], settings: &mut ModelineSettings) { - for line in modelines { - parse_vim_modeline(line, settings); - } -} - -static VIM_MODELINE_PATTERNS: LazyLock> = LazyLock::new(|| { - [ - // Second form: [text{white}]{vi:vim:Vim:}[white]se[t] {options}:[text] - // Allow escaped colons in options: match non-colon chars or backslash followed by any char - r"(?:^|\s)(vi|vim|Vim):(?:\s*)se(?:t)?\s+((?:[^\\:]|\\.)*):", - // First form: [text{white}]{vi:vim:}[white]{options} - r"(?:^|\s+)(vi|vim):(?:\s*(.+))", - ] - .iter() - .map(|pattern| Regex::new(pattern).expect("valid regex")) - .collect() -}); - -/// Parse Vim-style modelines -/// Supports both forms: -/// 1. First form: vi:noai:sw=3 ts=6 -/// 2. Second form: vim: set ft=rust ts=4 sw=4 et: -fn parse_vim_modeline(line: &str, settings: &mut ModelineSettings) { - for re in VIM_MODELINE_PATTERNS.iter() { - if let Some(captures) = re.captures(line) { - if let Some(options) = captures.get(2) { - parse_vim_settings(options.as_str().trim(), settings); - break; - } - } - } -} - -fn parse_vim_settings(content: &str, settings: &mut ModelineSettings) { - fn split_colon_unescape(input: &str) -> Vec { - let mut split = Vec::new(); - let mut str = String::new(); - let mut chars = input.chars().peekable(); - while let Some(c) = chars.next() { - if c == '\\' { - match chars.next() { - Some(escaped_char) => str.push(escaped_char), - None => str.push('\\'), - } - } else if c == ':' { - split.push(std::mem::take(&mut str)); - } else { - str.push(c); - } - } - split.push(str); - split - } - - let parts = split_colon_unescape(content); - for colon_part in parts { - let colon_part = colon_part.trim(); - if colon_part.is_empty() { - continue; - } - - // Each colon part might contain space-separated options - for part in colon_part.split_whitespace() { - if let Some((key, value)) = part.split_once('=') { - match key { - "ft" | "filetype" => { - settings.mode = Some(value.to_string()); - } - "ts" | "tabstop" => { - if let Ok(size) = value.parse::() { - settings.tab_size = Some(size); - } - } - "sw" | "shiftwidth" => { - if let Ok(size) = value.parse::() { - settings.indent_size = Some(size); - } - } - "tw" | "textwidth" => { - if let Ok(size) = value.parse::() { - settings.preferred_line_length = Some(size); - } - } - _ => { - settings - .vim_extra_variables - .push((key.to_string(), Some(value.to_string()))); - } - } - } else { - match part { - "ai" | "autoindent" => { - settings.auto_indent = Some(true); - } - "noai" | "noautoindent" => { - settings.auto_indent = Some(false); - } - "et" | "expandtab" => { - settings.hard_tabs = Some(false); - } - "noet" | "noexpandtab" => { - settings.hard_tabs = Some(true); - } - "eol" | "endofline" => { - settings.ensure_final_newline = Some(true); - } - "noeol" | "noendofline" => { - settings.ensure_final_newline = Some(false); - } - "set" => { - // Ignore the "set" keyword itself - } - _ => { - settings.vim_extra_variables.push((part.to_string(), None)); - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use indoc::indoc; - use pretty_assertions::assert_eq; - - #[test] - fn test_no_modeline() { - let content = "This is just regular content\nwith no modeline"; - assert!(parse_modeline(&[content], &[content]).is_none()); - } - - #[test] - fn test_emacs_bare_mode() { - let content = "/* -*- rust -*- */"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("rust".to_string()), - ..Default::default() - } - ); - } - - #[test] - fn test_emacs_modeline_parsing() { - let content = "/* -*- mode: rust; tab-width: 4; indent-tabs-mode: nil; -*- */"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("rust".to_string()), - tab_size: Some(NonZeroU32::new(4).unwrap()), - hard_tabs: Some(false), - ..Default::default() - } - ); - } - - #[test] - fn test_emacs_last_line_parsing() { - let content = indoc! {r#" - # Local Variables: - # compile-command: "cc foo.c -Dfoo=bar -Dhack=whatever \ - # -Dmumble=blaah" - # End: - "#} - .lines() - .collect::>(); - let settings = parse_modeline(&[], &content).unwrap(); - assert_eq!( - settings, - ModelineSettings { - emacs_extra_variables: vec![( - "compile-command".to_string(), - "\"cc foo.c -Dfoo=bar -Dhack=whatever -Dmumble=blaah\"".to_string() - ),], - ..Default::default() - } - ); - - let content = indoc! {" - foo - /* Local Variables: */ - /* eval: (font-lock-mode -1) */ - /* mode: old-c */ - /* mode: c */ - /* End: */ - /* mode: ignored */ - "} - .lines() - .collect::>(); - let settings = parse_modeline(&[], &content).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("c".to_string()), - emacs_extra_variables: vec![( - "eval".to_string(), - "(font-lock-mode -1)".to_string() - ),], - ..Default::default() - } - ); - } - - #[test] - fn test_vim_modeline_parsing() { - // Test second form (set format) - let content = "// vim: set ft=rust ts=4 sw=4 et:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("rust".to_string()), - tab_size: Some(NonZeroU32::new(4).unwrap()), - hard_tabs: Some(false), - indent_size: Some(NonZeroU32::new(4).unwrap()), - ..Default::default() - } - ); - - // Test first form (colon-separated) - let content = "vi:noai:sw=3:ts=6"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - tab_size: Some(NonZeroU32::new(6).unwrap()), - auto_indent: Some(false), - indent_size: Some(NonZeroU32::new(3).unwrap()), - ..Default::default() - } - ); - } - - #[test] - fn test_vim_modeline_first_form() { - // Examples from vim specification: vi:noai:sw=3 ts=6 - let content = " vi:noai:sw=3 ts=6 "; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - tab_size: Some(NonZeroU32::new(6).unwrap()), - auto_indent: Some(false), - indent_size: Some(NonZeroU32::new(3).unwrap()), - ..Default::default() - } - ); - - // Test with filetype - let content = "vim:ft=python:ts=8:noet"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("python".to_string()), - tab_size: Some(NonZeroU32::new(8).unwrap()), - hard_tabs: Some(true), - ..Default::default() - } - ); - } - - #[test] - fn test_vim_modeline_second_form() { - // Examples from vim specification: /* vim: set ai tw=75: */ - let content = "/* vim: set ai tw=75: */"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - auto_indent: Some(true), - preferred_line_length: Some(NonZeroU32::new(75).unwrap()), - ..Default::default() - } - ); - - // Test with 'Vim:' (capital V) - let content = "/* Vim: set ai tw=75: */"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - auto_indent: Some(true), - preferred_line_length: Some(NonZeroU32::new(75).unwrap()), - ..Default::default() - } - ); - - // Test 'se' shorthand - let content = "// vi: se ft=c ts=4:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("c".to_string()), - tab_size: Some(NonZeroU32::new(4).unwrap()), - ..Default::default() - } - ); - - // Test complex modeline with encoding - let content = "# vim: set ft=python ts=4 sw=4 et encoding=utf-8:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("python".to_string()), - tab_size: Some(NonZeroU32::new(4).unwrap()), - hard_tabs: Some(false), - indent_size: Some(NonZeroU32::new(4).unwrap()), - vim_extra_variables: vec![("encoding".to_string(), Some("utf-8".to_string()))], - ..Default::default() - } - ); - } - - #[test] - fn test_vim_modeline_edge_cases() { - // Test modeline at start of line (compatibility with version 3.0) - let content = "vi:ts=2:et"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - tab_size: Some(NonZeroU32::new(2).unwrap()), - hard_tabs: Some(false), - ..Default::default() - } - ); - - // Test vim at start of line - let content = "vim:ft=rust:noet"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("rust".to_string()), - hard_tabs: Some(true), - ..Default::default() - } - ); - - // Test mixed boolean flags - let content = "vim: set wrap noet ts=8:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - tab_size: Some(NonZeroU32::new(8).unwrap()), - hard_tabs: Some(true), - vim_extra_variables: vec![("wrap".to_string(), None)], - ..Default::default() - } - ); - } - - #[test] - fn test_vim_modeline_invalid_cases() { - // Test malformed options are ignored gracefully - let content = "vim: set ts=invalid ft=rust:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!( - settings, - ModelineSettings { - mode: Some("rust".to_string()), - ..Default::default() - } - ); - - // Test empty modeline content - this should still work as there might be options - let content = "vim: set :"; - // This should return None because there are no actual options - let result = parse_modeline(&[content], &[]); - assert!(result.is_none(), "Expected None but got: {:?}", result); - - // Test modeline without proper format - let content = "not a modeline"; - assert!(parse_modeline(&[content], &[]).is_none()); - - // Test word that looks like modeline but isn't - let content = "example: this could be confused with ex:"; - assert!(parse_modeline(&[content], &[]).is_none()); - } - - #[test] - fn test_vim_language_mapping() { - // Test vim-specific language mappings - let content = "vim: set ft=sh:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.mode, Some("sh".to_string())); - - let content = "vim: set ft=golang:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.mode, Some("golang".to_string())); - - let content = "vim: set filetype=js:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.mode, Some("js".to_string())); - } - - #[test] - fn test_vim_extra_variables() { - // Test that unknown vim options are stored as extra variables - let content = "vim: set foldmethod=marker conceallevel=2 custom=value:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - - assert!( - settings - .vim_extra_variables - .contains(&("foldmethod".to_string(), Some("marker".to_string()))) - ); - assert!( - settings - .vim_extra_variables - .contains(&("conceallevel".to_string(), Some("2".to_string()))) - ); - assert!( - settings - .vim_extra_variables - .contains(&("custom".to_string(), Some("value".to_string()))) - ); - } - - #[test] - fn test_modeline_position() { - // Test modeline in first lines - let first_lines = ["#!/bin/bash", "# vim: set ft=bash ts=4:"]; - let settings = parse_modeline(&first_lines, &[]).unwrap(); - assert_eq!(settings.mode, Some("bash".to_string())); - - // Test modeline in last lines - let last_lines = ["", "/* vim: set ft=c: */"]; - let settings = parse_modeline(&[], &last_lines).unwrap(); - assert_eq!(settings.mode, Some("c".to_string())); - - // Test no modeline found - let content = ["regular content", "no modeline here"]; - assert!(parse_modeline(&content, &content).is_none()); - } - - #[test] - fn test_vim_modeline_version_checks() { - // Note: Current implementation doesn't support version checks yet - // These are tests for future implementation based on vim spec - - // Test version-specific modelines (currently ignored in our implementation) - let content = "/* vim700: set foldmethod=marker */"; - // Should be ignored for now since we don't support version checks - assert!(parse_modeline(&[content], &[]).is_none()); - - let content = "/* vim>702: set cole=2: */"; - // Should be ignored for now since we don't support version checks - assert!(parse_modeline(&[content], &[]).is_none()); - } - - #[test] - fn test_vim_modeline_colon_escaping() { - // Test colon escaping as mentioned in vim spec - - // According to vim spec: "if you want to include a ':' in a set command precede it with a '\'" - let content = r#"/* vim: set fdm=expr fde=getline(v\:lnum)=~'{'?'>1'\:'1': */"#; - - let result = parse_modeline(&[content], &[]).unwrap(); - - // The modeline should parse fdm=expr and fde=getline(v:lnum)=~'{'?'>1':'1' - // as extra variables since they're not recognized settings - assert_eq!(result.vim_extra_variables.len(), 2); - assert_eq!( - result.vim_extra_variables[0], - ("fdm".to_string(), Some("expr".to_string())) - ); - assert_eq!( - result.vim_extra_variables[1], - ( - "fde".to_string(), - Some("getline(v:lnum)=~'{'?'>1':'1'".to_string()) - ) - ); - } - - #[test] - fn test_vim_modeline_whitespace_requirements() { - // Test whitespace requirements from vim spec - - // Valid: whitespace before vi/vim - let content = " vim: set ft=rust:"; - assert!(parse_modeline(&[content], &[]).is_some()); - - // Valid: tab before vi/vim - let content = "\tvim: set ft=rust:"; - assert!(parse_modeline(&[content], &[]).is_some()); - - // Valid: vi/vim at start of line (compatibility) - let content = "vim: set ft=rust:"; - assert!(parse_modeline(&[content], &[]).is_some()); - } - - #[test] - fn test_vim_modeline_comprehensive_examples() { - // Real-world examples from vim documentation and common usage - - // Python example - let content = "# vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.hard_tabs, Some(false)); - assert_eq!(settings.tab_size, Some(NonZeroU32::new(4).unwrap())); - - // C example with multiple options - let content = "/* vim: set ts=8 sw=8 noet ai cindent: */"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.tab_size, Some(NonZeroU32::new(8).unwrap())); - assert_eq!(settings.hard_tabs, Some(true)); - assert!( - settings - .vim_extra_variables - .contains(&("cindent".to_string(), None)) - ); - - // Shell script example - let content = "# vi: set ft=sh ts=2 sw=2 et:"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.mode, Some("sh".to_string())); - assert_eq!(settings.tab_size, Some(NonZeroU32::new(2).unwrap())); - assert_eq!(settings.hard_tabs, Some(false)); - - // First form colon-separated - let content = "vim:ft=xml:ts=2:sw=2:et"; - let settings = parse_modeline(&[content], &[]).unwrap(); - assert_eq!(settings.mode, Some("xml".to_string())); - assert_eq!(settings.tab_size, Some(NonZeroU32::new(2).unwrap())); - assert_eq!(settings.hard_tabs, Some(false)); - } - - #[test] - fn test_combined_emacs_vim_detection() { - // Test that both emacs and vim modelines can be detected in the same file - - let first_lines = [ - "#!/usr/bin/env python3", - "# -*- require-final-newline: t; -*-", - "# vim: set ft=python ts=4 sw=4 et:", - ]; - - // Should find the emacs modeline first (with coding) - let settings = parse_modeline(&first_lines, &[]).unwrap(); - assert_eq!(settings.ensure_final_newline, Some(true)); - assert_eq!(settings.tab_size, None); - - // Test vim-only content - let vim_only = ["# vim: set ft=python ts=4 sw=4 et:"]; - let settings = parse_modeline(&vim_only, &[]).unwrap(); - assert_eq!(settings.mode, Some("python".to_string())); - assert_eq!(settings.tab_size, Some(NonZeroU32::new(4).unwrap())); - assert_eq!(settings.hard_tabs, Some(false)); - } -} diff --git a/crates/language/src/task_context.rs b/crates/language/src/task_context.rs index dc59d21bd73a2d4a8e1d4a4c765195afffd2ce67..b8cc6d13fff14576ca938e36d8982973f6307912 100644 --- a/crates/language/src/task_context.rs +++ b/crates/language/src/task_context.rs @@ -1,11 +1,11 @@ use std::{ops::Range, path::PathBuf, sync::Arc}; -use crate::{Buffer, LanguageToolchainStore, Location, Runnable}; +use crate::{File, LanguageToolchainStore, Location, Runnable}; use anyhow::Result; use collections::HashMap; use fs::Fs; -use gpui::{App, Entity, Task}; +use gpui::{App, Task}; use lsp::LanguageServerName; use task::{TaskTemplates, TaskVariables}; use text::BufferId; @@ -37,7 +37,7 @@ pub trait ContextProvider: Send + Sync { } /// Provides all tasks, associated with the current language. - fn associated_tasks(&self, _: Option>, _: &App) -> Task> { + fn associated_tasks(&self, _: Option>, _: &App) -> Task> { Task::ready(None) } diff --git a/crates/languages/src/bash/config.toml b/crates/languages/src/bash/config.toml index 06574629f186800f4d95244d7c4129cbc6505d22..8ff4802aee5124201d013e0b2f0b01c7046e55a0 100644 --- a/crates/languages/src/bash/config.toml +++ b/crates/languages/src/bash/config.toml @@ -2,7 +2,6 @@ name = "Shell Script" code_fence_block_name = "bash" grammar = "bash" path_suffixes = ["sh", "bash", "bashrc", "bash_profile", "bash_aliases", "bash_logout", "bats", "profile", "zsh", "zshrc", "zshenv", "zsh_profile", "zsh_aliases", "zsh_histfile", "zlogin", "zprofile", ".env", "PKGBUILD", "APKBUILD"] -modeline_aliases = ["sh", "shell", "zsh", "fish"] line_comments = ["# "] first_line_pattern = '^#!.*\b(?:ash|bash|bats|dash|sh|zsh)\b' autoclose_before = "}])" diff --git a/crates/languages/src/cpp/config.toml b/crates/languages/src/cpp/config.toml index a6a641929da504a82d9b4c604fb4a929e1b16de7..8d85b4f2416cad7cc7935dbb657109d5f1126aa5 100644 --- a/crates/languages/src/cpp/config.toml +++ b/crates/languages/src/cpp/config.toml @@ -1,7 +1,6 @@ name = "C++" grammar = "cpp" path_suffixes = ["cc", "hh", "cpp", "h", "hpp", "cxx", "hxx", "c++", "h++", "ipp", "inl", "ino", "ixx", "cu", "cuh", "C", "H"] -modeline_aliases = ["c++", "cpp", "cxx"] line_comments = ["// ", "/// ", "//! "] decrease_indent_patterns = [ { pattern = "^\\s*\\{.*\\}?\\s*$", valid_after = ["if", "for", "while", "do", "switch", "else"] }, diff --git a/crates/languages/src/go.rs b/crates/languages/src/go.rs index 9330c31094586149e361ff813cfd47d8920766ea..ed6b456b1c74d0ef1e0611e0017d240a0158a4f0 100644 --- a/crates/languages/src/go.rs +++ b/crates/languages/src/go.rs @@ -2,7 +2,7 @@ use anyhow::{Context as _, Result}; use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; -use gpui::{App, AsyncApp, Entity, Task}; +use gpui::{App, AsyncApp, Task}; use http_client::github::latest_github_release; pub use language::*; use language::{LanguageToolchainStore, LspAdapterDelegate, LspInstaller}; @@ -544,7 +544,7 @@ impl ContextProvider for GoContextProvider { ))) } - fn associated_tasks(&self, _: Option>, _: &App) -> Task> { + fn associated_tasks(&self, _: Option>, _: &App) -> Task> { let package_cwd = if GO_PACKAGE_TASK_VARIABLE.template_value() == "." { None } else { diff --git a/crates/languages/src/go/config.toml b/crates/languages/src/go/config.toml index 655012944e62ffbf6cf5701457cb9f0d3b743c19..0a5122c038e1e38e0c963c3d22581f794656c276 100644 --- a/crates/languages/src/go/config.toml +++ b/crates/languages/src/go/config.toml @@ -1,7 +1,6 @@ name = "Go" grammar = "go" path_suffixes = ["go"] -modeline_aliases = ["golang"] line_comments = ["// "] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/languages/src/javascript/config.toml b/crates/languages/src/javascript/config.toml index 2850fd6bc47fe7d23fdfbf9588b2331fdef6e0fa..265f362ce4b655371471649c03c5a4a201da320c 100644 --- a/crates/languages/src/javascript/config.toml +++ b/crates/languages/src/javascript/config.toml @@ -1,7 +1,6 @@ name = "JavaScript" grammar = "tsx" path_suffixes = ["js", "jsx", "mjs", "cjs"] -modeline_aliases = ["js", "js2"] # [/ ] is so we match "env node" or "/node" but not "ts-node" first_line_pattern = '^#!.*\b(?:[/ ]node|deno run.*--ext[= ]js)\b' line_comments = ["// "] diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index 2701167bdb6051a5c186d2f5f9f47ae2e84a2306..a6b5f9aec32eaabb90211b216fb2a9df417b178b 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -4,10 +4,10 @@ use async_tar::Archive; use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; -use gpui::{App, AsyncApp, Entity, Task}; +use gpui::{App, AsyncApp, Task}; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use language::{ - Buffer, ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter, + ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, }; use lsp::{LanguageServerBinary, LanguageServerName, Uri}; @@ -44,11 +44,10 @@ pub(crate) struct JsonTaskProvider; impl ContextProvider for JsonTaskProvider { fn associated_tasks( &self, - buffer: Option>, + file: Option>, cx: &App, ) -> gpui::Task> { - let file = buffer.as_ref().and_then(|buf| buf.read(cx).file()); - let Some(file) = project::File::from_dyn(file).cloned() else { + let Some(file) = project::File::from_dyn(file.as_ref()).cloned() else { return Task::ready(None); }; let is_package_json = file.path.ends_with(RelPath::unix("package.json").unwrap()); diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 5e1d8b7b4b01053b05ce2e72061b557a6a41d47b..7e7b83b3cb5ce82c614d9ba7cd9faba6d2f3a17b 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -51,7 +51,6 @@ pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock> = matcher: LanguageMatcher { path_suffixes: vec!["COMMIT_EDITMSG".to_owned()], first_line_pattern: None, - ..LanguageMatcher::default() }, line_comments: vec![Arc::from("#")], ..LanguageConfig::default() diff --git a/crates/languages/src/markdown/config.toml b/crates/languages/src/markdown/config.toml index 58362f5d0334845608f2c2624145576eb44ba72d..10b1e49757edc106c76e0dc7c591098ebdc6723f 100644 --- a/crates/languages/src/markdown/config.toml +++ b/crates/languages/src/markdown/config.toml @@ -1,7 +1,6 @@ name = "Markdown" grammar = "markdown" path_suffixes = ["md", "mdx", "mdwn", "markdown", "MD"] -modeline_aliases = ["md"] completion_query_characters = ["-"] block_comment = { start = "", tab_size = 0 } autoclose_before = ";:.,=}])>" diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 74bb3d4c6148761d728e7c41d52a9fd7eb7231e0..d3b2dab028f627d7797feb6f710aebc83a1996d4 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -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, Entity, SharedString, Task}; +use gpui::{App, AsyncApp, SharedString, Task}; use http_client::github::{AssetKind, GitHubLspBinaryVersion, latest_github_release}; -use language::language_settings::LanguageSettings; -use language::{Buffer, ContextLocation, DynLspInstaller, LanguageToolchainStore, LspInstaller}; +use language::language_settings::language_settings; +use language::{ContextLocation, DynLspInstaller, LanguageToolchainStore, LspInstaller}; use language::{ContextProvider, LspAdapter, LspAdapterDelegate}; use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery}; use language::{Toolchain, ToolchainList, ToolchainLister, ToolchainMetadata}; @@ -783,10 +783,11 @@ impl ContextProvider for PythonContextProvider { toolchains: Arc, cx: &mut gpui::App, ) -> Task> { - 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 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 module_target = self.build_module_target(variables); let location_file = location.file_location.buffer.read(cx).file().cloned(); @@ -824,10 +825,10 @@ impl ContextProvider for PythonContextProvider { fn associated_tasks( &self, - buffer: Option>, + file: Option>, cx: &App, ) -> Task> { - let test_runner = selected_test_runner(buffer.as_ref(), cx); + let test_runner = selected_test_runner(file.as_ref(), cx); let mut tasks = vec![ // Execute a selection @@ -934,11 +935,9 @@ impl ContextProvider for PythonContextProvider { } } -fn selected_test_runner(location: Option<&Entity>, cx: &App) -> TestRunner { +fn selected_test_runner(location: Option<&Arc>, cx: &App) -> TestRunner { const TEST_RUNNER_VARIABLE: &str = "TEST_RUNNER"; - let language = LanguageName::new_static("Python"); - let settings = LanguageSettings::resolve(location.map(|b| b.read(cx)), Some(&language), cx); - settings + language_settings(Some(LanguageName::new_static("Python")), location, cx) .tasks .variables .get(TEST_RUNNER_VARIABLE) diff --git a/crates/languages/src/python/config.toml b/crates/languages/src/python/config.toml index fa409c5dd6519121e7130e4b33a6c3277ae1654b..d96a5ea5fefd0814c4b0787251e5cf6e4c166d5e 100644 --- a/crates/languages/src/python/config.toml +++ b/crates/languages/src/python/config.toml @@ -2,7 +2,6 @@ name = "Python" grammar = "python" path_suffixes = ["py", "pyi", "mpy"] first_line_pattern = '^#!.*((\bpython[0-9.]*\b)|(\buv run\b))' -modeline_aliases = ["py"] line_comments = ["# "] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index 6f330bf20958cb652e63109922e57a03903b01a1..0ac1b8af015c847b96319d6466696cfeacdc24a7 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -3,7 +3,7 @@ use async_trait::async_trait; use collections::HashMap; use futures::StreamExt; use futures::lock::OwnedMutexGuard; -use gpui::{App, AppContext, AsyncApp, Entity, SharedString, Task}; +use gpui::{App, AppContext, AsyncApp, SharedString, Task}; use http_client::github::AssetKind; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use http_client::github_download::{GithubBinaryMetadata, download_server_binary}; @@ -31,7 +31,7 @@ use util::merge_json_value_into; use util::rel_path::RelPath; use util::{ResultExt, maybe}; -use crate::language_settings::LanguageSettings; +use crate::language_settings::language_settings; pub struct RustLspAdapter; @@ -893,16 +893,23 @@ impl ContextProvider for RustContextProvider { fn associated_tasks( &self, - buffer: Option>, + file: Option>, cx: &App, ) -> Task> { const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; const CUSTOM_TARGET_DIR: &str = "RUST_TARGET_DIR"; - let language = LanguageName::new_static("Rust"); - let settings = LanguageSettings::resolve(buffer.map(|b| b.read(cx)), Some(&language), cx); - let package_to_run = settings.tasks.variables.get(DEFAULT_RUN_NAME_STR).cloned(); - let custom_target_dir = settings.tasks.variables.get(CUSTOM_TARGET_DIR).cloned(); + let language_sets = language_settings(Some("Rust".into()), file.as_ref(), cx); + let package_to_run = language_sets + .tasks + .variables + .get(DEFAULT_RUN_NAME_STR) + .cloned(); + let custom_target_dir = language_sets + .tasks + .variables + .get(CUSTOM_TARGET_DIR) + .cloned(); let run_task_args = if let Some(package_to_run) = package_to_run { vec!["run".into(), "-p".into(), package_to_run] } else { diff --git a/crates/languages/src/rust/config.toml b/crates/languages/src/rust/config.toml index 203a44853f8bd20f952d3db8f0c64dc4babe1017..826a219e9868a3f76a063efe8c91cec0be14c2da 100644 --- a/crates/languages/src/rust/config.toml +++ b/crates/languages/src/rust/config.toml @@ -1,7 +1,6 @@ name = "Rust" grammar = "rust" path_suffixes = ["rs"] -modeline_aliases = ["rs", "rustic"] line_comments = ["// ", "/// ", "//! "] autoclose_before = ";:.,=}])>" brackets = [ diff --git a/crates/languages/src/tsx/config.toml b/crates/languages/src/tsx/config.toml index 42438fdf890a98f319244332f384f574e02c2904..d0a4eb6532db621d741df2fbc99125e1c037ccdf 100644 --- a/crates/languages/src/tsx/config.toml +++ b/crates/languages/src/tsx/config.toml @@ -1,7 +1,6 @@ name = "TSX" grammar = "tsx" path_suffixes = ["tsx"] -modeline_aliases = ["typescript-txs"] line_comments = ["// "] block_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 } documentation_comment = { start = "/**", prefix = "* ", end = "*/", tab_size = 1 } diff --git a/crates/languages/src/typescript.rs b/crates/languages/src/typescript.rs index 1fd53136a697775963ea778e9993fdb2569b11bb..2b2fb19c629f85c6b51eba64d154b43e716f6827 100644 --- a/crates/languages/src/typescript.rs +++ b/crates/languages/src/typescript.rs @@ -3,11 +3,11 @@ use async_trait::async_trait; use chrono::{DateTime, Local}; use collections::HashMap; use futures::future::join_all; -use gpui::{App, AppContext, AsyncApp, Entity, Task}; +use gpui::{App, AppContext, AsyncApp, Task}; use itertools::Itertools as _; use language::{ - Buffer, ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, - LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain, + ContextLocation, ContextProvider, File, LanguageName, LanguageToolchainStore, LspAdapter, + LspAdapterDelegate, LspInstaller, Toolchain, }; use lsp::{CodeActionKind, LanguageServerBinary, LanguageServerName, Uri}; use node_runtime::{NodeRuntime, VersionStrategy}; @@ -425,11 +425,10 @@ async fn detect_package_manager( impl ContextProvider for TypeScriptContextProvider { fn associated_tasks( &self, - buffer: Option>, + file: Option>, cx: &App, ) -> Task> { - let file = buffer.and_then(|buffer| buffer.read(cx).file()); - let Some(file) = project::File::from_dyn(file).cloned() else { + let Some(file) = project::File::from_dyn(file.as_ref()).cloned() else { return Task::ready(None); }; let Some(worktree_root) = file.worktree.read(cx).root_dir() else { diff --git a/crates/languages/src/typescript/config.toml b/crates/languages/src/typescript/config.toml index c0e8a8899a99b0b65e2d073547f3eaf0fe714da2..67656e6a538da6c8860e9ab1b08fd6e6ee9cabbd 100644 --- a/crates/languages/src/typescript/config.toml +++ b/crates/languages/src/typescript/config.toml @@ -1,7 +1,6 @@ name = "TypeScript" grammar = "typescript" path_suffixes = ["ts", "cts", "mts"] -modeline_aliases = ["ts"] first_line_pattern = '^#!.*\b(?:deno run|ts-node|bun|tsx|[/ ]node)\b' line_comments = ["// "] block_comment = { start = "/*", prefix = "* ", end = "*/", tab_size = 1 } diff --git a/crates/languages/src/yaml/config.toml b/crates/languages/src/yaml/config.toml index 95fe81d04dbbb88e1c7deed7a84895cddb7dea1d..9a07a560b06766ac00dd73b6210023c4cddd491d 100644 --- a/crates/languages/src/yaml/config.toml +++ b/crates/languages/src/yaml/config.toml @@ -1,7 +1,6 @@ name = "YAML" grammar = "yaml" path_suffixes = ["yml", "yaml", "pixi.lock", "clang-format", "clangd", "bst"] -modeline_aliases = ["yml"] line_comments = ["# "] autoclose_before = ",]}" brackets = [ diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 09dfa62d708fdc2052fd9b612a2079dbe557b960..283d99db4bd722d7e8a6eaf9d86d2622a191d8f8 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -23,14 +23,13 @@ use language::{ IndentGuideSettings, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions, Unclipped, - language_settings::{AllLanguageSettings, LanguageSettings}, + language_settings::{LanguageSettings, language_settings}, }; #[cfg(any(test, feature = "test-support"))] use gpui::AppContext as _; use rope::DimensionPair; -use settings::Settings; use smallvec::SmallVec; use smol::future::yield_now; use std::{ @@ -2450,7 +2449,10 @@ impl MultiBuffer { .map(|excerpt| excerpt.buffer.remote_id()); buffer_id .and_then(|buffer_id| self.buffer(buffer_id)) - .map(|buffer| LanguageSettings::for_buffer(&buffer.read(cx), cx)) + .map(|buffer| { + let buffer = buffer.read(cx); + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) + }) .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::default(), cx)) } @@ -2459,11 +2461,14 @@ impl MultiBuffer { point: T, cx: &'a App, ) -> Cow<'a, LanguageSettings> { + let mut language = None; + let mut file = None; if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) { - LanguageSettings::for_buffer_at(buffer.read(cx), offset, cx) - } else { - Cow::Borrowed(&AllLanguageSettings::get_global(cx).defaults) + let buffer = buffer.read(cx); + language = buffer.language_at(offset); + file = buffer.file(); } + language_settings(language.map(|l| l.name()), file, cx) } pub fn for_each_buffer(&self, mut f: impl FnMut(&Entity)) { @@ -6075,7 +6080,8 @@ impl MultiBufferSnapshot { let end_row = MultiBufferRow(range.end.row); let mut row_indents = self.line_indents(start_row, |buffer| { - let settings = LanguageSettings::for_buffer_snapshot(buffer, None, cx); + let settings = + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx); settings.indent_guides.enabled || ignore_disabled_for_language }); @@ -6099,7 +6105,7 @@ impl MultiBufferSnapshot { .get_or_insert_with(|| { ( buffer.remote_id(), - LanguageSettings::for_buffer_snapshot(buffer, None, cx), + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx), ) }) .1; @@ -6195,7 +6201,13 @@ impl MultiBufferSnapshot { self.excerpts .first() .map(|excerpt| &excerpt.buffer) - .map(|buffer| LanguageSettings::for_buffer_snapshot(buffer, None, cx)) + .map(|buffer| { + language_settings( + buffer.language().map(|language| language.name()), + buffer.file(), + cx, + ) + }) .unwrap_or_else(move || self.language_settings_at(MultiBufferOffset::ZERO, cx)) } @@ -6204,11 +6216,13 @@ impl MultiBufferSnapshot { point: T, cx: &'a App, ) -> Cow<'a, LanguageSettings> { + let mut language = None; + let mut file = None; if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { - buffer.settings_at(offset, cx) - } else { - Cow::Borrowed(&AllLanguageSettings::get_global(cx).defaults) + language = buffer.language_at(offset); + file = buffer.file(); } + language_settings(language.map(|l| l.name()), file, cx) } pub fn language_scope_at(&self, point: T) -> Option { diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 6017d94a0bedcba76931e7194e65e6824244eea3..b4130b3c75e22c29108019b27665fb83a59bb0f5 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -2,8 +2,8 @@ use anyhow::Context as _; use collections::{HashMap, HashSet}; use fs::Fs; use gpui::{AsyncApp, Entity}; -use language::language_settings::{LanguageSettings, PrettierSettings}; -use language::{Buffer, Diff, Language}; +use language::language_settings::PrettierSettings; +use language::{Buffer, Diff, Language, language_settings::language_settings}; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use paths::default_prettier_dir; @@ -351,7 +351,7 @@ impl Prettier { let params = buffer .update(cx, |buffer, cx| { let buffer_language = buffer.language().map(|language| language.as_ref()); - let language_settings = LanguageSettings::for_buffer(&buffer, cx); + let language_settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); let prettier_settings = &language_settings.prettier; anyhow::ensure!( prettier_settings.allowed, @@ -500,7 +500,11 @@ impl Prettier { let buffer_language = buffer.language().map(|language| language.as_ref()); - let language_settings = LanguageSettings::for_buffer(buffer, cx); + let language_settings = language_settings( + buffer_language.map(|l| l.name()), + buffer.file(), + cx, + ); let prettier_settings = &language_settings.prettier; let parser = prettier_parser_name( buffer_path.as_deref(), diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 743de64c3c3238f96ac6b93e918744383745f7dc..e5b91094fe670b8ed740134fc5abcebe500caf81 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -18,7 +18,7 @@ use gpui::{App, AsyncApp, Entity, SharedString, Task}; use language::{ Anchor, Bias, Buffer, BufferSnapshot, CachedLspAdapter, CharKind, CharScopeContext, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, - language_settings::{InlayHintKind, LanguageSettings}, + language_settings::{InlayHintKind, LanguageSettings, language_settings}, point_from_lsp, point_to_lsp, proto::{deserialize_anchor, deserialize_version, serialize_anchor, serialize_version}, range_from_lsp, range_to_lsp, @@ -2893,7 +2893,9 @@ impl LspCommand for OnTypeFormatting { .await?; let options = buffer.update(&mut cx, |buffer, cx| { - lsp_formatting_options(LanguageSettings::for_buffer(buffer, cx).as_ref()) + lsp_formatting_options( + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx).as_ref(), + ) }); Ok(Self { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 35c91a8bf21d39796a1dca79d42bf6c0d74f6a8e..61c8ec10b2e66c98dbb9a4adf68dbc67c03caead 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -64,10 +64,10 @@ use language::{ Bias, BinaryStatus, Buffer, BufferRow, BufferSnapshot, CachedLspAdapter, Capability, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName, LanguageRegistry, LocalFile, LspAdapter, LspAdapterDelegate, LspInstaller, - ManifestDelegate, ManifestName, ModelineSettings, Patch, PointUtf16, TextBufferSnapshot, - ToOffset, ToPointUtf16, Toolchain, Transaction, Unclipped, - language_settings::{FormatOnSave, Formatter, LanguageSettings}, - modeline, point_to_lsp, + ManifestDelegate, ManifestName, Patch, PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, + Toolchain, Transaction, Unclipped, + language_settings::{FormatOnSave, Formatter, LanguageSettings, language_settings}, + point_to_lsp, proto::{ deserialize_anchor, deserialize_anchor_range, deserialize_lsp_edit, deserialize_version, serialize_anchor, serialize_anchor_range, serialize_lsp_edit, serialize_version, @@ -1515,7 +1515,9 @@ impl LocalLspStore { .language_servers_for_buffer(buffer, cx) .map(|(adapter, lsp)| (adapter.clone(), lsp.clone())) .collect::>(); - let settings = LanguageSettings::for_buffer(buffer, cx).into_owned(); + let settings = + language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx) + .into_owned(); (adapters_and_servers, settings) }) })?; @@ -4280,10 +4282,6 @@ impl LspStore { self.on_buffer_saved(buffer, cx); } - language::BufferEvent::Reloaded => { - self.on_buffer_reloaded(buffer, cx); - } - _ => {} } } @@ -4298,7 +4296,6 @@ impl LspStore { }) .detach(); - self.parse_modeline(buffer, cx); self.detect_language_for_buffer(buffer, cx); if let Some(local) = self.as_local_mut() { local.initialize_buffer(buffer, cx); @@ -4307,12 +4304,6 @@ impl LspStore { Ok(()) } - fn on_buffer_reloaded(&mut self, buffer: Entity, cx: &mut Context) { - if self.parse_modeline(&buffer, cx) { - self.detect_language_for_buffer(&buffer, cx); - } - } - pub(crate) fn register_buffer_with_language_servers( &mut self, buffer: &Entity, @@ -4535,54 +4526,6 @@ impl LspStore { }) } - fn parse_modeline(&mut self, buffer_handle: &Entity, cx: &mut Context) -> bool { - let buffer = buffer_handle.read(cx); - let content = buffer.as_rope(); - - let modeline_settings = { - let settings_store = cx.global::(); - let modeline_lines = settings_store - .raw_user_settings() - .and_then(|s| s.content.modeline_lines) - .or(settings_store.raw_default_settings().modeline_lines) - .unwrap_or(5); - - const MAX_MODELINE_BYTES: usize = 1024; - - let first_bytes = content.len().min(MAX_MODELINE_BYTES); - let mut first_lines = Vec::new(); - let mut lines = content.chunks_in_range(0..first_bytes).lines(); - for _ in 0..modeline_lines { - if let Some(line) = lines.next() { - first_lines.push(line.to_string()); - } else { - break; - } - } - let first_lines_ref: Vec<_> = first_lines.iter().map(|line| line.as_str()).collect(); - - let last_start = content.len().saturating_sub(MAX_MODELINE_BYTES); - let mut last_lines = Vec::new(); - let mut lines = content - .reversed_chunks_in_range(last_start..content.len()) - .lines(); - for _ in 0..modeline_lines { - if let Some(line) = lines.next() { - last_lines.push(line.to_string()); - } else { - break; - } - } - let last_lines_ref: Vec<_> = - last_lines.iter().rev().map(|line| line.as_str()).collect(); - modeline::parse_modeline(&first_lines_ref, &last_lines_ref) - }; - - log::debug!("Parsed modeline settings: {:?}", modeline_settings); - - buffer_handle.update(cx, |buffer, _cx| buffer.set_modeline(modeline_settings)) - } - fn detect_language_for_buffer( &mut self, buffer_handle: &Entity, @@ -4591,19 +4534,9 @@ impl LspStore { // If the buffer has a language, set it and start the language server if we haven't already. let buffer = buffer_handle.read(cx); let file = buffer.file()?; - let content = buffer.as_rope(); - let modeline_settings = buffer.modeline().map(Arc::as_ref); - let available_language = if let Some(ModelineSettings { - mode: Some(mode_name), - .. - }) = modeline_settings - { - self.languages - .available_language_for_modeline_name(mode_name) - } else { - self.languages.language_for_file(file, Some(content), cx) - }; + let content = buffer.as_rope(); + let available_language = self.languages.language_for_file(file, Some(content), cx); if let Some(available_language) = &available_language { if let Some(Ok(Ok(new_language))) = self .languages @@ -4648,12 +4581,8 @@ impl LspStore { } }); - let settings = LanguageSettings::resolve( - Some(&buffer_entity.read(cx)), - Some(&new_language.name()), - cx, - ) - .into_owned(); + let settings = + language_settings(Some(new_language.name()), buffer_file.as_ref(), cx).into_owned(); let buffer_file = File::from_dyn(buffer_file.as_ref()); let worktree_id = if let Some(file) = buffer_file { @@ -4960,9 +4889,10 @@ impl LspStore { let mut language_formatters_to_check = Vec::new(); for buffer in self.buffer_store.read(cx).buffers() { let buffer = buffer.read(cx); - let settings = LanguageSettings::for_buffer(buffer, cx); - if buffer.language().is_some() { - let buffer_file = File::from_dyn(buffer.file()); + let buffer_file = File::from_dyn(buffer.file()); + let buffer_language = buffer.language(); + let settings = language_settings(buffer_language.map(|l| l.name()), buffer.file(), cx); + if buffer_language.is_some() { language_formatters_to_check.push(( buffer_file.map(|f| f.worktree_id(cx)), settings.into_owned(), @@ -5536,9 +5466,9 @@ impl LspStore { }) .filter(|_| { maybe!({ - buffer.read(cx).language_at(position)?; + let language = buffer.read(cx).language_at(position)?; Some( - LanguageSettings::for_buffer_at(&buffer.read(cx), position, cx) + language_settings(Some(language.name()), buffer.read(cx).file(), cx) .linked_edits, ) }) == Some(true) @@ -5642,7 +5572,12 @@ impl LspStore { ) -> Task>> { let options = buffer.update(cx, |buffer, cx| { lsp_command::lsp_formatting_options( - LanguageSettings::for_buffer_at(buffer, position, cx).as_ref(), + language_settings( + buffer.language_at(position).map(|l| l.name()), + buffer.file(), + cx, + ) + .as_ref(), ) }); @@ -6329,9 +6264,13 @@ impl LspStore { let offset = position.to_offset(&snapshot); let scope = snapshot.language_scope_at(offset); let language = snapshot.language().cloned(); - let completion_settings = LanguageSettings::for_buffer(&buffer.read(cx), cx) - .completions - .clone(); + let completion_settings = language_settings( + language.as_ref().map(|language| language.name()), + buffer.read(cx).file(), + cx, + ) + .completions + .clone(); if !completion_settings.lsp { return Task::ready(Ok(Vec::new())); } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index ab525130fa05eebc07433fe4a5d38fb09b96c48f..53e936d476685d96915c417f74716f814bfd061f 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -20,14 +20,14 @@ use git::{ status::{StatusCode, TrackedStatus}, }; use git2::RepositoryInitOptions; -use gpui::{App, BackgroundExecutor, FutureExt, TestAppContext, UpdateGlobal}; +use gpui::{App, BackgroundExecutor, FutureExt, UpdateGlobal}; use itertools::Itertools; use language::{ Diagnostic, DiagnosticEntry, DiagnosticEntryRef, DiagnosticSet, DiagnosticSourceKind, DiskState, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageName, LineEnding, ManifestName, ManifestProvider, ManifestQuery, OffsetRangeExt, Point, ToPoint, ToolchainList, ToolchainLister, - language_settings::{LanguageSettings, LanguageSettingsContent}, + language_settings::{LanguageSettingsContent, language_settings}, markdown_lang, rust_lang, tree_sitter_typescript, }; use lsp::{ @@ -201,39 +201,48 @@ async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) { cx.executor().run_until_parked(); - let settings_for = async |path: &str, cx: &mut TestAppContext| -> LanguageSettings { - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path(path)), cx) - }) - .await - .unwrap(); - cx.update(|cx| LanguageSettings::for_buffer(&buffer.read(cx), cx).into_owned()) - }; + cx.update(|cx| { + let tree = worktree.read(cx); + let settings_for = |path: &str| { + let file_entry = tree.entry_for_path(rel_path(path)).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + language_settings(Some(file_language.name()), Some(&file), cx).into_owned() + }; - let settings_a = settings_for("a.rs", cx).await; - let settings_b = settings_for("b/b.rs", cx).await; - let settings_c = settings_for("c.js", cx).await; - let settings_readme = settings_for("README.json", cx).await; - // .editorconfig overrides .zed/settings - assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3)); - assert_eq!(settings_a.hard_tabs, true); - assert_eq!(settings_a.ensure_final_newline_on_save, true); - assert_eq!(settings_a.remove_trailing_whitespace_on_save, true); - assert_eq!(settings_a.preferred_line_length, 120); - - // .editorconfig in b/ overrides .editorconfig in root - assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2)); - - // "indent_size" is not set, so "tab_width" is used - assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10)); - - // When max_line_length is "off", default to .zed/settings.json - assert_eq!(settings_b.preferred_line_length, 64); - assert_eq!(settings_c.preferred_line_length, 64); - - // README.md should not be affected by .editorconfig's globe "*.rs" - assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8)); + let settings_a = settings_for("a.rs"); + let settings_b = settings_for("b/b.rs"); + let settings_c = settings_for("c.js"); + let settings_readme = settings_for("README.json"); + + // .editorconfig overrides .zed/settings + assert_eq!(Some(settings_a.tab_size), NonZeroU32::new(3)); + assert_eq!(settings_a.hard_tabs, true); + assert_eq!(settings_a.ensure_final_newline_on_save, true); + assert_eq!(settings_a.remove_trailing_whitespace_on_save, true); + assert_eq!(settings_a.preferred_line_length, 120); + + // .editorconfig in b/ overrides .editorconfig in root + assert_eq!(Some(settings_b.tab_size), NonZeroU32::new(2)); + + // "indent_size" is not set, so "tab_width" is used + assert_eq!(Some(settings_c.tab_size), NonZeroU32::new(10)); + + // When max_line_length is "off", default to .zed/settings.json + assert_eq!(settings_b.preferred_line_length, 64); + assert_eq!(settings_c.preferred_line_length, 64); + + // README.md should not be affected by .editorconfig's globe "*.rs" + assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8)); + }); } #[gpui::test] @@ -267,28 +276,37 @@ async fn test_external_editorconfig_support(cx: &mut gpui::TestAppContext) { let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); cx.executor().run_until_parked(); - let settings_for = async |path: &str, cx: &mut TestAppContext| -> LanguageSettings { - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path(path)), cx) - }) - .await - .unwrap(); - cx.update(|cx| LanguageSettings::for_buffer(&buffer.read(cx), cx).into_owned()) - }; - let settings_rs = settings_for("main.rs", cx).await; - let settings_md = settings_for("README.md", cx).await; - let settings_txt = settings_for("other.txt", cx).await; + cx.update(|cx| { + let tree = worktree.read(cx); + let settings_for = |path: &str| { + let file_entry = tree.entry_for_path(rel_path(path)).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + language_settings(Some(file_language.name()), Some(&file), cx).into_owned() + }; + + let settings_rs = settings_for("main.rs"); + let settings_md = settings_for("README.md"); + let settings_txt = settings_for("other.txt"); - // main.rs gets indent_size = 2 from parent's external .editorconfig - assert_eq!(Some(settings_rs.tab_size), NonZeroU32::new(2)); + // main.rs gets indent_size = 2 from parent's external .editorconfig + assert_eq!(Some(settings_rs.tab_size), NonZeroU32::new(2)); - // README.md gets indent_size = 3 from internal worktree .editorconfig - assert_eq!(Some(settings_md.tab_size), NonZeroU32::new(3)); + // README.md gets indent_size = 3 from internal worktree .editorconfig + assert_eq!(Some(settings_md.tab_size), NonZeroU32::new(3)); - // other.txt gets indent_size = 4 from grandparent's external .editorconfig - assert_eq!(Some(settings_txt.tab_size), NonZeroU32::new(4)); + // other.txt gets indent_size = 4 from grandparent's external .editorconfig + assert_eq!(Some(settings_txt.tab_size), NonZeroU32::new(4)); + }); } #[gpui::test] @@ -317,15 +335,20 @@ async fn test_external_editorconfig_root_stops_traversal(cx: &mut gpui::TestAppC cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // file.rs gets indent_size = 2 from worktree's root config, NOT 99 from parent assert_eq!(Some(settings.tab_size), NonZeroU32::new(2)); @@ -360,15 +383,20 @@ async fn test_external_editorconfig_root_in_parent_stops_traversal(cx: &mut gpui cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // file.rs gets indent_size = 4 from parent's root config, NOT 99 from grandparent assert_eq!(Some(settings.tab_size), NonZeroU32::new(4)); @@ -411,24 +439,30 @@ async fn test_external_editorconfig_shared_across_worktrees(cx: &mut gpui::TestA cx.executor().run_until_parked(); - let worktrees: Vec<_> = cx.update(|cx| project.read(cx).worktrees(cx).collect()); - assert_eq!(worktrees.len(), 2); - - for worktree in worktrees { - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); + cx.update(|cx| { + let worktrees: Vec<_> = project.read(cx).worktrees(cx).collect(); + assert_eq!(worktrees.len(), 2); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + for worktree in worktrees { + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = + language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Both worktrees should get indent_size = 5 from shared parent .editorconfig assert_eq!(Some(settings.tab_size), NonZeroU32::new(5)); - }); - } + } + }); } #[gpui::test] @@ -458,15 +492,20 @@ async fn test_external_editorconfig_not_loaded_without_internal_config( cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // file.rs should have default tab_size = 4, NOT 99 from parent's external .editorconfig // because without an internal .editorconfig, external configs are not loaded @@ -500,15 +539,20 @@ async fn test_external_editorconfig_modification_triggers_refresh(cx: &mut gpui: cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Test initial settings: tab_size = 4 from parent's external .editorconfig assert_eq!(Some(settings.tab_size), NonZeroU32::new(4)); @@ -523,15 +567,20 @@ async fn test_external_editorconfig_modification_triggers_refresh(cx: &mut gpui: cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Test settings updated: tab_size = 8 assert_eq!(Some(settings.tab_size), NonZeroU32::new(8)); @@ -566,16 +615,21 @@ async fn test_adding_worktree_discovers_external_editorconfigs(cx: &mut gpui::Te cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - let id = project.worktrees(cx).next().unwrap().read(cx).id(); - project.open_buffer((id, rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx).into_owned(); + let worktree = project.read(cx).worktrees(cx).next().unwrap(); + let tree = worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Test existing worktree has tab_size = 7 assert_eq!(Some(settings.tab_size), NonZeroU32::new(7)); @@ -590,15 +644,20 @@ async fn test_adding_worktree_discovers_external_editorconfigs(cx: &mut gpui::Te cx.executor().run_until_parked(); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((new_worktree.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = new_worktree.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, new_worktree.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Verify new worktree also has tab_size = 7 from shared parent editorconfig assert_eq!(Some(settings.tab_size), NonZeroU32::new(7)); @@ -739,15 +798,20 @@ async fn test_shared_external_editorconfig_cleanup_with_multiple_worktrees( assert_eq!(watcher_paths.len(), 1); }); - let buffer = project - .update(cx, |project, cx| { - project.open_buffer((worktree_b.read(cx).id(), rel_path("file.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings = LanguageSettings::for_buffer(&buffer.read(cx), cx); + let tree = worktree_b.read(cx); + let file_entry = tree.entry_for_path(rel_path("file.rs")).unwrap().clone(); + let file = File::for_entry(file_entry, worktree_b.clone()); + let file_language = project + .read(cx) + .languages() + .load_language_for_file_path(file.path.as_std_path()); + let file_language = cx + .foreground_executor() + .block_on(file_language) + .expect("Failed to get file language"); + let file = file as _; + let settings = language_settings(Some(file_language.name()), Some(&file), cx).into_owned(); // Test worktree_b still has correct settings assert_eq!(Some(settings.tab_size), NonZeroU32::new(5)); @@ -874,28 +938,26 @@ async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) id_base: "local worktree tasks from directory \".zed\"".into(), }; - let buffer_a = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("a/a.rs")), cx) - }) - .await - .unwrap(); - let buffer_b = project - .update(cx, |project, cx| { - project.open_buffer((worktree.read(cx).id(), rel_path("b/b.rs")), cx) - }) - .await - .unwrap(); - cx.update(|cx| { - let settings_a = LanguageSettings::for_buffer(&buffer_a.read(cx), cx); - let settings_b = LanguageSettings::for_buffer(&buffer_b.read(cx), cx); + let all_tasks = cx + .update(|cx| { + let tree = worktree.read(cx); - assert_eq!(settings_a.tab_size.get(), 8); - assert_eq!(settings_b.tab_size.get(), 2); - }); + let file_a = File::for_entry( + tree.entry_for_path(rel_path("a/a.rs")).unwrap().clone(), + worktree.clone(), + ) as _; + let settings_a = language_settings(None, Some(&file_a), cx); + let file_b = File::for_entry( + tree.entry_for_path(rel_path("b/b.rs")).unwrap().clone(), + worktree.clone(), + ) as _; + let settings_b = language_settings(None, Some(&file_b), cx); - let all_tasks = cx - .update(|cx| get_all_tasks(&project, task_contexts.clone(), cx)) + assert_eq!(settings_a.tab_size.get(), 8); + assert_eq!(settings_b.tab_size.get(), 2); + + get_all_tasks(&project, task_contexts.clone(), cx) + }) .await .into_iter() .map(|(source_kind, task)| { diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index 59150d113af22695ce7d5d3aeeda6d91cf19b4a7..5710de36dce6b4cbbd8d38b3e6c3b102fe5df6c0 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -778,7 +778,7 @@ mod tests { use language::Buffer; let text = crate::Rope::from("hello\nworld\nhello\nworld"); let snapshot = cx - .update(|app| Buffer::build_snapshot(text, None, None, None, app)) + .update(|app| Buffer::build_snapshot(text, None, None, app)) .await; let results = search_query.search(&snapshot, None).await; diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index e91bad6f13ebcda3d7d8d62c6feba04a437d7511..7a63b68422bbfc98684f32ebc637151de7d1d8d9 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -15,7 +15,7 @@ use gpui::{App, AppContext as _, Context, Entity, SharedString, Task, WeakEntity use itertools::Itertools; use language::{ Buffer, ContextLocation, ContextProvider, File, Language, LanguageToolchainStore, Location, - language_settings::LanguageSettings, + language_settings::language_settings, }; use lsp::{LanguageServerId, LanguageServerName}; use paths::{debug_task_file_name, task_file_name}; @@ -302,15 +302,17 @@ impl Inventory { let last_scheduled_scenarios = self.last_scheduled_scenarios.iter().cloned().collect(); let adapter = task_contexts.location().and_then(|location| { - let buffer = location.buffer.read(cx); - let adapter = LanguageSettings::for_buffer(&buffer, cx) + let (file, language) = { + let buffer = location.buffer.read(cx); + (buffer.file(), buffer.language()) + }; + let language_name = language.as_ref().map(|l| l.name()); + let adapter = language_settings(language_name, file, cx) .debuggers .first() .map(SharedString::from) .or_else(|| { - buffer - .language() - .and_then(|l| l.config().debuggers.first().map(SharedString::from)) + language.and_then(|l| l.config().debuggers.first().map(SharedString::from)) }); adapter.map(|adapter| (adapter, DapRegistry::global(cx).locators())) }); @@ -348,18 +350,19 @@ impl Inventory { label: &str, cx: &App, ) -> Task> { - let (buffer_worktree_id, language) = buffer - .as_ref() + let (buffer_worktree_id, file, language) = buffer .map(|buffer| { let buffer = buffer.read(cx); + let file = buffer.file().cloned(); ( - buffer.file().as_ref().map(|file| file.worktree_id(cx)), + file.as_ref().map(|file| file.worktree_id(cx)), + file, buffer.language().cloned(), ) }) - .unwrap_or((None, None)); + .unwrap_or((None, None, None)); - let tasks = self.list_tasks(buffer, language, worktree_id.or(buffer_worktree_id), cx); + let tasks = self.list_tasks(file, language, worktree_id.or(buffer_worktree_id), cx); let label = label.to_owned(); cx.background_spawn(async move { tasks @@ -375,7 +378,7 @@ impl Inventory { /// and global tasks last. No specific order inside source kinds groups. pub fn list_tasks( &self, - buffer: Option>, + file: Option>, language: Option>, worktree: Option, cx: &App, @@ -391,18 +394,14 @@ impl Inventory { }); let language_tasks = language .filter(|language| { - LanguageSettings::resolve( - buffer.as_ref().map(|b| b.read(cx)), - Some(&language.name()), - cx, - ) - .tasks - .enabled + language_settings(Some(language.name()), file.as_ref(), cx) + .tasks + .enabled }) .and_then(|language| { language .context_provider() - .map(|provider| provider.associated_tasks(buffer, cx)) + .map(|provider| provider.associated_tasks(file, cx)) }); cx.background_spawn(async move { if let Some(t) = language_tasks { @@ -436,7 +435,7 @@ impl Inventory { let task_source_kind = language.as_ref().map(|language| TaskSourceKind::Language { name: language.name().into(), }); - let buffer = location.map(|location| location.buffer.clone()); + let file = location.and_then(|location| location.buffer.read(cx).file().cloned()); let mut task_labels_to_ids = HashMap::>::default(); let mut lru_score = 0_u32; @@ -479,18 +478,14 @@ impl Inventory { let global_tasks = self.global_templates_from_settings().collect::>(); let associated_tasks = language .filter(|language| { - LanguageSettings::resolve( - buffer.as_ref().map(|b| b.read(cx)), - Some(&language.name()), - cx, - ) - .tasks - .enabled + language_settings(Some(language.name()), file.as_ref(), cx) + .tasks + .enabled }) .and_then(|language| { language .context_provider() - .map(|provider| provider.associated_tasks(buffer, cx)) + .map(|provider| provider.associated_tasks(file, cx)) }); let worktree_tasks = worktree .into_iter() @@ -1080,7 +1075,7 @@ impl ContextProviderWithTasks { } impl ContextProvider for ContextProviderWithTasks { - fn associated_tasks(&self, _: Option>, _: &App) -> Task> { + fn associated_tasks(&self, _: Option>, _: &App) -> Task> { Task::ready(Some(self.templates.clone())) } } diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 84ba3ed8c4994964390318f14961b89751a0cd7d..084d92c43188ecb66852b497d29afeb87507a04f 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -7,7 +7,6 @@ use client::{Client, UserStore}; use clock::FakeSystemClock; use collections::{HashMap, HashSet}; use language_model::{LanguageModelToolResultContent, fake_provider::FakeLanguageModel}; -use languages::rust_lang; use prompt_store::ProjectContext; use extension::ExtensionHostProxy; @@ -16,7 +15,7 @@ use gpui::{AppContext as _, Entity, SharedString, TestAppContext}; use http_client::{BlockedHttpClient, FakeHttpClient}; use language::{ Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding, - language_settings::{AllLanguageSettings, LanguageSettings}, + language_settings::{AllLanguageSettings, language_settings}, }; use lsp::{CompletionContext, CompletionResponse, CompletionTriggerKind, LanguageServerName}; use node_runtime::NodeRuntime; @@ -433,7 +432,6 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo let worktree_id = project .update(cx, |project, cx| { - project.languages().add(rust_lang()); project.find_or_create_worktree("/code/project1", true, cx) }) .await @@ -474,8 +472,9 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo }); cx.read(|cx| { + let file = buffer.read(cx).file(); assert_eq!( - LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers, + language_settings(Some("Rust".into()), file, cx).language_servers, ["override-rust-analyzer".to_string()] ) }); @@ -598,7 +597,6 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext let worktree_id = project .update(cx, |project, cx| { - project.languages().add(rust_lang()); project.find_or_create_worktree(path!("/code/project1"), true, cx) }) .await @@ -621,8 +619,9 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext let fake_second_lsp = fake_second_lsp.next().await.unwrap(); cx.read(|cx| { + let file = buffer.read(cx).file(); assert_eq!( - LanguageSettings::for_buffer(buffer.read(cx), cx).language_servers, + language_settings(Some("Rust".into()), file, cx).language_servers, ["rust-analyzer".to_string(), "fake-analyzer".to_string()] ) }); diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 45f87e4d47454da646fd43c0d220c027edc1299d..c09f856aad3265bf1c6de1f7f2dc6fdac3f266bd 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -216,7 +216,6 @@ impl VsCodeSettings { vim_mode: None, workspace: self.workspace_settings_content(), which_key: None, - modeline_lines: None, } } diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 6026e426c8e56559c39bf82741f39b51ee40f791..6923ba088b29afeaf3bc6ddbf6ca137c10f97166 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -176,13 +176,6 @@ pub struct SettingsContent { /// Settings related to Vim mode in Zed. pub vim: Option, - - /// Number of lines to search for modelines at the beginning and end of files. - /// Modelines contain editor directives (e.g., vim/emacs settings) that configure - /// the editor behavior for specific files. - /// - /// Default: 5 - pub modeline_lines: Option, } impl SettingsContent { diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 3e0a6f8cda401a293fe19d3b422141f19f73497b..dc9d1d81aed51da52e15cc030630d9d73991bac3 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -8216,7 +8216,7 @@ fn language_settings_data() -> Box<[SettingsPageItem]> { ] } - fn miscellaneous_section() -> [SettingsPageItem; 7] { + fn miscellaneous_section() -> [SettingsPageItem; 6] { [ SettingsPageItem::SectionHeader("Miscellaneous"), SettingsPageItem::SettingItem(SettingItem { @@ -8315,19 +8315,6 @@ fn language_settings_data() -> Box<[SettingsPageItem]> { metadata: None, files: USER | PROJECT, }), - SettingsPageItem::SettingItem(SettingItem { - title: "Vim/Emacs Modeline Support", - description: "Number of lines to search for modelines (set to 0 to disable).", - field: Box::new(SettingField { - json_path: Some("modeline_lines"), - pick: |settings_content| settings_content.modeline_lines.as_ref(), - write: |settings_content, value| { - settings_content.modeline_lines = value; - }, - }), - metadata: None, - files: USER | PROJECT, - }), ] } diff --git a/crates/tasks_ui/src/tasks_ui.rs b/crates/tasks_ui/src/tasks_ui.rs index 4a55da796da19e16520faa4bd59c7ce4f80bfb33..35c8a2ee220c6dba3732ca0f323bc50eb592ce19 100644 --- a/crates/tasks_ui/src/tasks_ui.rs +++ b/crates/tasks_ui/src/tasks_ui.rs @@ -204,19 +204,19 @@ where else { return Task::ready(Vec::new()); }; - let (language, buffer) = task_contexts + let (file, language) = task_contexts .location() .map(|location| { - let buffer = location.buffer.clone(); + let buffer = location.buffer.read(cx); ( - buffer.read(cx).language_at(location.range.start), - Some(buffer), + buffer.file().cloned(), + buffer.language_at(location.range.start), ) }) .unwrap_or_default(); task_inventory .read(cx) - .list_tasks(buffer, language, task_contexts.worktree(), cx) + .list_tasks(file, language, task_contexts.worktree(), cx) })? .await; diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index b673b6541d662a0621570b956b23d3327ae9bd3a..cc3bc65a56727dc8190a5ec5fe43ff8860031bab 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -22,7 +22,6 @@ - [Configuration](./ai/configuration.md) - [LLM Providers](./ai/llm-providers.md) - [Agent Settings](./ai/agent-settings.md) - - [Modelines](./modelines.md) - [Subscription](./ai/subscription.md) - [Models](./ai/models.md) - [Plans and Usage](./ai/plans-and-usage.md) diff --git a/docs/src/extensions/languages.md b/docs/src/extensions/languages.md index b43eb487b1a1d938ffe03cd040ef55657c6bc689..8bbf3d881084d66316558ec57a51f62ccc7d0ab6 100644 --- a/docs/src/extensions/languages.md +++ b/docs/src/extensions/languages.md @@ -27,7 +27,6 @@ line_comments = ["# "] - `tab_size` defines the indentation/tab size used for this language (default is `4`). - `hard_tabs` whether to indent with tabs (`true`) or spaces (`false`, the default). - `first_line_pattern` is a regular expression, that in addition to `path_suffixes` (above) or `file_types` in settings can be used to match files which should use this language. For example Zed uses this to identify Shell Scripts by matching the [shebangs lines](https://github.com/zed-industries/zed/blob/main/crates/languages/src/bash/config.toml) in the first line of a script. -- `modeline_aliases` is an array of additional Emacs modes or Vim filetypes to map modeline settings to Zed language. - `debuggers` is an array of strings that are used to identify debuggers in the language. When launching a debugger's `New Process Modal`, Zed will order available debuggers by the order of entries in this array.