diff --git a/Cargo.lock b/Cargo.lock index 652c584fd53795..def4b1e859eb18 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3685,6 +3685,12 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "ec4rs" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc37e66bbd9a1c221e31206689b5ff85961d830623ab0be9c6967d941094962c" + [[package]] name = "ecdsa" version = "0.14.8" @@ -10157,11 +10163,13 @@ version = "0.1.0" dependencies = [ "anyhow", "collections", + "ec4rs", "fs", "futures 0.3.30", "gpui", "indoc", "log", + "parking_lot", "paths", "pretty_assertions", "release_channel", diff --git a/Cargo.toml b/Cargo.toml index c72fec020fe678..88dbe439a49b01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -345,6 +345,7 @@ ctor = "0.2.6" dashmap = "6.0" derive_more = "0.99.17" dirs = "4.0" +ec4rs = "1.1" emojis = "0.6.1" env_logger = "0.11" exec = "0.3.1" diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 5d06720fe0095b..0f721a52a9d14b 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -44,9 +44,7 @@ use gpui::{ Transformation, UpdateGlobal, View, VisualContext, WeakView, WindowContext, }; use indexed_docs::IndexedDocsStore; -use language::{ - language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset, -}; +use language::{Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset}; use language_model::{ provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, Role, @@ -58,7 +56,7 @@ use project::lsp_store::LocalLspAdapterDelegate; use project::{Project, Worktree}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; -use settings::{update_settings_file, Settings}; +use settings::{update_settings_file, Settings, SoftWrap}; use smol::stream::StreamExt; use std::{ borrow::Cow, diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index c9360213ae5138..32cb2214f2a964 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1162,7 +1162,7 @@ impl InlineAssistant { enum DeletedLines {} let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::None, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_gutter(false, cx); editor.scroll_manager.set_forbid_vertical_scroll(true); @@ -1616,7 +1616,7 @@ impl PromptEditor { false, cx, ); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); // Since the prompt editors for all inline assistants are linked, // always show the cursor (even when it isn't focused) because // typing in one will make what you typed appear in all of them. @@ -1677,7 +1677,7 @@ impl PromptEditor { let focus = self.editor.focus_handle(cx).contains_focused(cx); self.editor = cx.new_view(|cx| { let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); editor.set_placeholder_text("Add a prompt…", cx); editor.set_text(prompt, cx); if focus { diff --git a/crates/assistant/src/prompt_library.rs b/crates/assistant/src/prompt_library.rs index 76ee95d5070b82..2f3c37ca0c1d50 100644 --- a/crates/assistant/src/prompt_library.rs +++ b/crates/assistant/src/prompt_library.rs @@ -17,7 +17,7 @@ use heed::{ types::{SerdeBincode, SerdeJson, Str}, Database, RoTxn, }; -use language::{language_settings::SoftWrap, Buffer, LanguageRegistry}; +use language::{Buffer, LanguageRegistry}; use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; @@ -26,7 +26,7 @@ use picker::{Picker, PickerDelegate}; use release_channel::ReleaseChannel; use rope::Rope; use serde::{Deserialize, Serialize}; -use settings::Settings; +use settings::{Settings, SoftWrap}; use std::{ cmp::Reverse, future::Future, diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index caf819bae535ee..15d73cb93f1989 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -663,7 +663,7 @@ impl PromptEditor { false, cx, ); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); editor.set_placeholder_text("Add a prompt…", cx); editor }); diff --git a/crates/assistant/src/workflow/step_view.rs b/crates/assistant/src/workflow/step_view.rs new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/crates/collab/src/tests/remote_editing_collaboration_tests.rs b/crates/collab/src/tests/remote_editing_collaboration_tests.rs index c4410fd776be7d..83aa341226af5d 100644 --- a/crates/collab/src/tests/remote_editing_collaboration_tests.rs +++ b/crates/collab/src/tests/remote_editing_collaboration_tests.rs @@ -100,7 +100,7 @@ async fn test_sharing_an_ssh_remote_project( let file = buffer_b.read(cx).file(); assert_eq!( all_language_settings(file, cx) - .language(Some(&("Rust".into()))) + .language(None, Some(&("Rust".into())), cx) .language_servers, ["override-rust-analyzer".into()] ) diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 38aaf2faa3b329..d275f275607ce7 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -9,12 +9,11 @@ use gpui::{ Render, Task, TextStyle, View, ViewContext, WeakView, }; use language::{ - language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, - LanguageServerId, ToOffset, + Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, LanguageServerId, ToOffset, }; use parking_lot::RwLock; use project::{search::SearchQuery, Completion}; -use settings::Settings; +use settings::{Settings, SoftWrap}; use std::{ops::Range, sync::Arc, sync::LazyLock, time::Duration}; use theme::ThemeSettings; use ui::{prelude::*, TextSize}; diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index cdbe65ba1dcca1..c3521cfc6aaccb 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -1235,7 +1235,11 @@ mod tests { unimplemented!() } - fn file_name<'a>(&'a self, _: &'a AppContext) -> &'a std::ffi::OsStr { + fn file_name(&self, _: &AppContext) -> &std::ffi::OsStr { + unimplemented!() + } + + fn abs_path_in_worktree(&self, _: &AppContext) -> Result { unimplemented!() } diff --git a/crates/copilot/src/copilot_completion_provider.rs b/crates/copilot/src/copilot_completion_provider.rs index c54fefad6fe599..1deccddb60ce13 100644 --- a/crates/copilot/src/copilot_completion_provider.rs +++ b/crates/copilot/src/copilot_completion_provider.rs @@ -77,7 +77,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider { let file = buffer.file(); let language = buffer.language_at(cursor_position); let settings = all_language_settings(file, cx); - settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref())) + settings.inline_completions_enabled(language.as_ref(), file, cx) } fn refresh( @@ -209,7 +209,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider { ) { let settings = AllLanguageSettings::get_global(cx); - let copilot_enabled = settings.inline_completions_enabled(None, None); + let copilot_enabled = settings.inline_completions_enabled(None, None, cx); if !copilot_enabled { return; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f797f82832f0ad..ca90c8c0bcfa55 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -91,7 +91,7 @@ pub use inline_completion_provider::*; pub use items::MAX_TAB_TITLE_LEN; use itertools::Itertools; use language::{ - language_settings::{self, all_language_settings, InlayHintSettings}, + language_settings::{all_language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, @@ -414,7 +414,7 @@ impl Default for EditorStyle { pub fn make_inlay_hints_style(cx: &WindowContext) -> HighlightStyle { let show_background = all_language_settings(None, cx) - .language(None) + .language(None, None, cx) .inlay_hints .show_background; @@ -531,7 +531,7 @@ pub struct Editor { select_larger_syntax_node_stack: Vec]>>, ime_transaction: Option, active_diagnostics: Option, - soft_wrap_mode_override: Option, + soft_wrap_mode_override: Option, project: Option>, completion_provider: Option>, collaboration_hub: Option>, @@ -1804,8 +1804,8 @@ impl Editor { let blink_manager = cx.new_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx)); - let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. }) - .then(|| language_settings::SoftWrap::PreferLine); + let soft_wrap_mode_override = + matches!(mode, EditorMode::SingleLine { .. }).then(|| settings::SoftWrap::PreferLine); let mut project_subscriptions = Vec::new(); if mode == EditorMode::Full { @@ -10784,23 +10784,17 @@ impl Editor { let settings = self.buffer.read(cx).settings_at(0, cx); let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap); match mode { - language_settings::SoftWrap::None => SoftWrap::None, - language_settings::SoftWrap::PreferLine => SoftWrap::PreferLine, - language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, - language_settings::SoftWrap::PreferredLineLength => { + settings::SoftWrap::None => SoftWrap::None, + settings::SoftWrap::PreferLine => SoftWrap::PreferLine, + settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth, + settings::SoftWrap::PreferredLineLength => { SoftWrap::Column(settings.preferred_line_length) } - language_settings::SoftWrap::Bounded => { - SoftWrap::Bounded(settings.preferred_line_length) - } + settings::SoftWrap::Bounded => SoftWrap::Bounded(settings.preferred_line_length), } } - pub fn set_soft_wrap_mode( - &mut self, - mode: language_settings::SoftWrap, - cx: &mut ViewContext, - ) { + pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext) { self.soft_wrap_mode_override = Some(mode); cx.notify(); } @@ -10833,9 +10827,9 @@ impl Editor { self.soft_wrap_mode_override.take(); } else { let soft_wrap = match self.soft_wrap_mode(cx) { - SoftWrap::None | SoftWrap::PreferLine => language_settings::SoftWrap::EditorWidth, + SoftWrap::None | SoftWrap::PreferLine => settings::SoftWrap::EditorWidth, SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => { - language_settings::SoftWrap::PreferLine + settings::SoftWrap::PreferLine } }; self.soft_wrap_mode_override = Some(soft_wrap); @@ -12679,7 +12673,11 @@ fn inlay_hint_settings( let language = snapshot.language_at(location); let settings = all_language_settings(file, cx); settings - .language(language.map(|l| l.name()).as_ref()) + .language( + file.and_then(|file| Some((file.worktree_id(cx), file.abs_path_in_worktree(cx).ok()?))), + language.map(|l| l.name()).as_ref(), + cx, + ) .inlay_hints } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index e11b38ba59680d..ad065f57ae6983 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -16,14 +16,14 @@ use gpui::{ use indoc::indoc; use language::{ language_settings::{ - AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent, PrettierSettings, + self, AllLanguageSettings, AllLanguageSettingsContent, Formatter, FormatterList, + IndentGuideSettings, LanguageSettingsContent, PrettierSettings, }, BracketPairConfig, Capability::ReadWrite, FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName, Override, ParsedMarkdown, Point, }; -use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use multi_buffer::MultiBufferIndentGuide; use parking_lot::Mutex; use project::FakeFs; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 47107b97546871..e37af55cf5c26a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -6329,7 +6329,6 @@ mod tests { Editor, MultiBuffer, }; use gpui::{TestAppContext, VisualTestContext}; - use language::language_settings; use log::info; use std::num::NonZeroU32; use ui::Context; @@ -6730,7 +6729,7 @@ mod tests { s.defaults.tab_size = NonZeroU32::new(tab_size); s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All); s.defaults.preferred_line_length = Some(editor_width as u32); - s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength); + s.defaults.soft_wrap = Some(settings::SoftWrap::PreferredLineLength); }); let actual_invisibles = collect_invisibles_from_new_editor( @@ -6785,7 +6784,7 @@ mod tests { let style = cx.update(|cx| editor.read(cx).style().unwrap().clone()); window .update(cx, |editor, cx| { - editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); editor.set_wrap_width(Some(editor_width), cx); }) .unwrap(); diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 5dc73634bda774..31ef78f45c9370 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -777,7 +777,7 @@ fn editor_with_deleted_text( }); let mut editor = Editor::for_multibuffer(multi_buffer, None, true, cx); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::None, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::None, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_gutter(false, cx); editor.scroll_manager.set_forbid_vertical_scroll(true); diff --git a/crates/extension/src/wasm_host/wit/since_v0_1_0.rs b/crates/extension/src/wasm_host/wit/since_v0_1_0.rs index 50547b6371c697..a22708df1cccd9 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_1_0.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_1_0.rs @@ -355,8 +355,12 @@ impl ExtensionImports for WasmState { cx.update(|cx| match category.as_str() { "language" => { let key = key.map(|k| LanguageName::new(&k)); - let settings = - AllLanguageSettings::get(location, cx).language(key.as_ref()); + let settings = AllLanguageSettings::get(location, cx).language( + location + .map(|location| (location.worktree_id, location.path.to_owned())), + key.as_ref(), + cx, + ); Ok(serde_json::to_string(&settings::LanguageSettings { tab_size: settings.tab_size, })?) diff --git a/crates/extension/src/wasm_host/wit/since_v0_2_0.rs b/crates/extension/src/wasm_host/wit/since_v0_2_0.rs index 7fa79c2544475b..9115a3ca5022d1 100644 --- a/crates/extension/src/wasm_host/wit/since_v0_2_0.rs +++ b/crates/extension/src/wasm_host/wit/since_v0_2_0.rs @@ -402,7 +402,8 @@ impl ExtensionImports for WasmState { "language" => { let key = key.map(|k| LanguageName::new(&k)); let settings = - AllLanguageSettings::get(location, cx).language(key.as_ref()); + // TODO kb use `location` instead of `None` + AllLanguageSettings::get(location, cx).language(None, key.as_ref(), cx); Ok(serde_json::to_string(&settings::LanguageSettings { tab_size: settings.tab_size, })?) diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index abf8266320247e..24cdf096dc144d 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -62,7 +62,7 @@ impl Render for InlineCompletionButton { let status = copilot.read(cx).status(); let enabled = self.editor_enabled.unwrap_or_else(|| { - all_language_settings.inline_completions_enabled(None, None) + all_language_settings.inline_completions_enabled(None, None, cx) }); let icon = match status { @@ -292,7 +292,7 @@ impl InlineCompletionButton { ); } - let globally_enabled = settings.inline_completions_enabled(None, None); + let globally_enabled = settings.inline_completions_enabled(None, None, cx); menu.entry( if globally_enabled { "Hide Inline Completions for All Files" @@ -337,10 +337,8 @@ impl InlineCompletionButton { let file = file.as_ref(); Some( file.map(|file| !file.is_private()).unwrap_or(true) - && all_language_settings(file, cx).inline_completions_enabled( - language, - file.map(|file| file.path().as_ref()), - ), + && all_language_settings(file, cx) + .inline_completions_enabled(language, file, cx), ) }; self.language = language.cloned(); @@ -442,7 +440,7 @@ async fn configure_disabled_globs( fn toggle_inline_completions_globally(fs: Arc, cx: &mut AppContext) { let show_inline_completions = - all_language_settings(None, cx).inline_completions_enabled(None, None); + all_language_settings(None, cx).inline_completions_enabled(None, None, cx); update_settings_file::(fs, cx, move |file, _| { file.defaults.show_inline_completions = Some(!show_inline_completions) }); @@ -466,7 +464,7 @@ fn toggle_inline_completions_for_language( cx: &mut AppContext, ) { let show_inline_completions = - all_language_settings(None, cx).inline_completions_enabled(Some(&language), None); + all_language_settings(None, cx).inline_completions_enabled(Some(&language), None, cx); update_settings_file::(fs, cx, move |file, _| { file.languages .entry(language.name()) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 43fe1565acb796..8aa39c28a19bcb 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -35,6 +35,7 @@ use smallvec::SmallVec; use smol::future::yield_now; use std::{ any::Any, + borrow::Cow, cell::Cell, cmp::{self, Ordering, Reverse}, collections::BTreeMap, @@ -358,6 +359,10 @@ pub trait File: Send + Sync { /// includes the name of the worktree's root folder). fn full_path(&self, cx: &AppContext) -> PathBuf; + /// Returns the full path to this file, resolved in its worktree. + /// This may be a non-existing path if the file is not local. + fn abs_path_in_worktree(&self, cx: &AppContext) -> Result; + /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr; @@ -2658,7 +2663,7 @@ impl BufferSnapshot { &self, position: D, cx: &'a AppContext, - ) -> &'a LanguageSettings { + ) -> Cow<'a, LanguageSettings> { language_settings(self.language_at(position), self.file.as_ref(), cx) } @@ -4178,6 +4183,10 @@ impl File for TestFile { self.path().file_name().unwrap_or(self.root_name.as_ref()) } + fn abs_path_in_worktree(&self, _: &AppContext) -> Result { + unimplemented!() + } + fn worktree_id(&self, _: &AppContext) -> WorktreeId { WorktreeId::from_usize(0) } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 309a67a1a96a41..e0c7a5b8e67d5b 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -20,7 +20,6 @@ mod task_context; pub mod buffer_tests; pub mod markdown; -use crate::language_settings::SoftWrap; use anyhow::{anyhow, Context, Result}; use async_trait::async_trait; use collections::{HashMap, HashSet}; @@ -39,7 +38,7 @@ use schemars::{ }; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; -use settings::WorktreeId; +use settings::{SoftWrap, WorktreeId}; use smol::future::FutureExt as _; use std::num::NonZeroU32; use std::{ diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 77c9a1d18cee14..d1cf2ef4a849cc 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -16,8 +16,16 @@ use serde::{ Deserialize, Deserializer, Serialize, }; use serde_json::Value; -use settings::{add_references_to_properties, Settings, SettingsLocation, SettingsSources}; -use std::{num::NonZeroU32, path::Path, sync::Arc}; +use settings::{ + add_references_to_properties, EditorConfigContent, Settings, SettingsLocation, SettingsSources, + SettingsStore, SoftWrap, WorktreeId, +}; +use std::{ + borrow::Cow, + num::NonZeroU32, + path::{Path, PathBuf}, + sync::Arc, +}; use util::serde::default_true; /// Initializes the language settings. @@ -30,9 +38,13 @@ pub fn language_settings<'a>( language: Option<&Arc>, file: Option<&Arc>, cx: &'a AppContext, -) -> &'a LanguageSettings { +) -> Cow<'a, LanguageSettings> { let language_name = language.map(|l| l.name()); - all_language_settings(file, cx).language(language_name.as_ref()) + all_language_settings(file, cx).language( + file.and_then(|file| Some((file.worktree_id(cx), file.abs_path_in_worktree(cx).ok()?))), + language_name.as_ref(), + cx, + ) } /// Returns the settings for all languages from the provided file. @@ -365,22 +377,6 @@ pub struct FeaturesContent { pub inline_completion_provider: Option, } -/// Controls the soft-wrapping behavior in the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] -#[serde(rename_all = "snake_case")] -pub enum SoftWrap { - /// Do not soft wrap. - None, - /// Prefer a single line generally, unless an overly long line is encountered. - PreferLine, - /// Soft wrap lines that exceed the editor width - EditorWidth, - /// Soft wrap lines at the preferred line length - PreferredLineLength, - /// Soft wrap line at the preferred line length or the editor width (whichever is smaller) - Bounded, -} - /// Controls the behavior of formatting files when they are saved. #[derive(Debug, Clone, PartialEq, Eq)] pub enum FormatOnSave { @@ -799,13 +795,32 @@ impl InlayHintSettings { impl AllLanguageSettings { /// Returns the [`LanguageSettings`] for the language with the specified name. - pub fn language<'a>(&'a self, language_name: Option<&LanguageName>) -> &'a LanguageSettings { - if let Some(name) = language_name { - if let Some(overrides) = self.languages.get(name) { - return overrides; - } + pub fn language<'a>( + &'a self, + // TODO kb wrong API, store the previous file in the `AllLanguageSettings` instead of requiring it here + abs_path_in_worktree: Option<(WorktreeId, PathBuf)>, + language_name: Option<&LanguageName>, + cx: &'a AppContext, + ) -> Cow<'a, LanguageSettings> { + let settings = language_name + .and_then(|name| self.languages.get(name)) + .unwrap_or(&self.defaults); + + let editorconfig_settings = + abs_path_in_worktree.and_then(|(worktree_id, file_abs_path)| { + cx.global::().editorconfig_settings( + worktree_id, + language_name.map(|name| name.0.to_string()), + &file_abs_path, + ) + }); + if let Some(content) = editorconfig_settings { + let mut settings = settings.clone(); + merge_with_editorconfig(&mut settings, &content); + Cow::Owned(settings) + } else { + Cow::Borrowed(settings) } - &self.defaults } /// Returns whether inline completions are enabled for the given path. @@ -821,16 +836,21 @@ impl AllLanguageSettings { pub fn inline_completions_enabled( &self, language: Option<&Arc>, - path: Option<&Path>, + file: Option<&Arc>, + cx: &AppContext, ) -> bool { - if let Some(path) = path { + if let Some(path) = file.map(|f| f.path()) { if !self.inline_completions_enabled_for_path(path) { return false; } } - self.language(language.map(|l| l.name()).as_ref()) - .show_inline_completions + self.language( + file.and_then(|file| Some((file.worktree_id(cx), file.abs_path_in_worktree(cx).ok()?))), + language.map(|l| l.name()).as_ref(), + cx, + ) + .show_inline_completions } } @@ -1089,6 +1109,28 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent merge(&mut settings.inlay_hints, src.inlay_hints); } +fn merge_with_editorconfig(settings: &mut LanguageSettings, content: &EditorConfigContent) { + fn merge(target: &mut T, value: Option) { + if let Some(value) = value { + *target = value; + } + } + merge(&mut settings.tab_size, content.tab_size); + merge(&mut settings.hard_tabs, content.hard_tabs); + merge( + &mut settings.remove_trailing_whitespace_on_save, + content.remove_trailing_whitespace_on_save, + ); + merge( + &mut settings.ensure_final_newline_on_save, + content.ensure_final_newline_on_save, + ); + merge( + &mut settings.preferred_line_length, + content.preferred_line_length, + ); + merge(&mut settings.soft_wrap, content.soft_wrap); +} /// Allows to enable/disable formatting with Prettier /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. diff --git a/crates/languages/src/rust.rs b/crates/languages/src/rust.rs index a32ffe50f519f1..9869caac661595 100644 --- a/crates/languages/src/rust.rs +++ b/crates/languages/src/rust.rs @@ -480,11 +480,12 @@ impl ContextProvider for RustContextProvider { cx: &AppContext, ) -> Option { const DEFAULT_RUN_NAME_STR: &str = "RUST_DEFAULT_PACKAGE_RUN"; - let package_to_run = all_language_settings(file.as_ref(), cx) - .language(Some(&"Rust".into())) - .tasks - .variables - .get(DEFAULT_RUN_NAME_STR); + let language_settings = all_language_settings(file.as_ref(), cx).language( + file.and_then(|file| Some((file.worktree_id(cx), file.abs_path_in_worktree(cx).ok()?))), + Some(&"Rust".into()), + cx, + ); + let package_to_run = language_settings.tasks.variables.get(DEFAULT_RUN_NAME_STR); let run_task_args = if let Some(package_to_run) = package_to_run { vec!["run".into(), "-p".into(), package_to_run.clone()] } else { diff --git a/crates/languages/src/yaml.rs b/crates/languages/src/yaml.rs index 06360847acc803..b8db0b2c8e4c4b 100644 --- a/crates/languages/src/yaml.rs +++ b/crates/languages/src/yaml.rs @@ -139,7 +139,7 @@ impl LspAdapter for YamlLspAdapter { let tab_size = cx.update(|cx| { AllLanguageSettings::get(Some(location), cx) - .language(Some(&"YAML".into())) + .language(None, Some(&"YAML".into()), cx) .tab_size })?; let mut options = serde_json::json!({"[yaml]": {"editor.tabSize": tab_size}}); diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 9dee092dea9f29..99010f258f10ab 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1727,7 +1727,7 @@ impl MultiBuffer { &self, point: T, cx: &'a AppContext, - ) -> &'a LanguageSettings { + ) -> Cow<'a, LanguageSettings> { let mut language = None; let mut file = None; if let Some((buffer, offset, _)) = self.point_to_buffer_offset(point, cx) { @@ -3462,7 +3462,7 @@ impl MultiBufferSnapshot { &'a self, point: T, cx: &'a AppContext, - ) -> &'a LanguageSettings { + ) -> Cow<'a, LanguageSettings> { let mut language = None; let mut file = None; if let Some((buffer, offset)) = self.point_to_buffer_offset(point) { diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index 2b7b10d9b369a9..3867cc989bb535 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -2304,7 +2304,7 @@ impl LspCommand for OnTypeFormatting { .await?; let options = buffer.update(&mut cx, |buffer, cx| { - lsp_formatting_options(language_settings(buffer.language(), buffer.file(), cx)) + lsp_formatting_options(&language_settings(buffer.language(), buffer.file(), cx)) })?; Ok(Self { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 58d9ba8926737d..f7fd00abf0f765 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -59,6 +59,7 @@ use smol::channel::Sender; use snippet::Snippet; use std::{ any::Any, + borrow::Cow, cmp::Ordering, convert::TryInto, ffi::OsStr, @@ -725,7 +726,8 @@ impl LspStore { }); let buffer_file = buffer.read(cx).file().cloned(); - let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + let settings = + language_settings(Some(&new_language), 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 { @@ -900,8 +902,10 @@ impl LspStore { language_servers_to_start.push((file.worktree.clone(), language.name())); } } - language_formatters_to_check - .push((buffer_file.map(|f| f.worktree_id(cx)), settings.clone())); + language_formatters_to_check.push(( + buffer_file.map(|f| f.worktree_id(cx)), + settings.into_owned(), + )); } } @@ -1247,9 +1251,14 @@ impl LspStore { .filter(|_| { maybe!({ let language_name = buffer.read(cx).language_at(position)?.name(); + let abs_path_in_a_worktree = buffer.read(cx).file().and_then(|file| { + let worktree_id = file.worktree_id(cx); + let abs_path = file.abs_path_in_worktree(cx).ok()?; + Some((worktree_id, abs_path)) + }); Some( AllLanguageSettings::get_global(cx) - .language(Some(&language_name)) + .language(abs_path_in_a_worktree, Some(&language_name), cx) .linked_edits, ) }) == Some(true) @@ -1348,7 +1357,7 @@ impl LspStore { cx: &mut ModelContext, ) -> Task>> { let options = buffer.update(cx, |buffer, cx| { - lsp_command::lsp_formatting_options(language_settings( + lsp_command::lsp_formatting_options(&language_settings( buffer.language_at(position).as_ref(), buffer.file(), cx, @@ -4677,9 +4686,17 @@ impl LspStore { worktree: &'a Model, language: &LanguageName, cx: &'a mut ModelContext, - ) -> &'a LanguageSettings { + ) -> Cow<'a, LanguageSettings> { let root_file = worktree.update(cx, |tree, cx| tree.root_file(cx)); - all_language_settings(root_file.map(|f| f as _).as_ref(), cx).language(Some(language)) + let abs_path_in_worktree = root_file.as_ref().and_then(|f| { + let worktree = worktree.read(cx); + Some((worktree.id(), f.abs_path_in_worktree(cx).ok()?)) + }); + all_language_settings(root_file.map(|f| f as _).as_ref(), cx).language( + abs_path_in_worktree, + Some(language), + cx, + ) } pub fn start_language_servers( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index f4816cf0cde66f..edfa1484a7a750 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -110,6 +110,8 @@ const MAX_PROJECT_SEARCH_HISTORY_SIZE: usize = 500; const MAX_SEARCH_RESULT_FILES: usize = 5_000; const MAX_SEARCH_RESULT_RANGES: usize = 10_000; +const EDITORCONFIG_FILE_NAME: &str = ".editorconfig"; + pub trait Item { fn try_open( project: &Model, @@ -2575,7 +2577,7 @@ impl Project { })?; let settings = buffer.update(&mut cx, |buffer, cx| { - language_settings(buffer.language(), buffer.file(), cx).clone() + language_settings(buffer.language(), buffer.file(), cx).into_owned() })?; let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save; @@ -3809,6 +3811,16 @@ impl Project { ); } }) + } else if path.ends_with(EDITORCONFIG_FILE_NAME) { + let settings_store = cx.global_mut::(); + if let Some(directory_abs_path) = abs_path.parent() { + let directory_abs_path = directory_abs_path.to_owned(); + if removed { + settings_store.unregister_editorconfig_directory(&directory_abs_path); + } else { + settings_store.register_editorconfig_directory(directory_abs_path); + } + } } } } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 72a38ccba7d781..55672ad2ea3f90 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -12,10 +12,11 @@ use lsp::{DiagnosticSeverity, NumberOrString}; use parking_lot::Mutex; use pretty_assertions::assert_eq; use serde_json::json; +use settings::SoftWrap; #[cfg(not(windows))] use std::os; -use std::{mem, ops::Range, task::Poll}; +use std::{mem, num::NonZeroU32, ops::Range, task::Poll}; use task::{ResolvedTask, TaskContext, TaskTemplate, TaskTemplates}; use unindent::Unindent as _; use util::{assert_set_eq, paths::PathMatcher, test::temp_tree, TryFutureExt as _}; @@ -91,6 +92,106 @@ async fn test_symlinks(cx: &mut gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_editorconfig_support(cx: &mut gpui::TestAppContext) { + init_test(cx); + + let dir = temp_tree(json!({ + ".editorconfig": r#" + root = true + [*.rs] + indent_style = tab + indent_size = 3 + end_of_line = lf + insert_final_newline = true + trim_trailing_whitespace = true + max_line_length = 80 + [*.js] + tab_width = 10 + "#, + ".zed": { + "settings.json": r#"{ + "tab_size": 8, + "hard_tabs": false, + "ensure_final_newline_on_save": false, + "remove_trailing_whitespace_on_save": false, + "preferred_line_length": 64, + "soft_wrap": "editor_width" + }"#, + }, + "a.rs": "fn a() {\n A\n}", + "b": { + ".editorconfig": r#" + [*.rs] + indent_size = 2 + max_line_length = off + "#, + "b.rs": "fn b() {\n B\n}", + }, + "c.js": "def c\n C\nend", + "README.json": "tabs are better\n", + })); + + let path = dir.path(); + let fs = FakeFs::new(cx.executor()); + fs.insert_tree_from_real_fs(path, path).await; + let project = Project::test(fs, [path], cx).await; + + let language_registry = project.read_with(cx, |project, _| project.languages().clone()); + language_registry.add(js_lang()); + language_registry.add(json_lang()); + language_registry.add(rust_lang()); + + let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap()); + + cx.executor().run_until_parked(); + + cx.update(|cx| { + let tree = worktree.read(cx); + let settings_for = |path: &str| { + let file_entry = tree.entry_for_path(path).unwrap().clone(); + let file = File::for_entry(file_entry, worktree.clone()); + let file_language = project + .read(cx) + .languages() + .language_for_file_path(file.path.as_ref()); + let file_language = cx + .background_executor() + .block(file_language) + .expect("Failed to get file language"); + language_settings(Some(&file_language), Some(&(file as _)), cx) + }; + + 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, 80); + + // "max_line_length" also sets "soft_wrap" + assert_eq!(settings_a.soft_wrap, SoftWrap::PreferredLineLength); + + // .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_b.soft_wrap, SoftWrap::EditorWidth); + + // README.md should not be affected by .editorconfig's globe "*.rs" + assert_eq!(Some(settings_readme.tab_size), NonZeroU32::new(8)); + }); +} + #[gpui::test] async fn test_managing_project_specific_settings(cx: &mut gpui::TestAppContext) { init_test(cx); diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index b7fc56d3c60262..42e5e93475f454 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -203,7 +203,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo server_cx.read(|cx| { assert_eq!( AllLanguageSettings::get_global(cx) - .language(Some(&"Rust".into())) + .language(None, Some(&"Rust".into()), cx) .language_servers, ["custom-rust-analyzer".into()] ) @@ -262,7 +262,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo }), cx ) - .language(Some(&"Rust".into())) + .language(None, Some(&"Rust".into()), cx) .language_servers, ["override-rust-analyzer".into()] ) @@ -272,7 +272,7 @@ async fn test_remote_settings(cx: &mut TestAppContext, server_cx: &mut TestAppCo let file = buffer.read(cx).file(); assert_eq!( all_language_settings(file, cx) - .language(Some(&"Rust".into())) + .language(None, Some(&"Rust".into()), cx) .language_servers, ["override-rust-analyzer".into()] ) @@ -355,7 +355,7 @@ async fn test_remote_lsp(cx: &mut TestAppContext, server_cx: &mut TestAppContext let file = buffer.read(cx).file(); assert_eq!( all_language_settings(file, cx) - .language(Some(&"Rust".into())) + .language(None, Some(&"Rust".into()), cx) .language_servers, ["rust-analyzer".into()] ) diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index e9f6f6e4899cc7..b95c3d41e466f3 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -18,11 +18,13 @@ test-support = ["gpui/test-support", "fs/test-support"] [dependencies] anyhow.workspace = true collections.workspace = true +ec4rs.workspace = true fs.workspace = true futures.workspace = true gpui.workspace = true log.workspace = true paths.workspace = true +parking_lot.workspace = true release_channel.workspace = true rust-embed.workspace = true schemars.workspace = true diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 5ece3f867e4ff4..05881670ac0968 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -13,7 +13,9 @@ pub use editable_setting_control::*; pub use json_schema::*; pub use keymap_file::KeymapFile; pub use settings_file::*; -pub use settings_store::{Settings, SettingsLocation, SettingsSources, SettingsStore}; +pub use settings_store::{ + EditorConfigContent, Settings, SettingsLocation, SettingsSources, SettingsStore, SoftWrap, +}; #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] pub struct WorktreeId(usize); diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 3ef8bffe2d3ded..5337a94c8e5a17 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1,16 +1,21 @@ use anyhow::{anyhow, Context, Result}; -use collections::{btree_map, hash_map, BTreeMap, HashMap}; +use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet}; +use ec4rs::property::{ + FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs, +}; use fs::Fs; use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal}; +use parking_lot::RwLock; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; -use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use smallvec::SmallVec; use std::{ any::{type_name, Any, TypeId}, fmt::Debug, + num::NonZeroU32, ops::Range, - path::Path, + path::{Path, PathBuf}, str, sync::{Arc, LazyLock}, }; @@ -156,6 +161,33 @@ pub struct SettingsLocation<'a> { pub path: &'a Path, } +/// The settings available in `.editorconfig` files and compatible with Zed. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct EditorConfigContent { + pub hard_tabs: Option, + pub tab_size: Option, + pub ensure_final_newline_on_save: Option, + pub remove_trailing_whitespace_on_save: Option, + pub preferred_line_length: Option, + pub soft_wrap: Option, +} + +/// Controls the soft-wrapping behavior in the editor. +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum SoftWrap { + /// Do not soft wrap. + None, + /// Prefer a single line generally, unless an overly long line is encountered. + PreferLine, + /// Soft wrap lines that overflow the editor + EditorWidth, + /// Soft wrap lines at the preferred line length + PreferredLineLength, + /// Soft wrap line at the preferred line length or the editor width (whichever is smaller) + Bounded, +} + /// A set of strongly-typed setting values defined via multiple JSON files. pub struct SettingsStore { setting_values: HashMap>, @@ -171,6 +203,15 @@ pub struct SettingsStore { setting_file_updates_tx: mpsc::UnboundedSender< Box LocalBoxFuture<'static, Result<()>>>, >, + dirs_with_editorconfig: HashSet, + resolved_editorconfig_chains: Arc>>, +} + +#[derive(Debug, Eq, PartialEq, Hash)] +struct EditorConfigKey { + language_name: Option, + worktree_id: WorktreeId, + editorconfig_chain: SmallVec<[PathBuf; 3]>, } impl Global for SettingsStore {} @@ -219,6 +260,8 @@ impl SettingsStore { (setting_file_update)(cx.clone()).await.log_err(); } }), + dirs_with_editorconfig: HashSet::default(), + resolved_editorconfig_chains: Arc::default(), } } @@ -780,6 +823,98 @@ impl SettingsStore { } Ok(()) } + + pub fn unregister_editorconfig_directory(&mut self, abs_path: &PathBuf) { + self.resolved_editorconfig_chains + .write() + .retain(|key, _| !key.editorconfig_chain.contains(abs_path)); + self.dirs_with_editorconfig.remove(abs_path); + } + + pub fn register_editorconfig_directory(&mut self, abs_path: PathBuf) { + self.resolved_editorconfig_chains + .write() + .retain(|key, _| !key.editorconfig_chain.contains(&abs_path)); + self.dirs_with_editorconfig.insert(abs_path); + } + + pub fn editorconfig_settings( + &self, + worktree_id: WorktreeId, + language_name: Option, + file_abs_path: &Path, + ) -> Option { + let mut editorconfig_chain = SmallVec::new(); + for ancestor_path in file_abs_path.ancestors() { + if self.dirs_with_editorconfig.contains(ancestor_path) { + editorconfig_chain.push(ancestor_path.to_owned()); + } + } + if editorconfig_chain.is_empty() { + return None; + } + + let chain_key = EditorConfigKey { + language_name, + worktree_id, + editorconfig_chain, + }; + + { + let resolved_editorconfig_chains = self.resolved_editorconfig_chains.read(); + if let Some(content) = resolved_editorconfig_chains.get(&chain_key) { + return Some(content.clone()); + } + } + + // FS operation that goes recursively up the directory tree, may be slow + let mut cfg = ec4rs::properties_of(file_abs_path).log_err()?; + if cfg.is_empty() { + return None; + } + + cfg.use_fallbacks(); + let max_line_length = cfg.get::().ok().and_then(|v| match v { + MaxLineLen::Value(u) => Some(u as u32), + MaxLineLen::Off => None, + }); + let editorconfig = EditorConfigContent { + tab_size: cfg.get::().ok().and_then(|v| match v { + IndentSize::Value(u) => NonZeroU32::new(u as u32), + IndentSize::UseTabWidth => cfg.get::().ok().and_then(|w| match w { + TabWidth::Value(u) => NonZeroU32::new(u as u32), + }), + }), + hard_tabs: cfg + .get::() + .map(|v| v.eq(&IndentStyle::Tabs)) + .ok(), + ensure_final_newline_on_save: cfg + .get::() + .map(|v| match v { + FinalNewline::Value(b) => b, + }) + .ok(), + remove_trailing_whitespace_on_save: cfg + .get::() + .map(|v| match v { + TrimTrailingWs::Value(b) => b, + }) + .ok(), + preferred_line_length: max_line_length, + soft_wrap: if max_line_length.is_some() { + Some(SoftWrap::PreferredLineLength) + } else { + None + }, + }; + + self.resolved_editorconfig_chains + .write() + .insert(chain_key, editorconfig.clone()); + + Some(editorconfig) + } } impl Debug for SettingsStore { diff --git a/crates/storybook/src/stories/auto_height_editor.rs b/crates/storybook/src/stories/auto_height_editor.rs index 7b3cee92c8bad7..c4b0aadabf3ffb 100644 --- a/crates/storybook/src/stories/auto_height_editor.rs +++ b/crates/storybook/src/stories/auto_height_editor.rs @@ -18,7 +18,7 @@ impl AutoHeightEditorStory { cx.new_view(|cx| Self { editor: cx.new_view(|cx| { let mut editor = Editor::auto_height(3, cx); - editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx); + editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx); editor }), }) diff --git a/crates/supermaven/src/supermaven_completion_provider.rs b/crates/supermaven/src/supermaven_completion_provider.rs index 261ce372d9f717..47a1a0082c6b03 100644 --- a/crates/supermaven/src/supermaven_completion_provider.rs +++ b/crates/supermaven/src/supermaven_completion_provider.rs @@ -119,7 +119,7 @@ impl InlineCompletionProvider for SupermavenCompletionProvider { let file = buffer.file(); let language = buffer.language_at(cursor_position); let settings = all_language_settings(file, cx); - settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref())) + settings.inline_completions_enabled(language.as_ref(), file, cx) } fn refresh( diff --git a/crates/vim/src/test/neovim_backed_test_context.rs b/crates/vim/src/test/neovim_backed_test_context.rs index 16ab7771bdd76e..9e18ac1325b405 100644 --- a/crates/vim/src/test/neovim_backed_test_context.rs +++ b/crates/vim/src/test/neovim_backed_test_context.rs @@ -1,12 +1,12 @@ use gpui::{px, size, Context, UpdateGlobal}; use indoc::indoc; -use settings::SettingsStore; +use settings::{SettingsStore, SoftWrap}; use std::{ ops::{Deref, DerefMut}, panic, thread, }; -use language::language_settings::{AllLanguageSettings, SoftWrap}; +use language::language_settings::AllLanguageSettings; use util::test::marked_text_offsets; use super::{neovim_connection::NeovimConnection, VimTestContext}; diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index d8555b71a4f67c..899cb3d6f5418f 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -3093,6 +3093,10 @@ impl language::File for File { full_path } + fn abs_path_in_worktree(&self, cx: &AppContext) -> Result { + self.worktree.read(cx).absolutize(&self.path) + } + /// Returns the last component of this handle's absolute path. If this handle refers to the root /// of its worktree, then this method will return the name of the worktree itself. fn file_name<'a>(&'a self, cx: &'a AppContext) -> &'a OsStr {