mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Merge branch 'main' into implicit-ancestry
This commit is contained in:
commit
da19edc3e3
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -1189,7 +1189,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@ -1362,6 +1362,7 @@ name = "copilot_button"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"assets",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
"copilot",
|
"copilot",
|
||||||
"editor",
|
"editor",
|
||||||
@ -1984,6 +1985,7 @@ dependencies = [
|
|||||||
"futures 0.3.25",
|
"futures 0.3.25",
|
||||||
"fuzzy",
|
"fuzzy",
|
||||||
"git",
|
"git",
|
||||||
|
"glob",
|
||||||
"gpui",
|
"gpui",
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
@ -2155,6 +2157,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"settings",
|
"settings",
|
||||||
|
"smallvec",
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"theme",
|
"theme",
|
||||||
"tree-sitter-markdown",
|
"tree-sitter-markdown",
|
||||||
@ -5965,8 +5968,10 @@ dependencies = [
|
|||||||
"collections",
|
"collections",
|
||||||
"fs",
|
"fs",
|
||||||
"futures 0.3.25",
|
"futures 0.3.25",
|
||||||
|
"glob",
|
||||||
"gpui",
|
"gpui",
|
||||||
"json_comments",
|
"json_comments",
|
||||||
|
"lazy_static",
|
||||||
"postage",
|
"postage",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"schemars",
|
"schemars",
|
||||||
@ -8454,6 +8459,7 @@ name = "workspace"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"assets",
|
||||||
"async-recursion 1.0.0",
|
"async-recursion 1.0.0",
|
||||||
"bincode",
|
"bincode",
|
||||||
"call",
|
"call",
|
||||||
@ -8540,7 +8546,7 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.85.0"
|
version = "0.86.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activity_indicator",
|
"activity_indicator",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
@ -77,6 +77,7 @@ async-trait = { version = "0.1" }
|
|||||||
ctor = { version = "0.1" }
|
ctor = { version = "0.1" }
|
||||||
env_logger = { version = "0.9" }
|
env_logger = { version = "0.9" }
|
||||||
futures = { version = "0.3" }
|
futures = { version = "0.3" }
|
||||||
|
glob = { version = "0.3.1" }
|
||||||
lazy_static = { version = "1.4.0" }
|
lazy_static = { version = "1.4.0" }
|
||||||
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
log = { version = "0.4.16", features = ["kv_unstable_serde"] }
|
||||||
ordered-float = { version = "2.1.1" }
|
ordered-float = { version = "2.1.1" }
|
||||||
|
@ -115,6 +115,13 @@
|
|||||||
// "git_gutter": "hide"
|
// "git_gutter": "hide"
|
||||||
"git_gutter": "tracked_files"
|
"git_gutter": "tracked_files"
|
||||||
},
|
},
|
||||||
|
"copilot": {
|
||||||
|
// The set of glob patterns for which copilot should be disabled
|
||||||
|
// in any matching file.
|
||||||
|
"disabled_globs": [
|
||||||
|
".env"
|
||||||
|
]
|
||||||
|
},
|
||||||
// Settings specific to journaling
|
// Settings specific to journaling
|
||||||
"journal": {
|
"journal": {
|
||||||
// The path of the directory where journal entries are stored
|
// The path of the directory where journal entries are stored
|
||||||
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathan@zed.dev>"]
|
|||||||
default-run = "collab"
|
default-run = "collab"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "collab"
|
name = "collab"
|
||||||
version = "0.10.0"
|
version = "0.11.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
|
@ -1306,10 +1306,9 @@ impl View for ContactList {
|
|||||||
"ContactList"
|
"ContactList"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut cx = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
cx.add_identifier("menu");
|
keymap.add_identifier("menu");
|
||||||
cx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
|
@ -140,10 +140,9 @@ impl View for ContextMenu {
|
|||||||
"ContextMenu"
|
"ContextMenu"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut cx = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
cx.add_identifier("menu");
|
keymap.add_identifier("menu");
|
||||||
cx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
|
@ -9,6 +9,7 @@ path = "src/copilot_button.rs"
|
|||||||
doctest = false
|
doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
assets = { path = "../assets" }
|
||||||
copilot = { path = "../copilot" }
|
copilot = { path = "../copilot" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
context_menu = { path = "../context_menu" }
|
context_menu = { path = "../context_menu" }
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
|
use anyhow::Result;
|
||||||
use context_menu::{ContextMenu, ContextMenuItem};
|
use context_menu::{ContextMenu, ContextMenuItem};
|
||||||
use copilot::{Copilot, SignOut, Status};
|
use copilot::{Copilot, SignOut, Status};
|
||||||
use editor::Editor;
|
use editor::{scroll::autoscroll::Autoscroll, Editor};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*,
|
elements::*,
|
||||||
platform::{CursorStyle, MouseButton},
|
platform::{CursorStyle, MouseButton},
|
||||||
AnyElement, AppContext, Element, Entity, MouseState, Subscription, View, ViewContext,
|
AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
|
||||||
ViewHandle, WindowContext,
|
ViewContext, ViewHandle, WeakViewHandle, WindowContext,
|
||||||
};
|
};
|
||||||
use settings::{settings_file::SettingsFile, Settings};
|
use settings::{settings_file::SettingsFile, Settings};
|
||||||
use std::sync::Arc;
|
use std::{path::Path, sync::Arc};
|
||||||
use util::ResultExt;
|
use util::{paths, ResultExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::ItemHandle, notifications::simple_message_notification::OsOpen, StatusItemView, Toast,
|
create_and_open_local_file, item::ItemHandle,
|
||||||
Workspace,
|
notifications::simple_message_notification::OsOpen, StatusItemView, Toast, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
|
const COPILOT_SETTINGS_URL: &str = "https://github.com/settings/copilot";
|
||||||
@ -24,6 +25,7 @@ pub struct CopilotButton {
|
|||||||
editor_subscription: Option<(Subscription, usize)>,
|
editor_subscription: Option<(Subscription, usize)>,
|
||||||
editor_enabled: Option<bool>,
|
editor_enabled: Option<bool>,
|
||||||
language: Option<Arc<str>>,
|
language: Option<Arc<str>>,
|
||||||
|
path: Option<Arc<Path>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for CopilotButton {
|
impl Entity for CopilotButton {
|
||||||
@ -51,7 +53,7 @@ impl View for CopilotButton {
|
|||||||
|
|
||||||
let enabled = self
|
let enabled = self
|
||||||
.editor_enabled
|
.editor_enabled
|
||||||
.unwrap_or(settings.show_copilot_suggestions(None));
|
.unwrap_or(settings.show_copilot_suggestions(None, None));
|
||||||
|
|
||||||
Stack::new()
|
Stack::new()
|
||||||
.with_child(
|
.with_child(
|
||||||
@ -161,6 +163,7 @@ impl CopilotButton {
|
|||||||
editor_subscription: None,
|
editor_subscription: None,
|
||||||
editor_enabled: None,
|
editor_enabled: None,
|
||||||
language: None,
|
language: None,
|
||||||
|
path: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,10 +190,10 @@ impl CopilotButton {
|
|||||||
pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
|
pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
let settings = cx.global::<Settings>();
|
let settings = cx.global::<Settings>();
|
||||||
|
|
||||||
let mut menu_options = Vec::with_capacity(6);
|
let mut menu_options = Vec::with_capacity(8);
|
||||||
|
|
||||||
if let Some(language) = self.language.clone() {
|
if let Some(language) = self.language.clone() {
|
||||||
let language_enabled = settings.show_copilot_suggestions(Some(language.as_ref()));
|
let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref()));
|
||||||
menu_options.push(ContextMenuItem::handler(
|
menu_options.push(ContextMenuItem::handler(
|
||||||
format!(
|
format!(
|
||||||
"{} Suggestions for {}",
|
"{} Suggestions for {}",
|
||||||
@ -201,7 +204,31 @@ impl CopilotButton {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let globally_enabled = cx.global::<Settings>().show_copilot_suggestions(None);
|
if let Some(path) = self.path.as_ref() {
|
||||||
|
let path_enabled = settings.copilot_enabled_for_path(path);
|
||||||
|
let path = path.clone();
|
||||||
|
menu_options.push(ContextMenuItem::handler(
|
||||||
|
format!(
|
||||||
|
"{} Suggestions for This Path",
|
||||||
|
if path_enabled { "Hide" } else { "Show" }
|
||||||
|
),
|
||||||
|
move |cx| {
|
||||||
|
if let Some(workspace) = cx.root_view().clone().downcast::<Workspace>() {
|
||||||
|
let workspace = workspace.downgrade();
|
||||||
|
cx.spawn(|_, cx| {
|
||||||
|
configure_disabled_globs(
|
||||||
|
workspace,
|
||||||
|
path_enabled.then_some(path.clone()),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let globally_enabled = cx.global::<Settings>().features.copilot;
|
||||||
menu_options.push(ContextMenuItem::handler(
|
menu_options.push(ContextMenuItem::handler(
|
||||||
if globally_enabled {
|
if globally_enabled {
|
||||||
"Hide Suggestions for All Files"
|
"Hide Suggestions for All Files"
|
||||||
@ -247,10 +274,14 @@ impl CopilotButton {
|
|||||||
let language_name = snapshot
|
let language_name = snapshot
|
||||||
.language_at(suggestion_anchor)
|
.language_at(suggestion_anchor)
|
||||||
.map(|language| language.name());
|
.map(|language| language.name());
|
||||||
|
let path = snapshot
|
||||||
|
.file_at(suggestion_anchor)
|
||||||
|
.map(|file| file.path().clone());
|
||||||
|
|
||||||
self.language = language_name.clone();
|
self.editor_enabled =
|
||||||
|
Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref()));
|
||||||
self.editor_enabled = Some(settings.show_copilot_suggestions(language_name.as_deref()));
|
self.language = language_name;
|
||||||
|
self.path = path;
|
||||||
|
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
@ -271,8 +302,62 @@ impl StatusItemView for CopilotButton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn configure_disabled_globs(
|
||||||
|
workspace: WeakViewHandle<Workspace>,
|
||||||
|
path_to_disable: Option<Arc<Path>>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
let settings_editor = workspace
|
||||||
|
.update(&mut cx, |_, cx| {
|
||||||
|
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||||
|
Settings::initial_user_settings_content(&assets::Assets)
|
||||||
|
.as_ref()
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.await?
|
||||||
|
.downcast::<Editor>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
settings_editor.downgrade().update(&mut cx, |item, cx| {
|
||||||
|
let text = item.buffer().read(cx).snapshot(cx).text();
|
||||||
|
|
||||||
|
let edits = SettingsFile::update_unsaved(&text, cx, |file| {
|
||||||
|
let copilot = file.copilot.get_or_insert_with(Default::default);
|
||||||
|
let globs = copilot.disabled_globs.get_or_insert_with(|| {
|
||||||
|
cx.global::<Settings>()
|
||||||
|
.copilot
|
||||||
|
.disabled_globs
|
||||||
|
.clone()
|
||||||
|
.iter()
|
||||||
|
.map(|glob| glob.as_str().to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(path_to_disable) = &path_to_disable {
|
||||||
|
globs.push(path_to_disable.to_string_lossy().into_owned());
|
||||||
|
} else {
|
||||||
|
globs.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !edits.is_empty() {
|
||||||
|
item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
|
||||||
|
selections.select_ranges(edits.iter().map(|e| e.0.clone()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// When *enabling* a path, don't actually perform an edit, just select the range.
|
||||||
|
if path_to_disable.is_some() {
|
||||||
|
item.edit(edits.iter().cloned(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
anyhow::Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn toggle_copilot_globally(cx: &mut AppContext) {
|
fn toggle_copilot_globally(cx: &mut AppContext) {
|
||||||
let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None);
|
let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None, None);
|
||||||
SettingsFile::update(cx, move |file_contents| {
|
SettingsFile::update(cx, move |file_contents| {
|
||||||
file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
|
||||||
});
|
});
|
||||||
@ -281,7 +366,7 @@ fn toggle_copilot_globally(cx: &mut AppContext) {
|
|||||||
fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
|
fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
|
||||||
let show_copilot_suggestions = cx
|
let show_copilot_suggestions = cx
|
||||||
.global::<Settings>()
|
.global::<Settings>()
|
||||||
.show_copilot_suggestions(Some(&language));
|
.show_copilot_suggestions(Some(&language), None);
|
||||||
|
|
||||||
SettingsFile::update(cx, move |file_contents| {
|
SettingsFile::update(cx, move |file_contents| {
|
||||||
file_contents.languages.insert(
|
file_contents.languages.insert(
|
||||||
@ -291,13 +376,13 @@ fn toggle_copilot_for_language(language: Arc<str>, cx: &mut AppContext) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_copilot(cx: &mut AppContext) {
|
fn hide_copilot(cx: &mut AppContext) {
|
||||||
SettingsFile::update(cx, move |file_contents| {
|
SettingsFile::update(cx, move |file_contents| {
|
||||||
file_contents.features.copilot = Some(false)
|
file_contents.features.copilot = Some(false)
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initiate_sign_in(cx: &mut WindowContext) {
|
fn initiate_sign_in(cx: &mut WindowContext) {
|
||||||
|
@ -80,6 +80,7 @@ workspace = { path = "../workspace", features = ["test-support"] }
|
|||||||
|
|
||||||
ctor.workspace = true
|
ctor.workspace = true
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
|
glob.workspace = true
|
||||||
rand.workspace = true
|
rand.workspace = true
|
||||||
unindent.workspace = true
|
unindent.workspace = true
|
||||||
tree-sitter = "0.20"
|
tree-sitter = "0.20"
|
||||||
|
@ -52,8 +52,8 @@ use itertools::Itertools;
|
|||||||
pub use language::{char_kind, CharKind};
|
pub use language::{char_kind, CharKind};
|
||||||
use language::{
|
use language::{
|
||||||
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
|
||||||
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
|
Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
|
||||||
Point, Selection, SelectionGoal, TransactionId,
|
OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
|
||||||
};
|
};
|
||||||
use link_go_to_definition::{
|
use link_go_to_definition::{
|
||||||
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
|
||||||
@ -1380,6 +1380,10 @@ impl Editor {
|
|||||||
self.buffer.read(cx).language_at(point, cx)
|
self.buffer.read(cx).language_at(point, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file_at<'a, T: ToOffset>(&self, point: T, cx: &'a AppContext) -> Option<Arc<dyn File>> {
|
||||||
|
self.buffer.read(cx).read(cx).file_at(point).cloned()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn active_excerpt(
|
pub fn active_excerpt(
|
||||||
&self,
|
&self,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
@ -1423,13 +1427,19 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_keymap_context_layer<Tag: 'static>(&mut self, context: KeymapContext) {
|
pub fn set_keymap_context_layer<Tag: 'static>(
|
||||||
|
&mut self,
|
||||||
|
context: KeymapContext,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
self.keymap_context_layers
|
self.keymap_context_layers
|
||||||
.insert(TypeId::of::<Tag>(), context);
|
.insert(TypeId::of::<Tag>(), context);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self) {
|
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||||
@ -2949,11 +2959,7 @@ impl Editor {
|
|||||||
|
|
||||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||||
let cursor = self.selections.newest_anchor().head();
|
let cursor = self.selections.newest_anchor().head();
|
||||||
let language_name = snapshot.language_at(cursor).map(|language| language.name());
|
if !self.is_copilot_enabled_at(cursor, &snapshot, cx) {
|
||||||
if !cx
|
|
||||||
.global::<Settings>()
|
|
||||||
.show_copilot_suggestions(language_name.as_deref())
|
|
||||||
{
|
|
||||||
self.clear_copilot_suggestions(cx);
|
self.clear_copilot_suggestions(cx);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -3104,6 +3110,25 @@ impl Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_copilot_enabled_at(
|
||||||
|
&self,
|
||||||
|
location: Anchor,
|
||||||
|
snapshot: &MultiBufferSnapshot,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> bool {
|
||||||
|
let settings = cx.global::<Settings>();
|
||||||
|
|
||||||
|
let path = snapshot.file_at(location).map(|file| file.path());
|
||||||
|
let language_name = snapshot
|
||||||
|
.language_at(location)
|
||||||
|
.map(|language| language.name());
|
||||||
|
if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
|
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
|
||||||
self.display_map.read(cx).has_suggestion()
|
self.display_map.read(cx).has_suggestion()
|
||||||
}
|
}
|
||||||
@ -6865,6 +6890,9 @@ impl Editor {
|
|||||||
self.language_at(0, cx)
|
self.language_at(0, cx)
|
||||||
.map(|language| language.name())
|
.map(|language| language.name())
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
|
self.file_at(0, cx)
|
||||||
|
.map(|file| file.path().clone())
|
||||||
|
.as_deref(),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
telemetry.report_clickhouse_event(event, settings.telemetry())
|
telemetry.report_clickhouse_event(event, settings.telemetry())
|
||||||
@ -7137,28 +7165,26 @@ impl View for Editor {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut context = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
let mode = match self.mode {
|
let mode = match self.mode {
|
||||||
EditorMode::SingleLine => "single_line",
|
EditorMode::SingleLine => "single_line",
|
||||||
EditorMode::AutoHeight { .. } => "auto_height",
|
EditorMode::AutoHeight { .. } => "auto_height",
|
||||||
EditorMode::Full => "full",
|
EditorMode::Full => "full",
|
||||||
};
|
};
|
||||||
context.add_key("mode", mode);
|
keymap.add_key("mode", mode);
|
||||||
if self.pending_rename.is_some() {
|
if self.pending_rename.is_some() {
|
||||||
context.add_identifier("renaming");
|
keymap.add_identifier("renaming");
|
||||||
}
|
}
|
||||||
match self.context_menu.as_ref() {
|
match self.context_menu.as_ref() {
|
||||||
Some(ContextMenu::Completions(_)) => context.add_identifier("showing_completions"),
|
Some(ContextMenu::Completions(_)) => keymap.add_identifier("showing_completions"),
|
||||||
Some(ContextMenu::CodeActions(_)) => context.add_identifier("showing_code_actions"),
|
Some(ContextMenu::CodeActions(_)) => keymap.add_identifier("showing_code_actions"),
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
for layer in self.keymap_context_layers.values() {
|
for layer in self.keymap_context_layers.values() {
|
||||||
context.extend(layer);
|
keymap.extend(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
|
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
|
||||||
|
@ -6387,6 +6387,97 @@ async fn test_copilot_multibuffer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_copilot_disabled_globs(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx: &mut gpui::TestAppContext,
|
||||||
|
) {
|
||||||
|
let (copilot, copilot_lsp) = Copilot::fake(cx);
|
||||||
|
cx.update(|cx| {
|
||||||
|
let mut settings = Settings::test(cx);
|
||||||
|
settings.copilot.disabled_globs = vec![glob::Pattern::new(".env*").unwrap()];
|
||||||
|
cx.set_global(settings);
|
||||||
|
cx.set_global(copilot)
|
||||||
|
});
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree(
|
||||||
|
"/test",
|
||||||
|
json!({
|
||||||
|
".env": "SECRET=something\n",
|
||||||
|
"README.md": "hello\n"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let project = Project::test(fs, ["/test".as_ref()], cx).await;
|
||||||
|
|
||||||
|
let private_buffer = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_local_buffer("/test/.env", cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let public_buffer = project
|
||||||
|
.update(cx, |project, cx| {
|
||||||
|
project.open_local_buffer("/test/README.md", cx)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let multibuffer = cx.add_model(|cx| {
|
||||||
|
let mut multibuffer = MultiBuffer::new(0);
|
||||||
|
multibuffer.push_excerpts(
|
||||||
|
private_buffer.clone(),
|
||||||
|
[ExcerptRange {
|
||||||
|
context: Point::new(0, 0)..Point::new(1, 0),
|
||||||
|
primary: None,
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
multibuffer.push_excerpts(
|
||||||
|
public_buffer.clone(),
|
||||||
|
[ExcerptRange {
|
||||||
|
context: Point::new(0, 0)..Point::new(1, 0),
|
||||||
|
primary: None,
|
||||||
|
}],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
multibuffer
|
||||||
|
});
|
||||||
|
let (_, editor) = cx.add_window(|cx| build_editor(multibuffer, cx));
|
||||||
|
|
||||||
|
let mut copilot_requests = copilot_lsp
|
||||||
|
.handle_request::<copilot::request::GetCompletions, _, _>(move |_params, _cx| async move {
|
||||||
|
Ok(copilot::request::GetCompletionsResult {
|
||||||
|
completions: vec![copilot::request::Completion {
|
||||||
|
text: "next line".into(),
|
||||||
|
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.change_selections(None, cx, |selections| {
|
||||||
|
selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
|
||||||
|
});
|
||||||
|
editor.next_copilot_suggestion(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
|
assert!(copilot_requests.try_next().is_err());
|
||||||
|
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
|
||||||
|
});
|
||||||
|
editor.next_copilot_suggestion(&Default::default(), cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
|
||||||
|
assert!(copilot_requests.try_next().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
|
||||||
let point = DisplayPoint::new(row as u32, column as u32);
|
let point = DisplayPoint::new(row as u32, column as u32);
|
||||||
point..point
|
point..point
|
||||||
|
@ -10,9 +10,9 @@ use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
|||||||
pub use language::Completion;
|
pub use language::Completion;
|
||||||
use language::{
|
use language::{
|
||||||
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
|
||||||
DiagnosticEntry, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16, Outline,
|
DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
|
||||||
OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _, ToOffsetUtf16 as _,
|
Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
|
||||||
ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -2754,6 +2754,11 @@ impl MultiBufferSnapshot {
|
|||||||
self.trailing_excerpt_update_count
|
self.trailing_excerpt_update_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn file_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<dyn File>> {
|
||||||
|
self.point_to_buffer_offset(point)
|
||||||
|
.and_then(|(buffer, _)| buffer.file())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
|
pub fn language_at<'a, T: ToOffset>(&'a self, point: T) -> Option<&'a Arc<Language>> {
|
||||||
self.point_to_buffer_offset(point)
|
self.point_to_buffer_offset(point)
|
||||||
.and_then(|(buffer, offset)| buffer.language_at(offset))
|
.and_then(|(buffer, offset)| buffer.language_at(offset))
|
||||||
|
@ -11,25 +11,27 @@ path = "src/feedback.rs"
|
|||||||
test-support = []
|
test-support = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
project = { path = "../project" }
|
||||||
|
search = { path = "../search" }
|
||||||
|
settings = { path = "../settings" }
|
||||||
|
theme = { path = "../theme" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
workspace = { path = "../workspace" }
|
||||||
|
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
gpui = { path = "../gpui" }
|
anyhow.workspace = true
|
||||||
|
smallvec.workspace = true
|
||||||
human_bytes = "0.4.1"
|
human_bytes = "0.4.1"
|
||||||
isahc = "1.7"
|
isahc = "1.7"
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
project = { path = "../project" }
|
|
||||||
search = { path = "../search" }
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
settings = { path = "../settings" }
|
|
||||||
sysinfo = "0.27.1"
|
sysinfo = "0.27.1"
|
||||||
theme = { path = "../theme" }
|
|
||||||
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
tree-sitter-markdown = { git = "https://github.com/MDeiml/tree-sitter-markdown", rev = "330ecab87a3e3a7211ac69bbadc19eabecdb1cca" }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
util = { path = "../util" }
|
|
||||||
workspace = { path = "../workspace" }
|
|
||||||
|
@ -15,6 +15,7 @@ use language::Buffer;
|
|||||||
use postage::prelude::Stream;
|
use postage::prelude::Stream;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
@ -25,7 +26,6 @@ use util::ResultExt;
|
|||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemEvent, ItemHandle},
|
item::{Item, ItemEvent, ItemHandle},
|
||||||
searchable::{SearchableItem, SearchableItemHandle},
|
searchable::{SearchableItem, SearchableItemHandle},
|
||||||
smallvec::SmallVec,
|
|
||||||
Workspace,
|
Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,14 +84,15 @@ pub trait View: Entity + Sized {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> keymap_matcher::KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut keymap_matcher::KeymapContext, _: &AppContext) {
|
||||||
Self::default_keymap_context()
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
}
|
}
|
||||||
fn default_keymap_context() -> keymap_matcher::KeymapContext {
|
|
||||||
let mut cx = keymap_matcher::KeymapContext::default();
|
fn reset_to_default_keymap_context(keymap: &mut keymap_matcher::KeymapContext) {
|
||||||
cx.add_identifier(Self::ui_name());
|
keymap.clear();
|
||||||
cx
|
keymap.add_identifier(Self::ui_name());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
|
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
|
||||||
serde_json::Value::Null
|
serde_json::Value::Null
|
||||||
}
|
}
|
||||||
@ -450,6 +451,7 @@ type WindowShouldCloseSubscriptionCallback = Box<dyn FnMut(&mut AppContext) -> b
|
|||||||
pub struct AppContext {
|
pub struct AppContext {
|
||||||
models: HashMap<usize, Box<dyn AnyModel>>,
|
models: HashMap<usize, Box<dyn AnyModel>>,
|
||||||
views: HashMap<(usize, usize), Box<dyn AnyView>>,
|
views: HashMap<(usize, usize), Box<dyn AnyView>>,
|
||||||
|
views_metadata: HashMap<(usize, usize), ViewMetadata>,
|
||||||
windows: HashMap<usize, Window>,
|
windows: HashMap<usize, Window>,
|
||||||
globals: HashMap<TypeId, Box<dyn Any>>,
|
globals: HashMap<TypeId, Box<dyn Any>>,
|
||||||
element_states: HashMap<ElementStateId, Box<dyn Any>>,
|
element_states: HashMap<ElementStateId, Box<dyn Any>>,
|
||||||
@ -511,6 +513,7 @@ impl AppContext {
|
|||||||
Self {
|
Self {
|
||||||
models: Default::default(),
|
models: Default::default(),
|
||||||
views: Default::default(),
|
views: Default::default(),
|
||||||
|
views_metadata: Default::default(),
|
||||||
windows: Default::default(),
|
windows: Default::default(),
|
||||||
globals: Default::default(),
|
globals: Default::default(),
|
||||||
element_states: Default::default(),
|
element_states: Default::default(),
|
||||||
@ -735,9 +738,9 @@ impl AppContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
|
pub fn view_type_id(&self, window_id: usize, view_id: usize) -> Option<TypeId> {
|
||||||
self.views
|
self.views_metadata
|
||||||
.get(&(window_id, view_id))
|
.get(&(window_id, view_id))
|
||||||
.map(|view| view.as_any().type_id())
|
.map(|metadata| metadata.type_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_labeled_tasks<'a>(
|
pub fn active_labeled_tasks<'a>(
|
||||||
@ -1053,9 +1056,10 @@ impl AppContext {
|
|||||||
.read_window(window_id, |cx| {
|
.read_window(window_id, |cx| {
|
||||||
if let Some(focused_view_id) = cx.focused_view_id() {
|
if let Some(focused_view_id) = cx.focused_view_id() {
|
||||||
for view_id in cx.ancestors(focused_view_id) {
|
for view_id in cx.ancestors(focused_view_id) {
|
||||||
if let Some(view) = cx.views.get(&(window_id, view_id)) {
|
if let Some(view_metadata) =
|
||||||
let view_type = view.as_any().type_id();
|
cx.views_metadata.get(&(window_id, view_id))
|
||||||
if let Some(actions) = cx.actions.get(&view_type) {
|
{
|
||||||
|
if let Some(actions) = cx.actions.get(&view_metadata.type_id) {
|
||||||
if actions.contains_key(&action_type) {
|
if actions.contains_key(&action_type) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1444,6 +1448,7 @@ impl AppContext {
|
|||||||
for (window_id, view_id) in dropped_views {
|
for (window_id, view_id) in dropped_views {
|
||||||
self.subscriptions.remove(view_id);
|
self.subscriptions.remove(view_id);
|
||||||
self.observations.remove(view_id);
|
self.observations.remove(view_id);
|
||||||
|
self.views_metadata.remove(&(window_id, view_id));
|
||||||
let mut view = self.views.remove(&(window_id, view_id)).unwrap();
|
let mut view = self.views.remove(&(window_id, view_id)).unwrap();
|
||||||
view.release(self);
|
view.release(self);
|
||||||
let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
|
let change_focus_to = self.windows.get_mut(&window_id).and_then(|window| {
|
||||||
@ -1775,9 +1780,11 @@ impl AppContext {
|
|||||||
observed_window_id: usize,
|
observed_window_id: usize,
|
||||||
observed_view_id: usize,
|
observed_view_id: usize,
|
||||||
) {
|
) {
|
||||||
if self
|
let view_key = (observed_window_id, observed_view_id);
|
||||||
|
if let Some((view, mut view_metadata)) = self
|
||||||
.views
|
.views
|
||||||
.contains_key(&(observed_window_id, observed_view_id))
|
.remove(&view_key)
|
||||||
|
.zip(self.views_metadata.remove(&view_key))
|
||||||
{
|
{
|
||||||
if let Some(window) = self.windows.get_mut(&observed_window_id) {
|
if let Some(window) = self.windows.get_mut(&observed_window_id) {
|
||||||
window
|
window
|
||||||
@ -1787,6 +1794,10 @@ impl AppContext {
|
|||||||
.insert(observed_view_id);
|
.insert(observed_view_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view.update_keymap_context(&mut view_metadata.keymap_context, self);
|
||||||
|
self.views.insert(view_key, view);
|
||||||
|
self.views_metadata.insert(view_key, view_metadata);
|
||||||
|
|
||||||
let mut observations = self.observations.clone();
|
let mut observations = self.observations.clone();
|
||||||
observations.emit(observed_view_id, |callback| callback(self));
|
observations.emit(observed_view_id, |callback| callback(self));
|
||||||
}
|
}
|
||||||
@ -2033,6 +2044,11 @@ pub enum ParentId {
|
|||||||
Root,
|
Root,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ViewMetadata {
|
||||||
|
type_id: TypeId,
|
||||||
|
keymap_context: KeymapContext,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct WindowInvalidation {
|
pub struct WindowInvalidation {
|
||||||
pub updated: HashSet<usize>,
|
pub updated: HashSet<usize>,
|
||||||
@ -2361,7 +2377,7 @@ pub trait AnyView {
|
|||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
view_id: usize,
|
view_id: usize,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
fn keymap_context(&self, cx: &AppContext) -> KeymapContext;
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext);
|
||||||
fn debug_json(&self, cx: &WindowContext) -> serde_json::Value;
|
fn debug_json(&self, cx: &WindowContext) -> serde_json::Value;
|
||||||
|
|
||||||
fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String>;
|
fn text_for_range(&self, range: Range<usize>, cx: &WindowContext) -> Option<String>;
|
||||||
@ -2433,11 +2449,10 @@ where
|
|||||||
cx.handle().into_any()
|
cx.handle().into_any()
|
||||||
} else {
|
} else {
|
||||||
let focused_type = cx
|
let focused_type = cx
|
||||||
.views
|
.views_metadata
|
||||||
.get(&(cx.window_id, focused_id))
|
.get(&(cx.window_id, focused_id))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_any()
|
.type_id;
|
||||||
.type_id();
|
|
||||||
AnyViewHandle::new(
|
AnyViewHandle::new(
|
||||||
cx.window_id,
|
cx.window_id,
|
||||||
focused_id,
|
focused_id,
|
||||||
@ -2454,11 +2469,10 @@ where
|
|||||||
cx.handle().into_any()
|
cx.handle().into_any()
|
||||||
} else {
|
} else {
|
||||||
let blurred_type = cx
|
let blurred_type = cx
|
||||||
.views
|
.views_metadata
|
||||||
.get(&(cx.window_id, blurred_id))
|
.get(&(cx.window_id, blurred_id))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_any()
|
.type_id;
|
||||||
.type_id();
|
|
||||||
AnyViewHandle::new(
|
AnyViewHandle::new(
|
||||||
cx.window_id,
|
cx.window_id,
|
||||||
blurred_id,
|
blurred_id,
|
||||||
@ -2489,8 +2503,8 @@ where
|
|||||||
View::modifiers_changed(self, event, &mut cx)
|
View::modifiers_changed(self, event, &mut cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, cx: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &AppContext) {
|
||||||
View::keymap_context(self, cx)
|
View::update_keymap_context(self, keymap, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_json(&self, cx: &WindowContext) -> serde_json::Value {
|
fn debug_json(&self, cx: &WindowContext) -> serde_json::Value {
|
||||||
@ -3234,7 +3248,6 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||||||
/// Return keystrokes that would dispatch the given action on the given view.
|
/// Return keystrokes that would dispatch the given action on the given view.
|
||||||
pub(crate) fn keystrokes_for_action(
|
pub(crate) fn keystrokes_for_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
view: &V,
|
|
||||||
view_id: usize,
|
view_id: usize,
|
||||||
action: &dyn Action,
|
action: &dyn Action,
|
||||||
) -> Option<SmallVec<[Keystroke; 2]>> {
|
) -> Option<SmallVec<[Keystroke; 2]>> {
|
||||||
@ -3244,21 +3257,13 @@ impl<'a, 'b, 'c, V: View> LayoutContext<'a, 'b, 'c, V> {
|
|||||||
let mut contexts = Vec::new();
|
let mut contexts = Vec::new();
|
||||||
let mut handler_depth = None;
|
let mut handler_depth = None;
|
||||||
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
for (i, view_id) in self.ancestors(view_id).enumerate() {
|
||||||
let view = if view_id == self.view_id {
|
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||||
Some(view as _)
|
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||||
} else {
|
|
||||||
self.views
|
|
||||||
.get(&(window_id, view_id))
|
|
||||||
.map(|view| view.as_ref())
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(view) = view {
|
|
||||||
if let Some(actions) = self.actions.get(&view.as_any().type_id()) {
|
|
||||||
if actions.contains_key(&action.as_any().type_id()) {
|
if actions.contains_key(&action.as_any().type_id()) {
|
||||||
handler_depth = Some(i);
|
handler_depth = Some(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
contexts.push(view.keymap_context(self));
|
contexts.push(view_metadata.keymap_context.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5687,8 +5692,8 @@ mod tests {
|
|||||||
"View"
|
"View"
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
self.keymap_context.clone()
|
*keymap = self.keymap_context.clone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -5852,52 +5857,51 @@ mod tests {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let view_1_id = view_1.id();
|
let view_1_id = view_1.id();
|
||||||
view_1.update(cx, |view_1, cx| {
|
view_1.update(cx, |_, cx| {
|
||||||
// Sanity check
|
view_2.update(cx, |_, cx| {
|
||||||
let mut new_parents = Default::default();
|
// Sanity check
|
||||||
let mut notify_views_if_parents_change = Default::default();
|
let mut new_parents = Default::default();
|
||||||
let mut layout_cx = LayoutContext::new(
|
let mut notify_views_if_parents_change = Default::default();
|
||||||
cx,
|
let mut layout_cx = LayoutContext::new(
|
||||||
&mut new_parents,
|
cx,
|
||||||
&mut notify_views_if_parents_change,
|
&mut new_parents,
|
||||||
false,
|
&mut notify_views_if_parents_change,
|
||||||
);
|
false,
|
||||||
assert_eq!(
|
);
|
||||||
layout_cx
|
assert_eq!(
|
||||||
.keystrokes_for_action(view_1, view_1_id, &Action1)
|
layout_cx
|
||||||
.unwrap()
|
.keystrokes_for_action(view_1_id, &Action1)
|
||||||
.as_slice(),
|
.unwrap()
|
||||||
&[Keystroke::parse("a").unwrap()]
|
.as_slice(),
|
||||||
);
|
&[Keystroke::parse("a").unwrap()]
|
||||||
assert_eq!(
|
);
|
||||||
layout_cx
|
assert_eq!(
|
||||||
.keystrokes_for_action(view_1, view_2.id(), &Action2)
|
layout_cx
|
||||||
.unwrap()
|
.keystrokes_for_action(view_2.id(), &Action2)
|
||||||
.as_slice(),
|
.unwrap()
|
||||||
&[Keystroke::parse("b").unwrap()]
|
.as_slice(),
|
||||||
);
|
&[Keystroke::parse("b").unwrap()]
|
||||||
|
);
|
||||||
|
|
||||||
// The 'a' keystroke propagates up the view tree from view_2
|
// The 'a' keystroke propagates up the view tree from view_2
|
||||||
// to view_1. The action, Action1, is handled by view_1.
|
// to view_1. The action, Action1, is handled by view_1.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout_cx
|
layout_cx
|
||||||
.keystrokes_for_action(view_1, view_2.id(), &Action1)
|
.keystrokes_for_action(view_2.id(), &Action1)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_slice(),
|
.as_slice(),
|
||||||
&[Keystroke::parse("a").unwrap()]
|
&[Keystroke::parse("a").unwrap()]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Actions that are handled below the current view don't have bindings
|
// Actions that are handled below the current view don't have bindings
|
||||||
assert_eq!(
|
assert_eq!(layout_cx.keystrokes_for_action(view_1_id, &Action2), None);
|
||||||
layout_cx.keystrokes_for_action(view_1, view_1_id, &Action2),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
|
|
||||||
// Actions that are handled in other branches of the tree should not have a binding
|
// Actions that are handled in other branches of the tree should not have a binding
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
layout_cx.keystrokes_for_action(view_1, view_2.id(), &GlobalAction),
|
layout_cx.keystrokes_for_action(view_2.id(), &GlobalAction),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check that global actions do not have a binding, even if a binding does exist in another view
|
// Check that global actions do not have a binding, even if a binding does exist in another view
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
elements::AnyRootElement,
|
elements::AnyRootElement,
|
||||||
geometry::rect::RectF,
|
geometry::rect::RectF,
|
||||||
json::ToJson,
|
json::ToJson,
|
||||||
keymap_matcher::{Binding, Keystroke, MatchResult},
|
keymap_matcher::{Binding, KeymapContext, Keystroke, MatchResult},
|
||||||
platform::{
|
platform::{
|
||||||
self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
|
self, Appearance, CursorStyle, Event, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent,
|
||||||
MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
|
MouseButton, MouseMovedEvent, PromptLevel, WindowBounds,
|
||||||
@ -34,7 +34,7 @@ use std::{
|
|||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::Reference;
|
use super::{Reference, ViewMetadata};
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
pub(crate) root_view: Option<AnyViewHandle>,
|
pub(crate) root_view: Option<AnyViewHandle>,
|
||||||
@ -364,10 +364,9 @@ impl<'a> WindowContext<'a> {
|
|||||||
let mut contexts = Vec::new();
|
let mut contexts = Vec::new();
|
||||||
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
|
let mut handler_depths_by_action_type = HashMap::<TypeId, usize>::default();
|
||||||
for (depth, view_id) in self.ancestors(view_id).enumerate() {
|
for (depth, view_id) in self.ancestors(view_id).enumerate() {
|
||||||
if let Some(view) = self.views.get(&(window_id, view_id)) {
|
if let Some(view_metadata) = self.views_metadata.get(&(window_id, view_id)) {
|
||||||
contexts.push(view.keymap_context(self));
|
contexts.push(view_metadata.keymap_context.clone());
|
||||||
let view_type = view.as_any().type_id();
|
if let Some(actions) = self.actions.get(&view_metadata.type_id) {
|
||||||
if let Some(actions) = self.actions.get(&view_type) {
|
|
||||||
handler_depths_by_action_type.extend(
|
handler_depths_by_action_type.extend(
|
||||||
actions
|
actions
|
||||||
.keys()
|
.keys()
|
||||||
@ -418,9 +417,9 @@ impl<'a> WindowContext<'a> {
|
|||||||
let dispatch_path = self
|
let dispatch_path = self
|
||||||
.ancestors(focused_view_id)
|
.ancestors(focused_view_id)
|
||||||
.filter_map(|view_id| {
|
.filter_map(|view_id| {
|
||||||
self.views
|
self.views_metadata
|
||||||
.get(&(window_id, view_id))
|
.get(&(window_id, view_id))
|
||||||
.map(|view| (view_id, view.keymap_context(self)))
|
.map(|view| (view_id, view.keymap_context.clone()))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -1149,6 +1148,15 @@ impl<'a> WindowContext<'a> {
|
|||||||
let view_id = post_inc(&mut self.next_entity_id);
|
let view_id = post_inc(&mut self.next_entity_id);
|
||||||
let mut cx = ViewContext::mutable(self, view_id);
|
let mut cx = ViewContext::mutable(self, view_id);
|
||||||
let handle = if let Some(view) = build_view(&mut cx) {
|
let handle = if let Some(view) = build_view(&mut cx) {
|
||||||
|
let mut keymap_context = KeymapContext::default();
|
||||||
|
view.update_keymap_context(&mut keymap_context, cx.app_context());
|
||||||
|
self.views_metadata.insert(
|
||||||
|
(window_id, view_id),
|
||||||
|
ViewMetadata {
|
||||||
|
type_id: TypeId::of::<T>(),
|
||||||
|
keymap_context,
|
||||||
|
},
|
||||||
|
);
|
||||||
self.views.insert((window_id, view_id), Box::new(view));
|
self.views.insert((window_id, view_id), Box::new(view));
|
||||||
self.window
|
self.window
|
||||||
.invalidation
|
.invalidation
|
||||||
|
@ -42,7 +42,7 @@ impl<V: View> Element<V> for KeystrokeLabel {
|
|||||||
cx: &mut LayoutContext<V>,
|
cx: &mut LayoutContext<V>,
|
||||||
) -> (Vector2F, AnyElement<V>) {
|
) -> (Vector2F, AnyElement<V>) {
|
||||||
let mut element = if let Some(keystrokes) =
|
let mut element = if let Some(keystrokes) =
|
||||||
cx.keystrokes_for_action(view, self.view_id, self.action.as_ref())
|
cx.keystrokes_for_action(self.view_id, self.action.as_ref())
|
||||||
{
|
{
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_children(keystrokes.iter().map(|keystroke| {
|
.with_children(keystrokes.iter().map(|keystroke| {
|
||||||
|
@ -17,6 +17,11 @@ impl KeymapContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.set.clear();
|
||||||
|
self.map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
pub fn extend(&mut self, other: &Self) {
|
pub fn extend(&mut self, other: &Self) {
|
||||||
for v in &other.set {
|
for v in &other.set {
|
||||||
self.set.insert(v.clone());
|
self.set.insert(v.clone());
|
||||||
|
@ -126,10 +126,9 @@ impl<D: PickerDelegate> View for Picker<D> {
|
|||||||
.into_any_named("picker")
|
.into_any_named("picker")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut cx = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
cx.add_identifier("menu");
|
keymap.add_identifier("menu");
|
||||||
cx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -28,7 +28,6 @@ fs = { path = "../fs" }
|
|||||||
fsevent = { path = "../fsevent" }
|
fsevent = { path = "../fsevent" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
git = { path = "../git" }
|
git = { path = "../git" }
|
||||||
glob = { version = "0.3.1" }
|
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
lsp = { path = "../lsp" }
|
lsp = { path = "../lsp" }
|
||||||
@ -43,6 +42,7 @@ anyhow.workspace = true
|
|||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
|
glob.workspace = true
|
||||||
ignore = "0.4"
|
ignore = "0.4"
|
||||||
lazy_static.workspace = true
|
lazy_static.workspace = true
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
@ -1317,10 +1317,9 @@ impl View for ProjectPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut cx = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
cx.add_identifier("menu");
|
keymap.add_identifier("menu");
|
||||||
cx
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,9 @@ theme = { path = "../theme" }
|
|||||||
staff_mode = { path = "../staff_mode" }
|
staff_mode = { path = "../staff_mode" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
|
|
||||||
|
glob.workspace = true
|
||||||
json_comments = "0.2"
|
json_comments = "0.2"
|
||||||
|
lazy_static.workspace = true
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
schemars = "0.8"
|
schemars = "0.8"
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
@ -7,6 +7,7 @@ use gpui::{
|
|||||||
font_cache::{FamilyId, FontCache},
|
font_cache::{FamilyId, FontCache},
|
||||||
fonts, AssetSource,
|
fonts, AssetSource,
|
||||||
};
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use schemars::{
|
use schemars::{
|
||||||
gen::{SchemaGenerator, SchemaSettings},
|
gen::{SchemaGenerator, SchemaSettings},
|
||||||
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
|
schema::{InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec},
|
||||||
@ -18,14 +19,19 @@ use sqlez::{
|
|||||||
bindable::{Bind, Column, StaticColumnCount},
|
bindable::{Bind, Column, StaticColumnCount},
|
||||||
statement::Statement,
|
statement::Statement,
|
||||||
};
|
};
|
||||||
use std::{collections::HashMap, num::NonZeroU32, str, sync::Arc};
|
use std::{
|
||||||
|
borrow::Cow, collections::HashMap, num::NonZeroU32, ops::Range, path::Path, str, sync::Arc,
|
||||||
|
};
|
||||||
use theme::{Theme, ThemeRegistry};
|
use theme::{Theme, ThemeRegistry};
|
||||||
use tree_sitter::Query;
|
use tree_sitter::{Query, Tree};
|
||||||
use util::{RangeExt, ResultExt as _};
|
use util::{RangeExt, ResultExt as _};
|
||||||
|
|
||||||
pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
|
pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
|
||||||
pub use watched_json::watch_files;
|
pub use watched_json::watch_files;
|
||||||
|
|
||||||
|
pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
|
||||||
|
pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub features: Features,
|
pub features: Features,
|
||||||
@ -47,6 +53,7 @@ pub struct Settings {
|
|||||||
pub editor_overrides: EditorSettings,
|
pub editor_overrides: EditorSettings,
|
||||||
pub git: GitSettings,
|
pub git: GitSettings,
|
||||||
pub git_overrides: GitSettings,
|
pub git_overrides: GitSettings,
|
||||||
|
pub copilot: CopilotSettings,
|
||||||
pub journal_defaults: JournalSettings,
|
pub journal_defaults: JournalSettings,
|
||||||
pub journal_overrides: JournalSettings,
|
pub journal_overrides: JournalSettings,
|
||||||
pub terminal_defaults: TerminalSettings,
|
pub terminal_defaults: TerminalSettings,
|
||||||
@ -61,29 +68,6 @@ pub struct Settings {
|
|||||||
pub base_keymap: BaseKeymap,
|
pub base_keymap: BaseKeymap,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum CopilotSettings {
|
|
||||||
#[default]
|
|
||||||
On,
|
|
||||||
Off,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CopilotSettings> for bool {
|
|
||||||
fn from(value: CopilotSettings) -> Self {
|
|
||||||
match value {
|
|
||||||
CopilotSettings::On => true,
|
|
||||||
CopilotSettings::Off => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CopilotSettings {
|
|
||||||
pub fn is_on(&self) -> bool {
|
|
||||||
<CopilotSettings as Into<bool>>::into(*self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
||||||
pub enum BaseKeymap {
|
pub enum BaseKeymap {
|
||||||
#[default]
|
#[default]
|
||||||
@ -150,6 +134,17 @@ impl TelemetrySettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CopilotSettings {
|
||||||
|
pub disabled_globs: Vec<glob::Pattern>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct CopilotSettingsContent {
|
||||||
|
#[serde(default)]
|
||||||
|
pub disabled_globs: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct GitSettings {
|
pub struct GitSettings {
|
||||||
pub git_gutter: Option<GitGutter>,
|
pub git_gutter: Option<GitGutter>,
|
||||||
@ -390,6 +385,8 @@ pub struct SettingsFileContent {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub buffer_font_features: Option<fonts::Features>,
|
pub buffer_font_features: Option<fonts::Features>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub copilot: Option<CopilotSettingsContent>,
|
||||||
|
#[serde(default)]
|
||||||
pub active_pane_magnification: Option<f32>,
|
pub active_pane_magnification: Option<f32>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub cursor_blink: Option<bool>,
|
pub cursor_blink: Option<bool>,
|
||||||
@ -438,8 +435,7 @@ pub struct LspSettings {
|
|||||||
pub initialization_options: Option<Value>,
|
pub initialization_options: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub struct Features {
|
pub struct Features {
|
||||||
pub copilot: bool,
|
pub copilot: bool,
|
||||||
}
|
}
|
||||||
@ -451,6 +447,13 @@ pub struct FeaturesContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Settings {
|
impl Settings {
|
||||||
|
pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
|
||||||
|
match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
|
||||||
|
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
|
||||||
|
Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Fill out the settings corresponding to the default.json file, overrides will be set later
|
/// Fill out the settings corresponding to the default.json file, overrides will be set later
|
||||||
pub fn defaults(
|
pub fn defaults(
|
||||||
assets: impl AssetSource,
|
assets: impl AssetSource,
|
||||||
@ -464,7 +467,7 @@ impl Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let defaults: SettingsFileContent = parse_json_with_comments(
|
let defaults: SettingsFileContent = parse_json_with_comments(
|
||||||
str::from_utf8(assets.load("settings/default.json").unwrap().as_ref()).unwrap(),
|
str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@ -506,6 +509,16 @@ impl Settings {
|
|||||||
show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
|
show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
|
||||||
},
|
},
|
||||||
editor_overrides: Default::default(),
|
editor_overrides: Default::default(),
|
||||||
|
copilot: CopilotSettings {
|
||||||
|
disabled_globs: defaults
|
||||||
|
.copilot
|
||||||
|
.unwrap()
|
||||||
|
.disabled_globs
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|s| glob::Pattern::new(&s).unwrap())
|
||||||
|
.collect(),
|
||||||
|
},
|
||||||
git: defaults.git.unwrap(),
|
git: defaults.git.unwrap(),
|
||||||
git_overrides: Default::default(),
|
git_overrides: Default::default(),
|
||||||
journal_defaults: defaults.journal,
|
journal_defaults: defaults.journal,
|
||||||
@ -576,6 +589,14 @@ impl Settings {
|
|||||||
merge(&mut self.base_keymap, data.base_keymap);
|
merge(&mut self.base_keymap, data.base_keymap);
|
||||||
merge(&mut self.features.copilot, data.features.copilot);
|
merge(&mut self.features.copilot, data.features.copilot);
|
||||||
|
|
||||||
|
if let Some(copilot) = data.copilot {
|
||||||
|
if let Some(disabled_globs) = copilot.disabled_globs {
|
||||||
|
self.copilot.disabled_globs = disabled_globs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|s| glob::Pattern::new(&s).ok())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
self.editor_overrides = data.editor;
|
self.editor_overrides = data.editor;
|
||||||
self.git_overrides = data.git.unwrap_or_default();
|
self.git_overrides = data.git.unwrap_or_default();
|
||||||
self.journal_overrides = data.journal;
|
self.journal_overrides = data.journal;
|
||||||
@ -602,11 +623,34 @@ impl Settings {
|
|||||||
&self.features
|
&self.features
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_copilot_suggestions(&self, language: Option<&str>) -> bool {
|
pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool {
|
||||||
self.features.copilot
|
if !self.features.copilot {
|
||||||
&& self.language_setting(language, |settings| {
|
return false;
|
||||||
settings.show_copilot_suggestions.map(Into::into)
|
}
|
||||||
})
|
|
||||||
|
if !self.copilot_enabled_for_language(language) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(path) = path {
|
||||||
|
if !self.copilot_enabled_for_path(path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
|
||||||
|
!self
|
||||||
|
.copilot
|
||||||
|
.disabled_globs
|
||||||
|
.iter()
|
||||||
|
.any(|glob| glob.matches_path(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool {
|
||||||
|
self.language_setting(language, |settings| settings.show_copilot_suggestions)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
|
pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
|
||||||
@ -751,6 +795,7 @@ impl Settings {
|
|||||||
show_copilot_suggestions: Some(true),
|
show_copilot_suggestions: Some(true),
|
||||||
},
|
},
|
||||||
editor_overrides: Default::default(),
|
editor_overrides: Default::default(),
|
||||||
|
copilot: Default::default(),
|
||||||
journal_defaults: Default::default(),
|
journal_defaults: Default::default(),
|
||||||
journal_overrides: Default::default(),
|
journal_overrides: Default::default(),
|
||||||
terminal_defaults: Default::default(),
|
terminal_defaults: Default::default(),
|
||||||
@ -859,17 +904,8 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_value: &Value) {
|
lazy_static! {
|
||||||
const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
|
static ref PAIR_QUERY: Query = Query::new(
|
||||||
const LANGAUGES: &'static str = "languages";
|
|
||||||
|
|
||||||
let mut parser = tree_sitter::Parser::new();
|
|
||||||
parser.set_language(tree_sitter_json::language()).unwrap();
|
|
||||||
let tree = parser.parse(&settings_content, None).unwrap();
|
|
||||||
|
|
||||||
let mut cursor = tree_sitter::QueryCursor::new();
|
|
||||||
|
|
||||||
let query = Query::new(
|
|
||||||
tree_sitter_json::language(),
|
tree_sitter_json::language(),
|
||||||
"
|
"
|
||||||
(pair
|
(pair
|
||||||
@ -878,14 +914,65 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
",
|
",
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let has_language_overrides = settings_content.contains(LANGUAGE_OVERRIDES);
|
fn update_object_in_settings_file<'a>(
|
||||||
|
old_object: &'a serde_json::Map<String, Value>,
|
||||||
|
new_object: &'a serde_json::Map<String, Value>,
|
||||||
|
text: &str,
|
||||||
|
syntax_tree: &Tree,
|
||||||
|
tab_size: usize,
|
||||||
|
key_path: &mut Vec<&'a str>,
|
||||||
|
edits: &mut Vec<(Range<usize>, String)>,
|
||||||
|
) {
|
||||||
|
for (key, old_value) in old_object.iter() {
|
||||||
|
key_path.push(key);
|
||||||
|
let new_value = new_object.get(key).unwrap_or(&Value::Null);
|
||||||
|
|
||||||
|
// If the old and new values are both objects, then compare them key by key,
|
||||||
|
// preserving the comments and formatting of the unchanged parts. Otherwise,
|
||||||
|
// replace the old value with the new value.
|
||||||
|
if let (Value::Object(old_sub_object), Value::Object(new_sub_object)) =
|
||||||
|
(old_value, new_value)
|
||||||
|
{
|
||||||
|
update_object_in_settings_file(
|
||||||
|
old_sub_object,
|
||||||
|
new_sub_object,
|
||||||
|
text,
|
||||||
|
syntax_tree,
|
||||||
|
tab_size,
|
||||||
|
key_path,
|
||||||
|
edits,
|
||||||
|
)
|
||||||
|
} else if old_value != new_value {
|
||||||
|
let (range, replacement) =
|
||||||
|
update_key_in_settings_file(text, syntax_tree, &key_path, tab_size, &new_value);
|
||||||
|
edits.push((range, replacement));
|
||||||
|
}
|
||||||
|
|
||||||
|
key_path.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_key_in_settings_file(
|
||||||
|
text: &str,
|
||||||
|
syntax_tree: &Tree,
|
||||||
|
key_path: &[&str],
|
||||||
|
tab_size: usize,
|
||||||
|
new_value: impl Serialize,
|
||||||
|
) -> (Range<usize>, String) {
|
||||||
|
const LANGUAGE_OVERRIDES: &'static str = "language_overrides";
|
||||||
|
const LANGUAGES: &'static str = "languages";
|
||||||
|
|
||||||
|
let mut cursor = tree_sitter::QueryCursor::new();
|
||||||
|
|
||||||
|
let has_language_overrides = text.contains(LANGUAGE_OVERRIDES);
|
||||||
|
|
||||||
let mut depth = 0;
|
let mut depth = 0;
|
||||||
let mut last_value_range = 0..0;
|
let mut last_value_range = 0..0;
|
||||||
let mut first_key_start = None;
|
let mut first_key_start = None;
|
||||||
let mut existing_value_range = 0..settings_content.len();
|
let mut existing_value_range = 0..text.len();
|
||||||
let matches = cursor.matches(&query, tree.root_node(), settings_content.as_bytes());
|
let matches = cursor.matches(&PAIR_QUERY, syntax_tree.root_node(), text.as_bytes());
|
||||||
for mat in matches {
|
for mat in matches {
|
||||||
if mat.captures.len() != 2 {
|
if mat.captures.len() != 2 {
|
||||||
continue;
|
continue;
|
||||||
@ -908,10 +995,10 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
|
|
||||||
first_key_start.get_or_insert_with(|| key_range.start);
|
first_key_start.get_or_insert_with(|| key_range.start);
|
||||||
|
|
||||||
let found_key = settings_content
|
let found_key = text
|
||||||
.get(key_range.clone())
|
.get(key_range.clone())
|
||||||
.map(|key_text| {
|
.map(|key_text| {
|
||||||
if key_path[depth] == LANGAUGES && has_language_overrides {
|
if key_path[depth] == LANGUAGES && has_language_overrides {
|
||||||
return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
|
return key_text == format!("\"{}\"", LANGUAGE_OVERRIDES);
|
||||||
} else {
|
} else {
|
||||||
return key_text == format!("\"{}\"", key_path[depth]);
|
return key_text == format!("\"{}\"", key_path[depth]);
|
||||||
@ -935,12 +1022,11 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
|
|
||||||
// We found the exact key we want, insert the new value
|
// We found the exact key we want, insert the new value
|
||||||
if depth == key_path.len() {
|
if depth == key_path.len() {
|
||||||
let new_val = serde_json::to_string_pretty(new_value)
|
let new_val = to_pretty_json(&new_value, tab_size, tab_size * depth);
|
||||||
.expect("Could not serialize new json field to string");
|
(existing_value_range, new_val)
|
||||||
settings_content.replace_range(existing_value_range, &new_val);
|
|
||||||
} else {
|
} else {
|
||||||
// We have key paths, construct the sub objects
|
// We have key paths, construct the sub objects
|
||||||
let new_key = if has_language_overrides && key_path[depth] == LANGAUGES {
|
let new_key = if has_language_overrides && key_path[depth] == LANGUAGES {
|
||||||
LANGUAGE_OVERRIDES
|
LANGUAGE_OVERRIDES
|
||||||
} else {
|
} else {
|
||||||
key_path[depth]
|
key_path[depth]
|
||||||
@ -949,7 +1035,7 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
// We don't have the key, construct the nested objects
|
// We don't have the key, construct the nested objects
|
||||||
let mut new_value = serde_json::to_value(new_value).unwrap();
|
let mut new_value = serde_json::to_value(new_value).unwrap();
|
||||||
for key in key_path[(depth + 1)..].iter().rev() {
|
for key in key_path[(depth + 1)..].iter().rev() {
|
||||||
if has_language_overrides && key == &LANGAUGES {
|
if has_language_overrides && key == &LANGUAGES {
|
||||||
new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
|
new_value = serde_json::json!({ LANGUAGE_OVERRIDES.to_string(): new_value });
|
||||||
} else {
|
} else {
|
||||||
new_value = serde_json::json!({ key.to_string(): new_value });
|
new_value = serde_json::json!({ key.to_string(): new_value });
|
||||||
@ -959,7 +1045,7 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
if let Some(first_key_start) = first_key_start {
|
if let Some(first_key_start) = first_key_start {
|
||||||
let mut row = 0;
|
let mut row = 0;
|
||||||
let mut column = 0;
|
let mut column = 0;
|
||||||
for (ix, char) in settings_content.char_indices() {
|
for (ix, char) in text.char_indices() {
|
||||||
if ix == first_key_start {
|
if ix == first_key_start {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -974,37 +1060,29 @@ fn write_settings_key(settings_content: &mut String, key_path: &[&str], new_valu
|
|||||||
if row > 0 {
|
if row > 0 {
|
||||||
// depth is 0 based, but division needs to be 1 based.
|
// depth is 0 based, but division needs to be 1 based.
|
||||||
let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
|
let new_val = to_pretty_json(&new_value, column / (depth + 1), column);
|
||||||
let content = format!(r#""{new_key}": {new_val},"#);
|
let space = ' ';
|
||||||
settings_content.insert_str(first_key_start, &content);
|
let content = format!("\"{new_key}\": {new_val},\n{space:width$}", width = column);
|
||||||
|
(first_key_start..first_key_start, content)
|
||||||
settings_content.insert_str(
|
|
||||||
first_key_start + content.len(),
|
|
||||||
&format!("\n{:width$}", ' ', width = column),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
let new_val = serde_json::to_string(&new_value).unwrap();
|
let new_val = serde_json::to_string(&new_value).unwrap();
|
||||||
let mut content = format!(r#""{new_key}": {new_val},"#);
|
let mut content = format!(r#""{new_key}": {new_val},"#);
|
||||||
content.push(' ');
|
content.push(' ');
|
||||||
settings_content.insert_str(first_key_start, &content);
|
(first_key_start..first_key_start, content)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
new_value = serde_json::json!({ new_key.to_string(): new_value });
|
new_value = serde_json::json!({ new_key.to_string(): new_value });
|
||||||
let indent_prefix_len = 4 * depth;
|
let indent_prefix_len = 4 * depth;
|
||||||
let new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
|
let mut new_val = to_pretty_json(&new_value, 4, indent_prefix_len);
|
||||||
|
|
||||||
settings_content.replace_range(existing_value_range, &new_val);
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
settings_content.push('\n');
|
new_val.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(existing_value_range, new_val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_pretty_json(
|
fn to_pretty_json(value: &impl Serialize, indent_size: usize, indent_prefix_len: usize) -> String {
|
||||||
value: &serde_json::Value,
|
|
||||||
indent_size: usize,
|
|
||||||
indent_prefix_len: usize,
|
|
||||||
) -> String {
|
|
||||||
const SPACES: [u8; 32] = [b' '; 32];
|
const SPACES: [u8; 32] = [b' '; 32];
|
||||||
|
|
||||||
debug_assert!(indent_size <= SPACES.len());
|
debug_assert!(indent_size <= SPACES.len());
|
||||||
@ -1031,13 +1109,16 @@ fn to_pretty_json(
|
|||||||
adjusted_text
|
adjusted_text
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_settings_file(
|
/// Update the settings file with the given callback.
|
||||||
mut text: String,
|
///
|
||||||
|
/// Returns a new JSON string and the offset where the first edit occurred.
|
||||||
|
fn update_settings_file(
|
||||||
|
text: &str,
|
||||||
mut old_file_content: SettingsFileContent,
|
mut old_file_content: SettingsFileContent,
|
||||||
|
tab_size: NonZeroU32,
|
||||||
update: impl FnOnce(&mut SettingsFileContent),
|
update: impl FnOnce(&mut SettingsFileContent),
|
||||||
) -> String {
|
) -> Vec<(Range<usize>, String)> {
|
||||||
let mut new_file_content = old_file_content.clone();
|
let mut new_file_content = old_file_content.clone();
|
||||||
|
|
||||||
update(&mut new_file_content);
|
update(&mut new_file_content);
|
||||||
|
|
||||||
if new_file_content.languages.len() != old_file_content.languages.len() {
|
if new_file_content.languages.len() != old_file_content.languages.len() {
|
||||||
@ -1055,51 +1136,25 @@ pub fn update_settings_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut parser = tree_sitter::Parser::new();
|
||||||
|
parser.set_language(tree_sitter_json::language()).unwrap();
|
||||||
|
let tree = parser.parse(text, None).unwrap();
|
||||||
|
|
||||||
let old_object = to_json_object(old_file_content);
|
let old_object = to_json_object(old_file_content);
|
||||||
let new_object = to_json_object(new_file_content);
|
let new_object = to_json_object(new_file_content);
|
||||||
|
let mut key_path = Vec::new();
|
||||||
fn apply_changes_to_json_text(
|
let mut edits = Vec::new();
|
||||||
old_object: &serde_json::Map<String, Value>,
|
update_object_in_settings_file(
|
||||||
new_object: &serde_json::Map<String, Value>,
|
&old_object,
|
||||||
current_key_path: Vec<&str>,
|
&new_object,
|
||||||
json_text: &mut String,
|
&text,
|
||||||
) {
|
&tree,
|
||||||
for (key, old_value) in old_object.iter() {
|
tab_size.get() as usize,
|
||||||
// We know that these two are from the same shape of object, so we can just unwrap
|
&mut key_path,
|
||||||
let new_value = new_object.get(key).unwrap();
|
&mut edits,
|
||||||
|
);
|
||||||
if old_value != new_value {
|
edits.sort_unstable_by_key(|e| e.0.start);
|
||||||
match new_value {
|
return edits;
|
||||||
Value::Bool(_) | Value::Number(_) | Value::String(_) => {
|
|
||||||
let mut key_path = current_key_path.clone();
|
|
||||||
key_path.push(key);
|
|
||||||
write_settings_key(json_text, &key_path, &new_value);
|
|
||||||
}
|
|
||||||
Value::Object(new_sub_object) => {
|
|
||||||
let mut key_path = current_key_path.clone();
|
|
||||||
key_path.push(key);
|
|
||||||
if let Value::Object(old_sub_object) = old_value {
|
|
||||||
apply_changes_to_json_text(
|
|
||||||
old_sub_object,
|
|
||||||
new_sub_object,
|
|
||||||
key_path,
|
|
||||||
json_text,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
unimplemented!("This function doesn't support changing values from simple values to objects yet");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Value::Null | Value::Array(_) => {
|
|
||||||
unimplemented!("We only support objects and simple values");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
apply_changes_to_json_text(&old_object, &new_object, vec![], &mut text);
|
|
||||||
|
|
||||||
text
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
|
fn to_json_object(settings_file: SettingsFileContent) -> serde_json::Map<String, Value> {
|
||||||
@ -1115,15 +1170,18 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use unindent::Unindent;
|
use unindent::Unindent;
|
||||||
|
|
||||||
fn assert_new_settings<S1: Into<String>, S2: Into<String>>(
|
fn assert_new_settings(
|
||||||
old_json: S1,
|
old_json: String,
|
||||||
update: fn(&mut SettingsFileContent),
|
update: fn(&mut SettingsFileContent),
|
||||||
expected_new_json: S2,
|
expected_new_json: String,
|
||||||
) {
|
) {
|
||||||
let old_json = old_json.into();
|
|
||||||
let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
|
let old_content: SettingsFileContent = serde_json::from_str(&old_json).unwrap_or_default();
|
||||||
let new_json = update_settings_file(old_json, old_content, update);
|
let edits = update_settings_file(&old_json, old_content, 4.try_into().unwrap(), update);
|
||||||
pretty_assertions::assert_eq!(new_json, expected_new_json.into());
|
let mut new_json = old_json;
|
||||||
|
for (range, replacement) in edits.into_iter().rev() {
|
||||||
|
new_json.replace_range(range, &replacement);
|
||||||
|
}
|
||||||
|
pretty_assertions::assert_eq!(new_json, expected_new_json);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1164,6 +1222,63 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_update_copilot_globs() {
|
||||||
|
assert_new_settings(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
|settings| {
|
||||||
|
settings.copilot = Some(CopilotSettingsContent {
|
||||||
|
disabled_globs: Some(vec![]),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"copilot": {
|
||||||
|
"disabled_globs": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_new_settings(
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"copilot": {
|
||||||
|
"disabled_globs": [
|
||||||
|
"**/*.json"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
|settings| {
|
||||||
|
settings
|
||||||
|
.copilot
|
||||||
|
.get_or_insert(Default::default())
|
||||||
|
.disabled_globs
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.push(".env".into());
|
||||||
|
},
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"copilot": {
|
||||||
|
"disabled_globs": [
|
||||||
|
"**/*.json",
|
||||||
|
".env"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_update_copilot() {
|
fn test_update_copilot() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
@ -1347,7 +1462,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_telemetry_setting() {
|
fn test_update_telemetry_setting() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
"{}",
|
"{}".into(),
|
||||||
|settings| settings.telemetry.set_diagnostics(true),
|
|settings| settings.telemetry.set_diagnostics(true),
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
@ -1363,7 +1478,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_update_object_empty_doc() {
|
fn test_update_object_empty_doc() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
"",
|
"".into(),
|
||||||
|settings| settings.telemetry.set_diagnostics(true),
|
|settings| settings.telemetry.set_diagnostics(true),
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
@ -1416,7 +1531,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn write_key_no_document() {
|
fn write_key_no_document() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
"",
|
"".to_string(),
|
||||||
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
||||||
r#"
|
r#"
|
||||||
{
|
{
|
||||||
@ -1430,16 +1545,16 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_write_theme_into_single_line_settings_without_theme() {
|
fn test_write_theme_into_single_line_settings_without_theme() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
r#"{ "a": "", "ok": true }"#,
|
r#"{ "a": "", "ok": true }"#.to_string(),
|
||||||
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
||||||
r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#,
|
r#"{ "theme": "summerfruit-light", "a": "", "ok": true }"#.to_string(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_write_theme_pre_object_whitespace() {
|
fn test_write_theme_pre_object_whitespace() {
|
||||||
assert_new_settings(
|
assert_new_settings(
|
||||||
r#" { "a": "", "ok": true }"#,
|
r#" { "a": "", "ok": true }"#.to_string(),
|
||||||
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
|settings| settings.theme = Some("summerfruit-light".to_string()),
|
||||||
r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
|
r#" { "theme": "summerfruit-light", "a": "", "ok": true }"#.unindent(),
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::{update_settings_file, watched_json::WatchedJsonFile, SettingsFileContent};
|
use crate::{update_settings_file, watched_json::WatchedJsonFile, Settings, SettingsFileContent};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{AppContext, AssetSource};
|
use gpui::AppContext;
|
||||||
use std::{io::ErrorKind, path::Path, sync::Arc};
|
use std::{io::ErrorKind, ops::Range, path::Path, sync::Arc};
|
||||||
|
|
||||||
// TODO: Switch SettingsFile to open a worktree and buffer for synchronization
|
// TODO: Switch SettingsFile to open a worktree and buffer for synchronization
|
||||||
// And instant updates in the Zed editor
|
// And instant updates in the Zed editor
|
||||||
@ -33,14 +33,7 @@ impl SettingsFile {
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Some(e) = err.downcast_ref::<std::io::Error>() {
|
if let Some(e) = err.downcast_ref::<std::io::Error>() {
|
||||||
if e.kind() == ErrorKind::NotFound {
|
if e.kind() == ErrorKind::NotFound {
|
||||||
return Ok(std::str::from_utf8(
|
return Ok(Settings::initial_user_settings_content(&Assets).to_string());
|
||||||
Assets
|
|
||||||
.load("settings/initial_user_settings.json")
|
|
||||||
.unwrap()
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.to_string());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@ -48,28 +41,39 @@ impl SettingsFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_unsaved(
|
||||||
|
text: &str,
|
||||||
|
cx: &AppContext,
|
||||||
|
update: impl FnOnce(&mut SettingsFileContent),
|
||||||
|
) -> Vec<(Range<usize>, String)> {
|
||||||
|
let this = cx.global::<SettingsFile>();
|
||||||
|
let tab_size = cx.global::<Settings>().tab_size(Some("JSON"));
|
||||||
|
let current_file_content = this.settings_file_content.current();
|
||||||
|
update_settings_file(&text, current_file_content, tab_size, update)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(
|
pub fn update(
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
update: impl 'static + Send + FnOnce(&mut SettingsFileContent),
|
update: impl 'static + Send + FnOnce(&mut SettingsFileContent),
|
||||||
) {
|
) {
|
||||||
let this = cx.global::<SettingsFile>();
|
let this = cx.global::<SettingsFile>();
|
||||||
|
let tab_size = cx.global::<Settings>().tab_size(Some("JSON"));
|
||||||
let current_file_content = this.settings_file_content.current();
|
let current_file_content = this.settings_file_content.current();
|
||||||
|
|
||||||
let fs = this.fs.clone();
|
let fs = this.fs.clone();
|
||||||
let path = this.path.clone();
|
let path = this.path.clone();
|
||||||
|
|
||||||
cx.background()
|
cx.background()
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
let old_text = SettingsFile::load_settings(path, &fs).await?;
|
let old_text = SettingsFile::load_settings(path, &fs).await?;
|
||||||
|
let edits = update_settings_file(&old_text, current_file_content, tab_size, update);
|
||||||
let new_text = update_settings_file(old_text, current_file_content, update);
|
let mut new_text = old_text;
|
||||||
|
for (range, replacement) in edits.into_iter().rev() {
|
||||||
|
new_text.replace_range(range, &replacement);
|
||||||
|
}
|
||||||
fs.atomic_write(path.to_path_buf(), new_text).await?;
|
fs.atomic_write(path.to_path_buf(), new_text).await?;
|
||||||
|
anyhow::Ok(())
|
||||||
Ok(()) as Result<()>
|
|
||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,11 +446,11 @@ impl View for TerminalView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, cx: &gpui::AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, cx: &gpui::AppContext) {
|
||||||
let mut context = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
|
|
||||||
let mode = self.terminal.read(cx).last_content.mode;
|
let mode = self.terminal.read(cx).last_content.mode;
|
||||||
context.add_key(
|
keymap.add_key(
|
||||||
"screen",
|
"screen",
|
||||||
if mode.contains(TermMode::ALT_SCREEN) {
|
if mode.contains(TermMode::ALT_SCREEN) {
|
||||||
"alt"
|
"alt"
|
||||||
@ -460,40 +460,40 @@ impl View for TerminalView {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if mode.contains(TermMode::APP_CURSOR) {
|
if mode.contains(TermMode::APP_CURSOR) {
|
||||||
context.add_identifier("DECCKM");
|
keymap.add_identifier("DECCKM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::APP_KEYPAD) {
|
if mode.contains(TermMode::APP_KEYPAD) {
|
||||||
context.add_identifier("DECPAM");
|
keymap.add_identifier("DECPAM");
|
||||||
} else {
|
} else {
|
||||||
context.add_identifier("DECPNM");
|
keymap.add_identifier("DECPNM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::SHOW_CURSOR) {
|
if mode.contains(TermMode::SHOW_CURSOR) {
|
||||||
context.add_identifier("DECTCEM");
|
keymap.add_identifier("DECTCEM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::LINE_WRAP) {
|
if mode.contains(TermMode::LINE_WRAP) {
|
||||||
context.add_identifier("DECAWM");
|
keymap.add_identifier("DECAWM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::ORIGIN) {
|
if mode.contains(TermMode::ORIGIN) {
|
||||||
context.add_identifier("DECOM");
|
keymap.add_identifier("DECOM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::INSERT) {
|
if mode.contains(TermMode::INSERT) {
|
||||||
context.add_identifier("IRM");
|
keymap.add_identifier("IRM");
|
||||||
}
|
}
|
||||||
//LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
|
//LNM is apparently the name for this. https://vt100.net/docs/vt510-rm/LNM.html
|
||||||
if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
|
if mode.contains(TermMode::LINE_FEED_NEW_LINE) {
|
||||||
context.add_identifier("LNM");
|
keymap.add_identifier("LNM");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::FOCUS_IN_OUT) {
|
if mode.contains(TermMode::FOCUS_IN_OUT) {
|
||||||
context.add_identifier("report_focus");
|
keymap.add_identifier("report_focus");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::ALTERNATE_SCROLL) {
|
if mode.contains(TermMode::ALTERNATE_SCROLL) {
|
||||||
context.add_identifier("alternate_scroll");
|
keymap.add_identifier("alternate_scroll");
|
||||||
}
|
}
|
||||||
if mode.contains(TermMode::BRACKETED_PASTE) {
|
if mode.contains(TermMode::BRACKETED_PASTE) {
|
||||||
context.add_identifier("bracketed_paste");
|
keymap.add_identifier("bracketed_paste");
|
||||||
}
|
}
|
||||||
if mode.intersects(TermMode::MOUSE_MODE) {
|
if mode.intersects(TermMode::MOUSE_MODE) {
|
||||||
context.add_identifier("any_mouse_reporting");
|
keymap.add_identifier("any_mouse_reporting");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
|
let mouse_reporting = if mode.contains(TermMode::MOUSE_REPORT_CLICK) {
|
||||||
@ -505,7 +505,7 @@ impl View for TerminalView {
|
|||||||
} else {
|
} else {
|
||||||
"off"
|
"off"
|
||||||
};
|
};
|
||||||
context.add_key("mouse_reporting", mouse_reporting);
|
keymap.add_key("mouse_reporting", mouse_reporting);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let format = if mode.contains(TermMode::SGR_MOUSE) {
|
let format = if mode.contains(TermMode::SGR_MOUSE) {
|
||||||
@ -515,9 +515,8 @@ impl View for TerminalView {
|
|||||||
} else {
|
} else {
|
||||||
"normal"
|
"normal"
|
||||||
};
|
};
|
||||||
context.add_key("mouse_format", format);
|
keymap.add_key("mouse_format", format);
|
||||||
}
|
}
|
||||||
context
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,7 +309,7 @@ impl Vim {
|
|||||||
editor.set_input_enabled(!state.vim_controlled());
|
editor.set_input_enabled(!state.vim_controlled());
|
||||||
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
editor.selections.line_mode = matches!(state.mode, Mode::Visual { line: true });
|
||||||
let context_layer = state.keymap_context_layer();
|
let context_layer = state.keymap_context_layer();
|
||||||
editor.set_keymap_context_layer::<Self>(context_layer);
|
editor.set_keymap_context_layer::<Self>(context_layer, cx);
|
||||||
} else {
|
} else {
|
||||||
Self::unhook_vim_settings(editor, cx);
|
Self::unhook_vim_settings(editor, cx);
|
||||||
}
|
}
|
||||||
@ -321,7 +321,7 @@ impl Vim {
|
|||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.set_input_enabled(true);
|
editor.set_input_enabled(true);
|
||||||
editor.selections.line_mode = false;
|
editor.selections.line_mode = false;
|
||||||
editor.remove_keymap_context_layer::<Self>();
|
editor.remove_keymap_context_layer::<Self>(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ test-support = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
assets = { path = "../assets" }
|
||||||
db = { path = "../db" }
|
db = { path = "../db" }
|
||||||
call = { path = "../call" }
|
call = { path = "../call" }
|
||||||
client = { path = "../client" }
|
client = { path = "../client" }
|
||||||
|
@ -1841,12 +1841,11 @@ impl View for Pane {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
fn update_keymap_context(&self, keymap: &mut KeymapContext, _: &AppContext) {
|
||||||
let mut keymap = Self::default_keymap_context();
|
Self::reset_to_default_keymap_context(keymap);
|
||||||
if self.docked.is_some() {
|
if self.docked.is_some() {
|
||||||
keymap.add_identifier("docked");
|
keymap.add_identifier("docked");
|
||||||
}
|
}
|
||||||
keymap
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,8 @@ pub mod sidebar;
|
|||||||
mod status_bar;
|
mod status_bar;
|
||||||
mod toolbar;
|
mod toolbar;
|
||||||
|
|
||||||
pub use smallvec;
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
use assets::Assets;
|
||||||
use call::ActiveCall;
|
use call::ActiveCall;
|
||||||
use client::{
|
use client::{
|
||||||
proto::{self, PeerId},
|
proto::{self, PeerId},
|
||||||
@ -38,7 +37,6 @@ use gpui::{
|
|||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
impl_actions,
|
impl_actions,
|
||||||
keymap_matcher::KeymapContext,
|
|
||||||
platform::{
|
platform::{
|
||||||
CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
|
CursorStyle, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds,
|
||||||
WindowOptions,
|
WindowOptions,
|
||||||
@ -48,13 +46,14 @@ use gpui::{
|
|||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
|
use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ProjectItem};
|
||||||
use language::LanguageRegistry;
|
use language::{LanguageRegistry, Rope};
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp, env,
|
cmp, env,
|
||||||
future::Future,
|
future::Future,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
str,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@ -83,7 +82,7 @@ use status_bar::StatusBar;
|
|||||||
pub use status_bar::StatusItemView;
|
pub use status_bar::StatusItemView;
|
||||||
use theme::{Theme, ThemeRegistry};
|
use theme::{Theme, ThemeRegistry};
|
||||||
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
|
||||||
use util::ResultExt;
|
use util::{paths, ResultExt};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
|
static ref ZED_WINDOW_SIZE: Option<Vector2F> = env::var("ZED_WINDOW_SIZE")
|
||||||
@ -127,6 +126,8 @@ actions!(
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
actions!(zed, [OpenSettings]);
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct OpenPaths {
|
pub struct OpenPaths {
|
||||||
pub paths: Vec<PathBuf>,
|
pub paths: Vec<PathBuf>,
|
||||||
@ -266,6 +267,17 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
.detach();
|
.detach();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cx.add_action(
|
||||||
|
move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
||||||
|
create_and_open_local_file(&paths::SETTINGS, cx, || {
|
||||||
|
Settings::initial_user_settings_content(&Assets)
|
||||||
|
.as_ref()
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let client = &app_state.client;
|
let client = &app_state.client;
|
||||||
client.add_view_request_handler(Workspace::handle_follow);
|
client.add_view_request_handler(Workspace::handle_follow);
|
||||||
client.add_view_message_handler(Workspace::handle_unfollow);
|
client.add_view_message_handler(Workspace::handle_unfollow);
|
||||||
@ -2770,10 +2782,6 @@ impl View for Workspace {
|
|||||||
cx.focus(&self.active_pane);
|
cx.focus(&self.active_pane);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn keymap_context(&self, _: &AppContext) -> KeymapContext {
|
|
||||||
Self::default_keymap_context()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewId {
|
impl ViewId {
|
||||||
@ -2929,6 +2937,33 @@ pub fn open_new(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_and_open_local_file(
|
||||||
|
path: &'static Path,
|
||||||
|
cx: &mut ViewContext<Workspace>,
|
||||||
|
default_content: impl 'static + Send + FnOnce() -> Rope,
|
||||||
|
) -> Task<Result<Box<dyn ItemHandle>>> {
|
||||||
|
cx.spawn(|workspace, mut cx| async move {
|
||||||
|
let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
|
||||||
|
if !fs.is_file(path).await {
|
||||||
|
fs.create_file(path, Default::default()).await?;
|
||||||
|
fs.save(path, &default_content(), Default::default())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut items = workspace
|
||||||
|
.update(&mut cx, |workspace, cx| {
|
||||||
|
workspace.with_local_workspace(cx, |workspace, cx| {
|
||||||
|
workspace.open_paths(vec![path.to_path_buf()], false, cx)
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.await?
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let item = items.pop().flatten();
|
||||||
|
item.ok_or_else(|| anyhow!("path {path:?} is not a file"))?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn join_remote_project(
|
pub fn join_remote_project(
|
||||||
project_id: u64,
|
project_id: u64,
|
||||||
follow_user_id: u64,
|
follow_user_id: u64,
|
||||||
|
@ -3,7 +3,7 @@ authors = ["Nathan Sobo <nathansobo@gmail.com>"]
|
|||||||
description = "The fast, collaborative code editor."
|
description = "The fast, collaborative code editor."
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
name = "zed"
|
name = "zed"
|
||||||
version = "0.85.0"
|
version = "0.86.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
@ -52,9 +52,10 @@ use staff_mode::StaffMode;
|
|||||||
use theme::ThemeRegistry;
|
use theme::ThemeRegistry;
|
||||||
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
|
use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
self, dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, Workspace,
|
dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
|
||||||
|
Workspace,
|
||||||
};
|
};
|
||||||
use zed::{self, build_window_options, initialize_workspace, languages, menus, OpenSettings};
|
use zed::{self, build_window_options, initialize_workspace, languages, menus};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let http = http::client();
|
let http = http::client();
|
||||||
|
@ -12,7 +12,7 @@ pub fn menus() -> Vec<Menu<'static>> {
|
|||||||
MenuItem::submenu(Menu {
|
MenuItem::submenu(Menu {
|
||||||
name: "Preferences",
|
name: "Preferences",
|
||||||
items: vec![
|
items: vec![
|
||||||
MenuItem::action("Open Settings", super::OpenSettings),
|
MenuItem::action("Open Settings", workspace::OpenSettings),
|
||||||
MenuItem::action("Open Key Bindings", super::OpenKeymap),
|
MenuItem::action("Open Key Bindings", super::OpenKeymap),
|
||||||
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
MenuItem::action("Open Default Settings", super::OpenDefaultSettings),
|
||||||
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
MenuItem::action("Open Default Key Bindings", super::OpenDefaultKeymap),
|
||||||
|
@ -21,22 +21,24 @@ use gpui::{
|
|||||||
geometry::vector::vec2f,
|
geometry::vector::vec2f,
|
||||||
impl_actions,
|
impl_actions,
|
||||||
platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions},
|
platform::{Platform, PromptLevel, TitlebarOptions, WindowBounds, WindowKind, WindowOptions},
|
||||||
AppContext, AssetSource, ViewContext,
|
AppContext, ViewContext,
|
||||||
};
|
};
|
||||||
use language::Rope;
|
|
||||||
pub use lsp;
|
pub use lsp;
|
||||||
pub use project;
|
pub use project;
|
||||||
use project_panel::ProjectPanel;
|
use project_panel::ProjectPanel;
|
||||||
use search::{BufferSearchBar, ProjectSearchBar};
|
use search::{BufferSearchBar, ProjectSearchBar};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::to_string_pretty;
|
use serde_json::to_string_pretty;
|
||||||
use settings::Settings;
|
use settings::{Settings, DEFAULT_SETTINGS_ASSET_PATH};
|
||||||
use std::{borrow::Cow, env, path::Path, str, sync::Arc};
|
use std::{borrow::Cow, str, sync::Arc};
|
||||||
use terminal_view::terminal_button::TerminalButton;
|
use terminal_view::terminal_button::TerminalButton;
|
||||||
use util::{channel::ReleaseChannel, paths, ResultExt};
|
use util::{channel::ReleaseChannel, paths, ResultExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
pub use workspace;
|
pub use workspace;
|
||||||
use workspace::{open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow, Workspace};
|
use workspace::{
|
||||||
|
create_and_open_local_file, open_new, sidebar::SidebarSide, AppState, NewFile, NewWindow,
|
||||||
|
Workspace,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Clone, PartialEq)]
|
#[derive(Deserialize, Clone, PartialEq)]
|
||||||
pub struct OpenBrowser {
|
pub struct OpenBrowser {
|
||||||
@ -57,7 +59,6 @@ actions!(
|
|||||||
ToggleFullScreen,
|
ToggleFullScreen,
|
||||||
Quit,
|
Quit,
|
||||||
DebugElements,
|
DebugElements,
|
||||||
OpenSettings,
|
|
||||||
OpenLog,
|
OpenLog,
|
||||||
OpenLicenses,
|
OpenLicenses,
|
||||||
OpenTelemetryLog,
|
OpenTelemetryLog,
|
||||||
@ -148,20 +149,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
|
|||||||
})
|
})
|
||||||
.detach_and_log_err(cx);
|
.detach_and_log_err(cx);
|
||||||
});
|
});
|
||||||
cx.add_action(
|
|
||||||
move |workspace: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
|
|
||||||
open_config_file(workspace, &paths::SETTINGS, cx, || {
|
|
||||||
str::from_utf8(
|
|
||||||
Assets
|
|
||||||
.load("settings/initial_user_settings.json")
|
|
||||||
.unwrap()
|
|
||||||
.as_ref(),
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.into()
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext<Workspace>| {
|
move |workspace: &mut Workspace, _: &OpenLog, cx: &mut ViewContext<Workspace>| {
|
||||||
open_log_file(workspace, cx);
|
open_log_file(workspace, cx);
|
||||||
@ -184,8 +171,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
move |workspace: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
|
move |_: &mut Workspace, _: &OpenKeymap, cx: &mut ViewContext<Workspace>| {
|
||||||
open_config_file(workspace, &paths::KEYMAP, cx, Default::default);
|
create_and_open_local_file(&paths::KEYMAP, cx, Default::default).detach_and_log_err(cx);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
cx.add_action(
|
cx.add_action(
|
||||||
@ -205,7 +192,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
|
|||||||
cx: &mut ViewContext<Workspace>| {
|
cx: &mut ViewContext<Workspace>| {
|
||||||
open_bundled_file(
|
open_bundled_file(
|
||||||
workspace,
|
workspace,
|
||||||
"settings/default.json",
|
DEFAULT_SETTINGS_ASSET_PATH,
|
||||||
"Default Settings",
|
"Default Settings",
|
||||||
"JSON",
|
"JSON",
|
||||||
cx,
|
cx,
|
||||||
@ -450,33 +437,6 @@ fn about(_: &mut Workspace, _: &About, cx: &mut gpui::ViewContext<Workspace>) {
|
|||||||
cx.prompt(PromptLevel::Info, &format!("{app_name} {version}"), &["OK"]);
|
cx.prompt(PromptLevel::Info, &format!("{app_name} {version}"), &["OK"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_config_file(
|
|
||||||
workspace: &mut Workspace,
|
|
||||||
path: &'static Path,
|
|
||||||
cx: &mut ViewContext<Workspace>,
|
|
||||||
default_content: impl 'static + Send + FnOnce() -> Rope,
|
|
||||||
) {
|
|
||||||
let fs = workspace.app_state().fs.clone();
|
|
||||||
cx.spawn(|workspace, mut cx| async move {
|
|
||||||
if !fs.is_file(path).await {
|
|
||||||
fs.create_file(path, Default::default()).await?;
|
|
||||||
fs.save(path, &default_content(), Default::default())
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
workspace
|
|
||||||
.update(&mut cx, |workspace, cx| {
|
|
||||||
workspace.with_local_workspace(cx, |workspace, cx| {
|
|
||||||
workspace.open_paths(vec![path.to_path_buf()], false, cx)
|
|
||||||
})
|
|
||||||
})?
|
|
||||||
.await?
|
|
||||||
.await;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
|
||||||
})
|
|
||||||
.detach_and_log_err(cx)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
fn open_log_file(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||||
const MAX_LINES: usize = 1000;
|
const MAX_LINES: usize = 1000;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user