Merge remote-tracking branch 'origin/main' into panels
This commit is contained in:
commit
208ff2fba7
33 changed files with 827 additions and 309 deletions
10
.github/pull_request_template.md
vendored
10
.github/pull_request_template.md
vendored
|
@ -2,4 +2,12 @@
|
||||||
|
|
||||||
Release Notes:
|
Release Notes:
|
||||||
|
|
||||||
* [[Added foo / Fixed bar / No notes]]
|
Use `N/A` in this section if this item should be skipped in the release notes.
|
||||||
|
|
||||||
|
Add release note lines here:
|
||||||
|
|
||||||
|
* (Added|Fixed|Improved) ... ([#<public_issue_number_if_exists>](https://github.com/zed-industries/community/issues/<public_issue_number_if_exists>)).
|
||||||
|
* ...
|
||||||
|
|
||||||
|
If the release notes are only intended for a specific release channel only, add `(<release_channel>-only)` to the end of the release note line.
|
||||||
|
These will be removed by the person making the release.
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -4886,6 +4886,7 @@ dependencies = [
|
||||||
name = "project_panel"
|
name = "project_panel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"client",
|
"client",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
"db",
|
"db",
|
||||||
|
@ -4899,6 +4900,7 @@ dependencies = [
|
||||||
"project",
|
"project",
|
||||||
"schemars",
|
"schemars",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"theme",
|
"theme",
|
||||||
|
|
|
@ -68,10 +68,12 @@
|
||||||
"cmd-z": "editor::Undo",
|
"cmd-z": "editor::Undo",
|
||||||
"cmd-shift-z": "editor::Redo",
|
"cmd-shift-z": "editor::Redo",
|
||||||
"up": "editor::MoveUp",
|
"up": "editor::MoveUp",
|
||||||
|
"ctrl-up": "editor::MoveToStartOfParagraph",
|
||||||
"pageup": "editor::PageUp",
|
"pageup": "editor::PageUp",
|
||||||
"shift-pageup": "editor::MovePageUp",
|
"shift-pageup": "editor::MovePageUp",
|
||||||
"home": "editor::MoveToBeginningOfLine",
|
"home": "editor::MoveToBeginningOfLine",
|
||||||
"down": "editor::MoveDown",
|
"down": "editor::MoveDown",
|
||||||
|
"ctrl-down": "editor::MoveToEndOfParagraph",
|
||||||
"pagedown": "editor::PageDown",
|
"pagedown": "editor::PageDown",
|
||||||
"shift-pagedown": "editor::MovePageDown",
|
"shift-pagedown": "editor::MovePageDown",
|
||||||
"end": "editor::MoveToEndOfLine",
|
"end": "editor::MoveToEndOfLine",
|
||||||
|
@ -104,6 +106,8 @@
|
||||||
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
||||||
"alt-shift-right": "editor::SelectToNextWordEnd",
|
"alt-shift-right": "editor::SelectToNextWordEnd",
|
||||||
"alt-shift-f": "editor::SelectToNextWordEnd",
|
"alt-shift-f": "editor::SelectToNextWordEnd",
|
||||||
|
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||||
|
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||||
"cmd-shift-up": "editor::SelectToBeginning",
|
"cmd-shift-up": "editor::SelectToBeginning",
|
||||||
"cmd-shift-down": "editor::SelectToEnd",
|
"cmd-shift-down": "editor::SelectToEnd",
|
||||||
"cmd-a": "editor::SelectAll",
|
"cmd-a": "editor::SelectAll",
|
||||||
|
|
|
@ -52,19 +52,32 @@
|
||||||
// 3. Draw all invisible symbols:
|
// 3. Draw all invisible symbols:
|
||||||
// "all"
|
// "all"
|
||||||
"show_whitespaces": "selection",
|
"show_whitespaces": "selection",
|
||||||
// Whether to show the scrollbar in the editor.
|
// Scrollbar related settings
|
||||||
// This setting can take four values:
|
"scrollbar": {
|
||||||
//
|
// When to show the scrollbar in the editor.
|
||||||
// 1. Show the scrollbar if there's important information or
|
// This setting can take four values:
|
||||||
// follow the system's configured behavior (default):
|
//
|
||||||
// "auto"
|
// 1. Show the scrollbar if there's important information or
|
||||||
// 2. Match the system's configured behavior:
|
// follow the system's configured behavior (default):
|
||||||
// "system"
|
// "auto"
|
||||||
// 3. Always show the scrollbar:
|
// 2. Match the system's configured behavior:
|
||||||
// "always"
|
// "system"
|
||||||
// 4. Never show the scrollbar:
|
// 3. Always show the scrollbar:
|
||||||
// "never"
|
// "always"
|
||||||
"show_scrollbars": "auto",
|
// 4. Never show the scrollbar:
|
||||||
|
// "never"
|
||||||
|
"show": "auto",
|
||||||
|
// Whether to show git diff indicators in the scrollbar.
|
||||||
|
"git_diff": true
|
||||||
|
},
|
||||||
|
"project_panel": {
|
||||||
|
// Whether to show the git status in the project panel.
|
||||||
|
"git_status": true,
|
||||||
|
// Where to dock project panel. Can be 'left' or 'right'.
|
||||||
|
"dock": "left",
|
||||||
|
// Default width of the project panel.
|
||||||
|
"default_width": 240
|
||||||
|
},
|
||||||
// Whether the screen sharing icon is shown in the os status bar.
|
// Whether the screen sharing icon is shown in the os status bar.
|
||||||
"show_call_status_icon": true,
|
"show_call_status_icon": true,
|
||||||
// Whether to use language servers to provide code intelligence.
|
// Whether to use language servers to provide code intelligence.
|
||||||
|
@ -128,13 +141,6 @@
|
||||||
},
|
},
|
||||||
// Automatically update Zed
|
// Automatically update Zed
|
||||||
"auto_update": true,
|
"auto_update": true,
|
||||||
// Settings specific to the project panel
|
|
||||||
"project_panel": {
|
|
||||||
// Where to dock project panel. Can be 'left' or 'right'.
|
|
||||||
"dock": "left",
|
|
||||||
// Default width of the project panel.
|
|
||||||
"default_width": 240
|
|
||||||
},
|
|
||||||
// Git gutter behavior configuration.
|
// Git gutter behavior configuration.
|
||||||
"git": {
|
"git": {
|
||||||
// Control whether the git gutter is shown. May take 2 values:
|
// Control whether the git gutter is shown. May take 2 values:
|
||||||
|
|
|
@ -339,7 +339,7 @@ pub struct TelemetrySettings {
|
||||||
pub metrics: bool,
|
pub metrics: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TelemetrySettingsContent {
|
pub struct TelemetrySettingsContent {
|
||||||
pub diagnostics: Option<bool>,
|
pub diagnostics: Option<bool>,
|
||||||
pub metrics: Option<bool>,
|
pub metrics: Option<bool>,
|
||||||
|
|
|
@ -2688,6 +2688,7 @@ async fn test_git_branch_name(
|
||||||
});
|
});
|
||||||
|
|
||||||
let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
|
let project_remote_c = client_c.build_remote_project(project_id, cx_c).await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
project_remote_c.read_with(cx_c, |project, cx| {
|
project_remote_c.read_with(cx_c, |project, cx| {
|
||||||
assert_branch(Some("branch-2"), project, cx)
|
assert_branch(Some("branch-2"), project, cx)
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@ use theme::ThemeSettings;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
|
||||||
ItemNavHistory, Pane, ToolbarItemLocation, Workspace,
|
ItemNavHistory, Pane, PaneBackdrop, ToolbarItemLocation, Workspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(diagnostics, [Deploy]);
|
actions!(diagnostics, [Deploy]);
|
||||||
|
@ -90,11 +90,15 @@ impl View for ProjectDiagnosticsEditor {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
|
||||||
if self.path_states.is_empty() {
|
if self.path_states.is_empty() {
|
||||||
let theme = &theme::current(cx).project_diagnostics;
|
let theme = &theme::current(cx).project_diagnostics;
|
||||||
Label::new("No problems in workspace", theme.empty_message.clone())
|
PaneBackdrop::new(
|
||||||
.aligned()
|
cx.view_id(),
|
||||||
.contained()
|
Label::new("No problems in workspace", theme.empty_message.clone())
|
||||||
.with_style(theme.container)
|
.aligned()
|
||||||
.into_any()
|
.contained()
|
||||||
|
.with_style(theme.container)
|
||||||
|
.into_any(),
|
||||||
|
)
|
||||||
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
ChildView::new(&self.editor, cx).into_any()
|
ChildView::new(&self.editor, cx).into_any()
|
||||||
}
|
}
|
||||||
|
@ -161,8 +165,13 @@ impl ProjectDiagnosticsEditor {
|
||||||
editor.set_vertical_scroll_margin(5, cx);
|
editor.set_vertical_scroll_margin(5, cx);
|
||||||
editor
|
editor
|
||||||
});
|
});
|
||||||
cx.subscribe(&editor, |_, _, event, cx| cx.emit(event.clone()))
|
cx.subscribe(&editor, |this, _, event, cx| {
|
||||||
.detach();
|
cx.emit(event.clone());
|
||||||
|
if event == &editor::Event::Focused && this.path_states.is_empty() {
|
||||||
|
cx.focus_self()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
let project = project_handle.read(cx);
|
let project = project_handle.read(cx);
|
||||||
let paths_to_update = project
|
let paths_to_update = project
|
||||||
|
|
|
@ -216,6 +216,8 @@ actions!(
|
||||||
MoveToNextSubwordEnd,
|
MoveToNextSubwordEnd,
|
||||||
MoveToBeginningOfLine,
|
MoveToBeginningOfLine,
|
||||||
MoveToEndOfLine,
|
MoveToEndOfLine,
|
||||||
|
MoveToStartOfParagraph,
|
||||||
|
MoveToEndOfParagraph,
|
||||||
MoveToBeginning,
|
MoveToBeginning,
|
||||||
MoveToEnd,
|
MoveToEnd,
|
||||||
SelectUp,
|
SelectUp,
|
||||||
|
@ -226,6 +228,8 @@ actions!(
|
||||||
SelectToPreviousSubwordStart,
|
SelectToPreviousSubwordStart,
|
||||||
SelectToNextWordEnd,
|
SelectToNextWordEnd,
|
||||||
SelectToNextSubwordEnd,
|
SelectToNextSubwordEnd,
|
||||||
|
SelectToStartOfParagraph,
|
||||||
|
SelectToEndOfParagraph,
|
||||||
SelectToBeginning,
|
SelectToBeginning,
|
||||||
SelectToEnd,
|
SelectToEnd,
|
||||||
SelectAll,
|
SelectAll,
|
||||||
|
@ -337,6 +341,8 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(Editor::move_to_next_subword_end);
|
cx.add_action(Editor::move_to_next_subword_end);
|
||||||
cx.add_action(Editor::move_to_beginning_of_line);
|
cx.add_action(Editor::move_to_beginning_of_line);
|
||||||
cx.add_action(Editor::move_to_end_of_line);
|
cx.add_action(Editor::move_to_end_of_line);
|
||||||
|
cx.add_action(Editor::move_to_start_of_paragraph);
|
||||||
|
cx.add_action(Editor::move_to_end_of_paragraph);
|
||||||
cx.add_action(Editor::move_to_beginning);
|
cx.add_action(Editor::move_to_beginning);
|
||||||
cx.add_action(Editor::move_to_end);
|
cx.add_action(Editor::move_to_end);
|
||||||
cx.add_action(Editor::select_up);
|
cx.add_action(Editor::select_up);
|
||||||
|
@ -349,6 +355,8 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(Editor::select_to_next_subword_end);
|
cx.add_action(Editor::select_to_next_subword_end);
|
||||||
cx.add_action(Editor::select_to_beginning_of_line);
|
cx.add_action(Editor::select_to_beginning_of_line);
|
||||||
cx.add_action(Editor::select_to_end_of_line);
|
cx.add_action(Editor::select_to_end_of_line);
|
||||||
|
cx.add_action(Editor::select_to_start_of_paragraph);
|
||||||
|
cx.add_action(Editor::select_to_end_of_paragraph);
|
||||||
cx.add_action(Editor::select_to_beginning);
|
cx.add_action(Editor::select_to_beginning);
|
||||||
cx.add_action(Editor::select_to_end);
|
cx.add_action(Editor::select_to_end);
|
||||||
cx.add_action(Editor::select_all);
|
cx.add_action(Editor::select_all);
|
||||||
|
@ -525,15 +533,6 @@ pub struct EditorSnapshot {
|
||||||
ongoing_scroll: OngoingScroll,
|
ongoing_scroll: OngoingScroll,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorSnapshot {
|
|
||||||
fn has_scrollbar_info(&self) -> bool {
|
|
||||||
self.buffer_snapshot
|
|
||||||
.git_diff_hunks_in_range(0..self.max_point().row())
|
|
||||||
.next()
|
|
||||||
.is_some()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct SelectionHistoryEntry {
|
struct SelectionHistoryEntry {
|
||||||
selections: Arc<[Selection<Anchor>]>,
|
selections: Arc<[Selection<Anchor>]>,
|
||||||
|
@ -4762,6 +4761,80 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_to_start_of_paragraph(
|
||||||
|
&mut self,
|
||||||
|
_: &MoveToStartOfParagraph,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
selection.collapse_to(
|
||||||
|
movement::start_of_paragraph(map, selection.head()),
|
||||||
|
SelectionGoal::None,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_to_end_of_paragraph(
|
||||||
|
&mut self,
|
||||||
|
_: &MoveToEndOfParagraph,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
selection.collapse_to(
|
||||||
|
movement::end_of_paragraph(map, selection.head()),
|
||||||
|
SelectionGoal::None,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_to_start_of_paragraph(
|
||||||
|
&mut self,
|
||||||
|
_: &SelectToStartOfParagraph,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_heads_with(|map, head, _| {
|
||||||
|
(movement::start_of_paragraph(map, head), SelectionGoal::None)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_to_end_of_paragraph(
|
||||||
|
&mut self,
|
||||||
|
_: &SelectToEndOfParagraph,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
|
cx.propagate_action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_heads_with(|map, head, _| {
|
||||||
|
(movement::end_of_paragraph(map, head), SelectionGoal::None)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
|
pub fn move_to_beginning(&mut self, _: &MoveToBeginning, cx: &mut ViewContext<Self>) {
|
||||||
if matches!(self.mode, EditorMode::SingleLine) {
|
if matches!(self.mode, EditorMode::SingleLine) {
|
||||||
cx.propagate_action();
|
cx.propagate_action();
|
||||||
|
@ -7128,6 +7201,7 @@ pub enum Event {
|
||||||
BufferEdited,
|
BufferEdited,
|
||||||
Edited,
|
Edited,
|
||||||
Reparsed,
|
Reparsed,
|
||||||
|
Focused,
|
||||||
Blurred,
|
Blurred,
|
||||||
DirtyChanged,
|
DirtyChanged,
|
||||||
Saved,
|
Saved,
|
||||||
|
@ -7181,6 +7255,7 @@ impl View for Editor {
|
||||||
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
fn focus_in(&mut self, _: AnyViewHandle, cx: &mut ViewContext<Self>) {
|
||||||
if cx.is_self_focused() {
|
if cx.is_self_focused() {
|
||||||
let focused_event = EditorFocused(cx.handle());
|
let focused_event = EditorFocused(cx.handle());
|
||||||
|
cx.emit(Event::Focused);
|
||||||
cx.emit_global(focused_event);
|
cx.emit_global(focused_event);
|
||||||
}
|
}
|
||||||
if let Some(rename) = self.pending_rename.as_ref() {
|
if let Some(rename) = self.pending_rename.as_ref() {
|
||||||
|
|
|
@ -7,25 +7,36 @@ pub struct EditorSettings {
|
||||||
pub cursor_blink: bool,
|
pub cursor_blink: bool,
|
||||||
pub hover_popover_enabled: bool,
|
pub hover_popover_enabled: bool,
|
||||||
pub show_completions_on_input: bool,
|
pub show_completions_on_input: bool,
|
||||||
pub show_scrollbars: ShowScrollbars,
|
pub scrollbar: Scrollbar,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
pub struct Scrollbar {
|
||||||
|
pub show: ShowScrollbar,
|
||||||
|
pub git_diff: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ShowScrollbars {
|
pub enum ShowScrollbar {
|
||||||
#[default]
|
|
||||||
Auto,
|
Auto,
|
||||||
System,
|
System,
|
||||||
Always,
|
Always,
|
||||||
Never,
|
Never,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct EditorSettingsContent {
|
pub struct EditorSettingsContent {
|
||||||
pub cursor_blink: Option<bool>,
|
pub cursor_blink: Option<bool>,
|
||||||
pub hover_popover_enabled: Option<bool>,
|
pub hover_popover_enabled: Option<bool>,
|
||||||
pub show_completions_on_input: Option<bool>,
|
pub show_completions_on_input: Option<bool>,
|
||||||
pub show_scrollbars: Option<ShowScrollbars>,
|
pub scrollbar: Option<ScrollbarContent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
|
pub struct ScrollbarContent {
|
||||||
|
pub show: Option<ShowScrollbar>,
|
||||||
|
pub git_diff: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Setting for EditorSettings {
|
impl Setting for EditorSettings {
|
||||||
|
|
|
@ -1243,6 +1243,118 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx, |_| {});
|
||||||
|
let mut cx = EditorTestContext::new(cx);
|
||||||
|
|
||||||
|
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
|
||||||
|
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
|
||||||
|
|
||||||
|
cx.set_state(
|
||||||
|
&r#"ˇone
|
||||||
|
two
|
||||||
|
|
||||||
|
three
|
||||||
|
fourˇ
|
||||||
|
five
|
||||||
|
|
||||||
|
six"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"one
|
||||||
|
two
|
||||||
|
ˇ
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
ˇ
|
||||||
|
six"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"one
|
||||||
|
two
|
||||||
|
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
ˇ
|
||||||
|
sixˇ"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"ˇone
|
||||||
|
two
|
||||||
|
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
|
||||||
|
sixˇ"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"ˇone
|
||||||
|
two
|
||||||
|
ˇ
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
|
||||||
|
six"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"ˇone
|
||||||
|
two
|
||||||
|
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
|
||||||
|
sixˇ"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"one
|
||||||
|
two
|
||||||
|
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
ˇ
|
||||||
|
sixˇ"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.update_editor(|editor, cx| editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, cx));
|
||||||
|
cx.assert_editor_state(
|
||||||
|
&r#"one
|
||||||
|
two
|
||||||
|
ˇ
|
||||||
|
three
|
||||||
|
four
|
||||||
|
five
|
||||||
|
ˇ
|
||||||
|
six"#
|
||||||
|
.unindent(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
|
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx, |_| {});
|
init_test(cx, |_| {});
|
||||||
|
|
|
@ -5,7 +5,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
|
display_map::{BlockStyle, DisplaySnapshot, FoldStatus, TransformBlock},
|
||||||
editor_settings::ShowScrollbars,
|
editor_settings::ShowScrollbar,
|
||||||
git::{diff_hunk_to_display, DisplayDiffHunk},
|
git::{diff_hunk_to_display, DisplayDiffHunk},
|
||||||
hover_popover::{
|
hover_popover::{
|
||||||
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
|
hide_hover, hover_at, HOVER_POPOVER_GAP, MIN_POPOVER_CHARACTER_WIDTH,
|
||||||
|
@ -1052,51 +1052,53 @@ impl EditorElement {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let diff_style = theme::current(cx).editor.diff.clone();
|
if layout.is_singleton && settings::get::<EditorSettings>(cx).scrollbar.git_diff {
|
||||||
for hunk in layout
|
let diff_style = theme::current(cx).editor.scrollbar.git.clone();
|
||||||
.position_map
|
for hunk in layout
|
||||||
.snapshot
|
.position_map
|
||||||
.buffer_snapshot
|
.snapshot
|
||||||
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
.buffer_snapshot
|
||||||
{
|
.git_diff_hunks_in_range(0..(max_row.floor() as u32))
|
||||||
let start_display = Point::new(hunk.buffer_range.start, 0)
|
{
|
||||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
let start_display = Point::new(hunk.buffer_range.start, 0)
|
||||||
let end_display = Point::new(hunk.buffer_range.end, 0)
|
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||||
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
let end_display = Point::new(hunk.buffer_range.end, 0)
|
||||||
let start_y = y_for_row(start_display.row() as f32);
|
.to_display_point(&layout.position_map.snapshot.display_snapshot);
|
||||||
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
let start_y = y_for_row(start_display.row() as f32);
|
||||||
y_for_row((end_display.row() + 1) as f32)
|
let mut end_y = if hunk.buffer_range.start == hunk.buffer_range.end {
|
||||||
} else {
|
y_for_row((end_display.row() + 1) as f32)
|
||||||
y_for_row((end_display.row()) as f32)
|
} else {
|
||||||
};
|
y_for_row((end_display.row()) as f32)
|
||||||
|
};
|
||||||
|
|
||||||
if end_y - start_y < 1. {
|
if end_y - start_y < 1. {
|
||||||
end_y = start_y + 1.;
|
end_y = start_y + 1.;
|
||||||
|
}
|
||||||
|
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
||||||
|
|
||||||
|
let color = match hunk.status() {
|
||||||
|
DiffHunkStatus::Added => diff_style.inserted,
|
||||||
|
DiffHunkStatus::Modified => diff_style.modified,
|
||||||
|
DiffHunkStatus::Removed => diff_style.deleted,
|
||||||
|
};
|
||||||
|
|
||||||
|
let border = Border {
|
||||||
|
width: 1.,
|
||||||
|
color: style.thumb.border.color,
|
||||||
|
overlay: false,
|
||||||
|
top: false,
|
||||||
|
right: true,
|
||||||
|
bottom: false,
|
||||||
|
left: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
scene.push_quad(Quad {
|
||||||
|
bounds,
|
||||||
|
background: Some(color),
|
||||||
|
border,
|
||||||
|
corner_radius: style.thumb.corner_radius,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
let bounds = RectF::from_points(vec2f(left, start_y), vec2f(right, end_y));
|
|
||||||
|
|
||||||
let color = match hunk.status() {
|
|
||||||
DiffHunkStatus::Added => diff_style.inserted,
|
|
||||||
DiffHunkStatus::Modified => diff_style.modified,
|
|
||||||
DiffHunkStatus::Removed => diff_style.deleted,
|
|
||||||
};
|
|
||||||
|
|
||||||
let border = Border {
|
|
||||||
width: 1.,
|
|
||||||
color: style.thumb.border.color,
|
|
||||||
overlay: false,
|
|
||||||
top: false,
|
|
||||||
right: true,
|
|
||||||
bottom: false,
|
|
||||||
left: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
scene.push_quad(Quad {
|
|
||||||
bounds,
|
|
||||||
background: Some(color),
|
|
||||||
border,
|
|
||||||
corner_radius: style.thumb.corner_radius,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.push_quad(Quad {
|
scene.push_quad(Quad {
|
||||||
|
@ -2065,13 +2067,17 @@ impl Element<Editor> for EditorElement {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let show_scrollbars = match settings::get::<EditorSettings>(cx).show_scrollbars {
|
let scrollbar_settings = &settings::get::<EditorSettings>(cx).scrollbar;
|
||||||
ShowScrollbars::Auto => {
|
let show_scrollbars = match scrollbar_settings.show {
|
||||||
snapshot.has_scrollbar_info() || editor.scroll_manager.scrollbars_visible()
|
ShowScrollbar::Auto => {
|
||||||
|
// Git
|
||||||
|
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
|
||||||
|
// Scrollmanager
|
||||||
|
|| editor.scroll_manager.scrollbars_visible()
|
||||||
}
|
}
|
||||||
ShowScrollbars::System => editor.scroll_manager.scrollbars_visible(),
|
ShowScrollbar::System => editor.scroll_manager.scrollbars_visible(),
|
||||||
ShowScrollbars::Always => true,
|
ShowScrollbar::Always => true,
|
||||||
ShowScrollbars::Never => false,
|
ShowScrollbar::Never => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let include_root = editor
|
let include_root = editor
|
||||||
|
@ -2290,6 +2296,7 @@ impl Element<Editor> for EditorElement {
|
||||||
text_size,
|
text_size,
|
||||||
scrollbar_row_range,
|
scrollbar_row_range,
|
||||||
show_scrollbars,
|
show_scrollbars,
|
||||||
|
is_singleton,
|
||||||
max_row,
|
max_row,
|
||||||
gutter_margin,
|
gutter_margin,
|
||||||
active_rows,
|
active_rows,
|
||||||
|
@ -2445,6 +2452,7 @@ pub struct LayoutState {
|
||||||
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
||||||
scrollbar_row_range: Range<f32>,
|
scrollbar_row_range: Range<f32>,
|
||||||
show_scrollbars: bool,
|
show_scrollbars: bool,
|
||||||
|
is_singleton: bool,
|
||||||
max_row: u32,
|
max_row: u32,
|
||||||
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
|
context_menu: Option<(DisplayPoint, AnyElement<Editor>)>,
|
||||||
code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
|
code_actions_indicator: Option<(u32, AnyElement<Editor>)>,
|
||||||
|
|
|
@ -193,6 +193,44 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn start_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||||
|
let point = display_point.to_point(map);
|
||||||
|
if point.row == 0 {
|
||||||
|
return map.max_point();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut found_non_blank_line = false;
|
||||||
|
for row in (0..point.row + 1).rev() {
|
||||||
|
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||||
|
if found_non_blank_line && blank {
|
||||||
|
return Point::new(row, 0).to_display_point(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
found_non_blank_line |= !blank;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisplayPoint::zero()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn end_of_paragraph(map: &DisplaySnapshot, display_point: DisplayPoint) -> DisplayPoint {
|
||||||
|
let point = display_point.to_point(map);
|
||||||
|
if point.row == map.max_buffer_row() {
|
||||||
|
return DisplayPoint::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut found_non_blank_line = false;
|
||||||
|
for row in point.row..map.max_buffer_row() + 1 {
|
||||||
|
let blank = map.buffer_snapshot.is_line_blank(row);
|
||||||
|
if found_non_blank_line && blank {
|
||||||
|
return Point::new(row, 0).to_display_point(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
found_non_blank_line |= !blank;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.max_point()
|
||||||
|
}
|
||||||
|
|
||||||
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
|
/// Scans for a boundary preceding the given start point `from` until a boundary is found, indicated by the
|
||||||
/// given predicate returning true. The predicate is called with the character to the left and right
|
/// given predicate returning true. The predicate is called with the character to the left and right
|
||||||
/// of the candidate boundary location, and will be called with `\n` characters indicating the start
|
/// of the candidate boundary location, and will be called with `\n` characters indicating the start
|
||||||
|
|
|
@ -2841,6 +2841,15 @@ impl MultiBufferSnapshot {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_git_diffs(&self) -> bool {
|
||||||
|
for excerpt in self.excerpts.iter() {
|
||||||
|
if !excerpt.buffer.git_diff.is_empty() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn git_diff_hunks_in_range_rev<'a>(
|
pub fn git_diff_hunks_in_range_rev<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
row_range: Range<u32>,
|
row_range: Range<u32>,
|
||||||
|
|
|
@ -71,6 +71,10 @@ impl BufferDiff {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.tree.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hunks_in_row_range<'a>(
|
pub fn hunks_in_row_range<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
range: Range<u32>,
|
range: Range<u32>,
|
||||||
|
|
|
@ -1644,10 +1644,17 @@ impl Buffer {
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
if lamport_timestamp > self.diagnostics_timestamp {
|
if lamport_timestamp > self.diagnostics_timestamp {
|
||||||
match self.diagnostics.binary_search_by_key(&server_id, |e| e.0) {
|
let ix = self.diagnostics.binary_search_by_key(&server_id, |e| e.0);
|
||||||
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
|
if diagnostics.len() == 0 {
|
||||||
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
|
if let Ok(ix) = ix {
|
||||||
};
|
self.diagnostics.remove(ix);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match ix {
|
||||||
|
Err(ix) => self.diagnostics.insert(ix, (server_id, diagnostics)),
|
||||||
|
Ok(ix) => self.diagnostics[ix].1 = diagnostics,
|
||||||
|
};
|
||||||
|
}
|
||||||
self.diagnostics_timestamp = lamport_timestamp;
|
self.diagnostics_timestamp = lamport_timestamp;
|
||||||
self.diagnostics_update_count += 1;
|
self.diagnostics_update_count += 1;
|
||||||
self.text.lamport_clock.observe(lamport_timestamp);
|
self.text.lamport_clock.observe(lamport_timestamp);
|
||||||
|
|
|
@ -80,6 +80,10 @@ impl DiagnosticSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.diagnostics.summary().count
|
||||||
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
|
pub fn iter(&self) -> impl Iterator<Item = &DiagnosticEntry<Anchor>> {
|
||||||
self.diagnostics.iter()
|
self.diagnostics.iter()
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub struct CopilotSettings {
|
||||||
pub disabled_globs: Vec<GlobMatcher>,
|
pub disabled_globs: Vec<GlobMatcher>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct AllLanguageSettingsContent {
|
pub struct AllLanguageSettingsContent {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub features: Option<FeaturesContent>,
|
pub features: Option<FeaturesContent>,
|
||||||
|
|
|
@ -16,6 +16,7 @@ use copilot::Copilot;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc::{self, UnboundedReceiver},
|
channel::mpsc::{self, UnboundedReceiver},
|
||||||
future::{try_join_all, Shared},
|
future::{try_join_all, Shared},
|
||||||
|
stream::FuturesUnordered,
|
||||||
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
|
||||||
};
|
};
|
||||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||||
|
@ -1374,7 +1375,7 @@ impl Project {
|
||||||
return Task::ready(Ok(existing_buffer));
|
return Task::ready(Ok(existing_buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
|
let loading_watch = match self.loading_buffers_by_path.entry(project_path.clone()) {
|
||||||
// If the given path is already being loaded, then wait for that existing
|
// If the given path is already being loaded, then wait for that existing
|
||||||
// task to complete and return the same buffer.
|
// task to complete and return the same buffer.
|
||||||
hash_map::Entry::Occupied(e) => e.get().clone(),
|
hash_map::Entry::Occupied(e) => e.get().clone(),
|
||||||
|
@ -1405,15 +1406,9 @@ impl Project {
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.foreground().spawn(async move {
|
cx.foreground().spawn(async move {
|
||||||
loop {
|
pump_loading_buffer_reciever(loading_watch)
|
||||||
if let Some(result) = loading_watch.borrow().as_ref() {
|
.await
|
||||||
match result {
|
.map_err(|error| anyhow!("{}", error))
|
||||||
Ok(buffer) => return Ok(buffer.clone()),
|
|
||||||
Err(error) => return Err(anyhow!("{}", error)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
loading_watch.next().await;
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2565,6 +2560,23 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for buffer in self.opened_buffers.values() {
|
||||||
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
|
buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.update_diagnostics(server_id, Default::default(), cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for worktree in &self.worktrees {
|
||||||
|
if let Some(worktree) = worktree.upgrade(cx) {
|
||||||
|
worktree.update(cx, |worktree, cx| {
|
||||||
|
if let Some(worktree) = worktree.as_local_mut() {
|
||||||
|
worktree.clear_diagnostics_for_language_server(server_id, cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.language_server_statuses.remove(&server_id);
|
self.language_server_statuses.remove(&server_id);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
|
@ -4805,6 +4817,51 @@ impl Project {
|
||||||
) {
|
) {
|
||||||
debug_assert!(worktree_handle.read(cx).is_local());
|
debug_assert!(worktree_handle.read(cx).is_local());
|
||||||
|
|
||||||
|
// Setup the pending buffers
|
||||||
|
let future_buffers = self
|
||||||
|
.loading_buffers_by_path
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(path, receiver)| {
|
||||||
|
let path = &path.path;
|
||||||
|
let (work_directory, repo) = repos
|
||||||
|
.iter()
|
||||||
|
.find(|(work_directory, _)| path.starts_with(work_directory))?;
|
||||||
|
|
||||||
|
let repo_relative_path = path.strip_prefix(work_directory).log_err()?;
|
||||||
|
|
||||||
|
let receiver = receiver.clone();
|
||||||
|
let repo_ptr = repo.repo_ptr.clone();
|
||||||
|
let repo_relative_path = repo_relative_path.to_owned();
|
||||||
|
Some(async move {
|
||||||
|
pump_loading_buffer_reciever(receiver)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.map(|buffer| (buffer, repo_relative_path, repo_ptr))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<FuturesUnordered<_>>()
|
||||||
|
.filter_map(|result| async move {
|
||||||
|
let (buffer_handle, repo_relative_path, repo_ptr) = result?;
|
||||||
|
|
||||||
|
let lock = repo_ptr.lock();
|
||||||
|
lock.load_index_text(&repo_relative_path)
|
||||||
|
.map(|diff_base| (diff_base, buffer_handle))
|
||||||
|
});
|
||||||
|
|
||||||
|
let update_diff_base_fn = update_diff_base(self);
|
||||||
|
cx.spawn(|_, mut cx| async move {
|
||||||
|
let diff_base_tasks = cx
|
||||||
|
.background()
|
||||||
|
.spawn(future_buffers.collect::<Vec<_>>())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
for (diff_base, buffer) in diff_base_tasks.into_iter() {
|
||||||
|
update_diff_base_fn(Some(diff_base), buffer, &mut cx);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
// And the current buffers
|
||||||
for (_, buffer) in &self.opened_buffers {
|
for (_, buffer) in &self.opened_buffers {
|
||||||
if let Some(buffer) = buffer.upgrade(cx) {
|
if let Some(buffer) = buffer.upgrade(cx) {
|
||||||
let file = match File::from_dyn(buffer.read(cx).file()) {
|
let file = match File::from_dyn(buffer.read(cx).file()) {
|
||||||
|
@ -4824,18 +4881,17 @@ impl Project {
|
||||||
.find(|(work_directory, _)| path.starts_with(work_directory))
|
.find(|(work_directory, _)| path.starts_with(work_directory))
|
||||||
{
|
{
|
||||||
Some(repo) => repo.clone(),
|
Some(repo) => repo.clone(),
|
||||||
None => return,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let relative_repo = match path.strip_prefix(work_directory).log_err() {
|
let relative_repo = match path.strip_prefix(work_directory).log_err() {
|
||||||
Some(relative_repo) => relative_repo.to_owned(),
|
Some(relative_repo) => relative_repo.to_owned(),
|
||||||
None => return,
|
None => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
drop(worktree);
|
drop(worktree);
|
||||||
|
|
||||||
let remote_id = self.remote_id();
|
let update_diff_base_fn = update_diff_base(self);
|
||||||
let client = self.client.clone();
|
|
||||||
let git_ptr = repo.repo_ptr.clone();
|
let git_ptr = repo.repo_ptr.clone();
|
||||||
let diff_base_task = cx
|
let diff_base_task = cx
|
||||||
.background()
|
.background()
|
||||||
|
@ -4843,21 +4899,7 @@ impl Project {
|
||||||
|
|
||||||
cx.spawn(|_, mut cx| async move {
|
cx.spawn(|_, mut cx| async move {
|
||||||
let diff_base = diff_base_task.await;
|
let diff_base = diff_base_task.await;
|
||||||
|
update_diff_base_fn(diff_base, buffer, &mut cx);
|
||||||
let buffer_id = buffer.update(&mut cx, |buffer, cx| {
|
|
||||||
buffer.set_diff_base(diff_base.clone(), cx);
|
|
||||||
buffer.remote_id()
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(project_id) = remote_id {
|
|
||||||
client
|
|
||||||
.send(proto::UpdateDiffBase {
|
|
||||||
project_id,
|
|
||||||
buffer_id: buffer_id as u64,
|
|
||||||
diff_base,
|
|
||||||
})
|
|
||||||
.log_err();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -6747,3 +6789,40 @@ impl Item for Buffer {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn pump_loading_buffer_reciever(
|
||||||
|
mut receiver: postage::watch::Receiver<Option<Result<ModelHandle<Buffer>, Arc<anyhow::Error>>>>,
|
||||||
|
) -> Result<ModelHandle<Buffer>, Arc<anyhow::Error>> {
|
||||||
|
loop {
|
||||||
|
if let Some(result) = receiver.borrow().as_ref() {
|
||||||
|
match result {
|
||||||
|
Ok(buffer) => return Ok(buffer.to_owned()),
|
||||||
|
Err(e) => return Err(e.to_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
receiver.next().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_diff_base(
|
||||||
|
project: &Project,
|
||||||
|
) -> impl Fn(Option<String>, ModelHandle<Buffer>, &mut AsyncAppContext) {
|
||||||
|
let remote_id = project.remote_id();
|
||||||
|
let client = project.client().clone();
|
||||||
|
move |diff_base, buffer, cx| {
|
||||||
|
let buffer_id = buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.set_diff_base(diff_base.clone(), cx);
|
||||||
|
buffer.remote_id()
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(project_id) = remote_id {
|
||||||
|
client
|
||||||
|
.send(proto::UpdateDiffBase {
|
||||||
|
project_id,
|
||||||
|
buffer_id: buffer_id as u64,
|
||||||
|
diff_base,
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use settings::Setting;
|
use settings::Setting;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct ProjectSettings {
|
pub struct ProjectSettings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub lsp: HashMap<Arc<str>, LspSettings>,
|
pub lsp: HashMap<Arc<str>, LspSettings>,
|
||||||
|
|
|
@ -926,6 +926,95 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_restarting_server_with_diagnostics_published(cx: &mut gpui::TestAppContext) {
|
||||||
|
init_test(cx);
|
||||||
|
|
||||||
|
let mut language = Language::new(
|
||||||
|
LanguageConfig {
|
||||||
|
path_suffixes: vec!["rs".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let mut fake_servers = language
|
||||||
|
.set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
|
||||||
|
..Default::default()
|
||||||
|
}))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let fs = FakeFs::new(cx.background());
|
||||||
|
fs.insert_tree("/dir", json!({ "a.rs": "x" })).await;
|
||||||
|
|
||||||
|
let project = Project::test(fs, ["/dir".as_ref()], cx).await;
|
||||||
|
project.update(cx, |project, _| project.languages.add(Arc::new(language)));
|
||||||
|
|
||||||
|
let buffer = project
|
||||||
|
.update(cx, |project, cx| project.open_local_buffer("/dir/a.rs", cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Publish diagnostics
|
||||||
|
let fake_server = fake_servers.next().await.unwrap();
|
||||||
|
fake_server.notify::<lsp::notification::PublishDiagnostics>(lsp::PublishDiagnosticsParams {
|
||||||
|
uri: Url::from_file_path("/dir/a.rs").unwrap(),
|
||||||
|
version: None,
|
||||||
|
diagnostics: vec![lsp::Diagnostic {
|
||||||
|
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
|
||||||
|
severity: Some(lsp::DiagnosticSeverity::ERROR),
|
||||||
|
message: "the message".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
buffer.read_with(cx, |buffer, _| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer
|
||||||
|
.snapshot()
|
||||||
|
.diagnostics_in_range::<_, usize>(0..1, false)
|
||||||
|
.map(|entry| entry.diagnostic.message.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
["the message".to_string()]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
project.read_with(cx, |project, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
project.diagnostic_summary(cx),
|
||||||
|
DiagnosticSummary {
|
||||||
|
error_count: 1,
|
||||||
|
warning_count: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
project.update(cx, |project, cx| {
|
||||||
|
project.restart_language_servers_for_buffers([buffer.clone()], cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The diagnostics are cleared.
|
||||||
|
cx.foreground().run_until_parked();
|
||||||
|
buffer.read_with(cx, |buffer, _| {
|
||||||
|
assert_eq!(
|
||||||
|
buffer
|
||||||
|
.snapshot()
|
||||||
|
.diagnostics_in_range::<_, usize>(0..1, false)
|
||||||
|
.map(|entry| entry.diagnostic.message.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
Vec::<String>::new(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
project.read_with(cx, |project, cx| {
|
||||||
|
assert_eq!(
|
||||||
|
project.diagnostic_summary(cx),
|
||||||
|
DiagnosticSummary {
|
||||||
|
error_count: 0,
|
||||||
|
warning_count: 0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
|
async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
|
||||||
init_test(cx);
|
init_test(cx);
|
||||||
|
|
|
@ -329,7 +329,7 @@ pub struct LocalMutableSnapshot {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LocalRepositoryEntry {
|
pub struct LocalRepositoryEntry {
|
||||||
pub(crate) scan_id: usize,
|
pub(crate) scan_id: usize,
|
||||||
pub(crate) full_scan_id: usize,
|
pub(crate) git_dir_scan_id: usize,
|
||||||
pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
|
pub(crate) repo_ptr: Arc<Mutex<dyn GitRepository>>,
|
||||||
/// Path to the actual .git folder.
|
/// Path to the actual .git folder.
|
||||||
/// Note: if .git is a file, this points to the folder indicated by the .git file
|
/// Note: if .git is a file, this points to the folder indicated by the .git file
|
||||||
|
@ -737,6 +737,45 @@ impl LocalWorktree {
|
||||||
self.diagnostics.get(path).cloned().unwrap_or_default()
|
self.diagnostics.get(path).cloned().unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear_diagnostics_for_language_server(
|
||||||
|
&mut self,
|
||||||
|
server_id: LanguageServerId,
|
||||||
|
_: &mut ModelContext<Worktree>,
|
||||||
|
) {
|
||||||
|
let worktree_id = self.id().to_proto();
|
||||||
|
self.diagnostic_summaries
|
||||||
|
.retain(|path, summaries_by_server_id| {
|
||||||
|
if summaries_by_server_id.remove(&server_id).is_some() {
|
||||||
|
if let Some(share) = self.share.as_ref() {
|
||||||
|
self.client
|
||||||
|
.send(proto::UpdateDiagnosticSummary {
|
||||||
|
project_id: share.project_id,
|
||||||
|
worktree_id,
|
||||||
|
summary: Some(proto::DiagnosticSummary {
|
||||||
|
path: path.to_string_lossy().to_string(),
|
||||||
|
language_server_id: server_id.0 as u64,
|
||||||
|
error_count: 0,
|
||||||
|
warning_count: 0,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
!summaries_by_server_id.is_empty()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.diagnostics.retain(|_, diagnostics_by_server_id| {
|
||||||
|
if let Ok(ix) = diagnostics_by_server_id.binary_search_by_key(&server_id, |e| e.0) {
|
||||||
|
diagnostics_by_server_id.remove(ix);
|
||||||
|
!diagnostics_by_server_id.is_empty()
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update_diagnostics(
|
pub fn update_diagnostics(
|
||||||
&mut self,
|
&mut self,
|
||||||
server_id: LanguageServerId,
|
server_id: LanguageServerId,
|
||||||
|
@ -800,6 +839,7 @@ impl LocalWorktree {
|
||||||
fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
|
fn set_snapshot(&mut self, new_snapshot: LocalSnapshot, cx: &mut ModelContext<Worktree>) {
|
||||||
let updated_repos =
|
let updated_repos =
|
||||||
self.changed_repos(&self.git_repositories, &new_snapshot.git_repositories);
|
self.changed_repos(&self.git_repositories, &new_snapshot.git_repositories);
|
||||||
|
|
||||||
self.snapshot = new_snapshot;
|
self.snapshot = new_snapshot;
|
||||||
|
|
||||||
if let Some(share) = self.share.as_mut() {
|
if let Some(share) = self.share.as_mut() {
|
||||||
|
@ -830,7 +870,7 @@ impl LocalWorktree {
|
||||||
old_repos.next();
|
old_repos.next();
|
||||||
}
|
}
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
if old_repo.scan_id != new_repo.scan_id {
|
if old_repo.git_dir_scan_id != new_repo.git_dir_scan_id {
|
||||||
if let Some(entry) = self.entry_for_id(**new_entry_id) {
|
if let Some(entry) = self.entry_for_id(**new_entry_id) {
|
||||||
diff.insert(entry.path.clone(), (*new_repo).clone());
|
diff.insert(entry.path.clone(), (*new_repo).clone());
|
||||||
}
|
}
|
||||||
|
@ -2006,7 +2046,7 @@ impl LocalSnapshot {
|
||||||
work_dir_id,
|
work_dir_id,
|
||||||
LocalRepositoryEntry {
|
LocalRepositoryEntry {
|
||||||
scan_id,
|
scan_id,
|
||||||
full_scan_id: scan_id,
|
git_dir_scan_id: scan_id,
|
||||||
repo_ptr: repo,
|
repo_ptr: repo,
|
||||||
git_dir_path: parent_path.clone(),
|
git_dir_path: parent_path.clone(),
|
||||||
},
|
},
|
||||||
|
@ -3166,7 +3206,7 @@ impl BackgroundScanner {
|
||||||
snapshot.build_repo(dot_git_dir.into(), fs);
|
snapshot.build_repo(dot_git_dir.into(), fs);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if repo.full_scan_id == scan_id {
|
if repo.git_dir_scan_id == scan_id {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
(*entry_id, repo.repo_ptr.to_owned())
|
(*entry_id, repo.repo_ptr.to_owned())
|
||||||
|
@ -3183,7 +3223,7 @@ impl BackgroundScanner {
|
||||||
|
|
||||||
snapshot.git_repositories.update(&entry_id, |entry| {
|
snapshot.git_repositories.update(&entry_id, |entry| {
|
||||||
entry.scan_id = scan_id;
|
entry.scan_id = scan_id;
|
||||||
entry.full_scan_id = scan_id;
|
entry.git_dir_scan_id = scan_id;
|
||||||
});
|
});
|
||||||
|
|
||||||
snapshot.repository_entries.update(&work_dir, |entry| {
|
snapshot.repository_entries.update(&work_dir, |entry| {
|
||||||
|
@ -3212,7 +3252,7 @@ impl BackgroundScanner {
|
||||||
let local_repo = snapshot.get_local_repo(&repo)?.to_owned();
|
let local_repo = snapshot.get_local_repo(&repo)?.to_owned();
|
||||||
|
|
||||||
// Short circuit if we've already scanned everything
|
// Short circuit if we've already scanned everything
|
||||||
if local_repo.full_scan_id == scan_id {
|
if local_repo.git_dir_scan_id == scan_id {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,11 @@ util = { path = "../util" }
|
||||||
workspace = { path = "../workspace" }
|
workspace = { path = "../workspace" }
|
||||||
postage.workspace = true
|
postage.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
schemars.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
serde_derive.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
anyhow.workspace = true
|
||||||
|
schemars.workspace = true
|
||||||
unicase = "2.6"
|
unicase = "2.6"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod project_panel_settings;
|
||||||
|
|
||||||
use context_menu::{ContextMenu, ContextMenuItem};
|
use context_menu::{ContextMenu, ContextMenuItem};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use drag_and_drop::{DragAndDrop, Draggable};
|
use drag_and_drop::{DragAndDrop, Draggable};
|
||||||
|
@ -7,7 +9,7 @@ use gpui::{
|
||||||
actions,
|
actions,
|
||||||
anyhow::{self, anyhow, Result},
|
anyhow::{self, anyhow, Result},
|
||||||
elements::{
|
elements::{
|
||||||
AnchorCorner, ChildView, ComponentHost, ContainerStyle, Empty, Flex, MouseEventHandler,
|
AnchorCorner, ChildView, ContainerStyle, Empty, Flex, Label, MouseEventHandler,
|
||||||
ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
|
ParentElement, ScrollTarget, Stack, Svg, UniformList, UniformListState,
|
||||||
},
|
},
|
||||||
geometry::vector::Vector2F,
|
geometry::vector::Vector2F,
|
||||||
|
@ -21,7 +23,7 @@ use project::{
|
||||||
repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath,
|
repository::GitFileStatus, Entry, EntryKind, Fs, Project, ProjectEntryId, ProjectPath,
|
||||||
Worktree, WorktreeId,
|
Worktree, WorktreeId,
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
use project_panel_settings::{ProjectPanelDockPosition, ProjectPanelSettings};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::SettingsStore;
|
use settings::SettingsStore;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -32,7 +34,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use theme::{ui::FileName, ProjectPanelEntry};
|
use theme::ProjectPanelEntry;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
use util::{ResultExt, TryFutureExt};
|
use util::{ResultExt, TryFutureExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -43,39 +45,6 @@ use workspace::{
|
||||||
const PROJECT_PANEL_KEY: &'static str = "ProjectPanel";
|
const PROJECT_PANEL_KEY: &'static str = "ProjectPanel";
|
||||||
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
pub struct ProjectPanelSettings {
|
|
||||||
dock: ProjectPanelDockPosition,
|
|
||||||
default_width: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl settings::Setting for ProjectPanelSettings {
|
|
||||||
const KEY: Option<&'static str> = Some("project_panel");
|
|
||||||
|
|
||||||
type FileContent = ProjectPanelSettingsContent;
|
|
||||||
|
|
||||||
fn load(
|
|
||||||
default_value: &Self::FileContent,
|
|
||||||
user_values: &[&Self::FileContent],
|
|
||||||
_: &AppContext,
|
|
||||||
) -> Result<Self> {
|
|
||||||
Self::load_via_json_merge(default_value, user_values)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
|
||||||
pub struct ProjectPanelSettingsContent {
|
|
||||||
dock: Option<ProjectPanelDockPosition>,
|
|
||||||
default_width: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum ProjectPanelDockPosition {
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProjectPanel {
|
pub struct ProjectPanel {
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
fs: Arc<dyn Fs>,
|
fs: Arc<dyn Fs>,
|
||||||
|
@ -156,8 +125,12 @@ actions!(
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init_settings(cx: &mut AppContext) {
|
||||||
settings::register::<ProjectPanelSettings>(cx);
|
settings::register::<ProjectPanelSettings>(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
init_settings(cx);
|
||||||
cx.add_action(ProjectPanel::expand_selected_entry);
|
cx.add_action(ProjectPanel::expand_selected_entry);
|
||||||
cx.add_action(ProjectPanel::collapse_selected_entry);
|
cx.add_action(ProjectPanel::collapse_selected_entry);
|
||||||
cx.add_action(ProjectPanel::select_prev);
|
cx.add_action(ProjectPanel::select_prev);
|
||||||
|
@ -1116,6 +1089,7 @@ impl ProjectPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
let end_ix = range.end.min(ix + visible_worktree_entries.len());
|
let end_ix = range.end.min(ix + visible_worktree_entries.len());
|
||||||
|
let git_status_setting = settings::get::<ProjectPanelSettings>(cx).git_status;
|
||||||
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
|
if let Some(worktree) = self.project.read(cx).worktree_for_id(*worktree_id, cx) {
|
||||||
let snapshot = worktree.read(cx).snapshot();
|
let snapshot = worktree.read(cx).snapshot();
|
||||||
let root_name = OsStr::new(snapshot.root_name());
|
let root_name = OsStr::new(snapshot.root_name());
|
||||||
|
@ -1129,7 +1103,9 @@ impl ProjectPanel {
|
||||||
for (entry, repo) in
|
for (entry, repo) in
|
||||||
snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter())
|
snapshot.entries_with_repositories(visible_worktree_entries[entry_range].iter())
|
||||||
{
|
{
|
||||||
let status = (entry.path.parent().is_some() && !entry.is_ignored)
|
let status = (git_status_setting
|
||||||
|
&& entry.path.parent().is_some()
|
||||||
|
&& !entry.is_ignored)
|
||||||
.then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path)))
|
.then(|| repo.and_then(|repo| repo.status_for_path(&snapshot, &entry.path)))
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
|
@ -1195,6 +1171,17 @@ impl ProjectPanel {
|
||||||
let kind = details.kind;
|
let kind = details.kind;
|
||||||
let show_editor = details.is_editing && !details.is_processing;
|
let show_editor = details.is_editing && !details.is_processing;
|
||||||
|
|
||||||
|
let mut filename_text_style = style.text.clone();
|
||||||
|
filename_text_style.color = details
|
||||||
|
.git_status
|
||||||
|
.as_ref()
|
||||||
|
.map(|status| match status {
|
||||||
|
GitFileStatus::Added => style.status.git.inserted,
|
||||||
|
GitFileStatus::Modified => style.status.git.modified,
|
||||||
|
GitFileStatus::Conflict => style.status.git.conflict,
|
||||||
|
})
|
||||||
|
.unwrap_or(style.text.color);
|
||||||
|
|
||||||
Flex::row()
|
Flex::row()
|
||||||
.with_child(
|
.with_child(
|
||||||
if kind == EntryKind::Dir {
|
if kind == EntryKind::Dir {
|
||||||
|
@ -1222,16 +1209,12 @@ impl ProjectPanel {
|
||||||
.flex(1.0, true)
|
.flex(1.0, true)
|
||||||
.into_any()
|
.into_any()
|
||||||
} else {
|
} else {
|
||||||
ComponentHost::new(FileName::new(
|
Label::new(details.filename.clone(), filename_text_style)
|
||||||
details.filename.clone(),
|
.contained()
|
||||||
details.git_status,
|
.with_margin_left(style.icon_spacing)
|
||||||
FileName::style(style.text.clone(), &theme::current(cx)),
|
.aligned()
|
||||||
))
|
.left()
|
||||||
.contained()
|
.into_any()
|
||||||
.with_margin_left(style.icon_spacing)
|
|
||||||
.aligned()
|
|
||||||
.left()
|
|
||||||
.into_any()
|
|
||||||
})
|
})
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(style.height)
|
.with_height(style.height)
|
||||||
|
@ -2240,6 +2223,7 @@ mod tests {
|
||||||
cx.foreground().forbid_parking();
|
cx.foreground().forbid_parking();
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.set_global(SettingsStore::test(cx));
|
cx.set_global(SettingsStore::test(cx));
|
||||||
|
init_settings(cx);
|
||||||
theme::init((), cx);
|
theme::init((), cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init_settings(cx);
|
editor::init_settings(cx);
|
||||||
|
@ -2253,6 +2237,7 @@ mod tests {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
let app_state = AppState::test(cx);
|
let app_state = AppState::test(cx);
|
||||||
theme::init((), cx);
|
theme::init((), cx);
|
||||||
|
init_settings(cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
pane::init(cx);
|
pane::init(cx);
|
||||||
|
|
39
crates/project_panel/src/project_panel_settings.rs
Normal file
39
crates/project_panel/src/project_panel_settings.rs
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
use anyhow;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use settings::Setting;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ProjectPanelDockPosition {
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct ProjectPanelSettings {
|
||||||
|
pub git_status: bool,
|
||||||
|
pub dock: ProjectPanelDockPosition,
|
||||||
|
pub default_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
|
pub struct ProjectPanelSettingsContent {
|
||||||
|
pub git_status: Option<bool>,
|
||||||
|
pub dock: Option<ProjectPanelDockPosition>,
|
||||||
|
pub default_width: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Setting for ProjectPanelSettings {
|
||||||
|
const KEY: Option<&'static str> = Some("project_panel");
|
||||||
|
|
||||||
|
type FileContent = ProjectPanelSettingsContent;
|
||||||
|
|
||||||
|
fn load(
|
||||||
|
default_value: &Self::FileContent,
|
||||||
|
user_values: &[&Self::FileContent],
|
||||||
|
_: &gpui::AppContext,
|
||||||
|
) -> anyhow::Result<Self> {
|
||||||
|
Self::load_via_json_merge(default_value, user_values)
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ pub fn init(cx: &mut AppContext) {
|
||||||
cx.add_action(ProjectSearchBar::search_in_new);
|
cx.add_action(ProjectSearchBar::search_in_new);
|
||||||
cx.add_action(ProjectSearchBar::select_next_match);
|
cx.add_action(ProjectSearchBar::select_next_match);
|
||||||
cx.add_action(ProjectSearchBar::select_prev_match);
|
cx.add_action(ProjectSearchBar::select_prev_match);
|
||||||
cx.add_action(ProjectSearchBar::toggle_focus);
|
cx.add_action(ProjectSearchBar::move_focus_to_results);
|
||||||
cx.capture_action(ProjectSearchBar::tab);
|
cx.capture_action(ProjectSearchBar::tab);
|
||||||
cx.capture_action(ProjectSearchBar::tab_previous);
|
cx.capture_action(ProjectSearchBar::tab_previous);
|
||||||
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
|
add_toggle_option_action::<ToggleCaseSensitive>(SearchOption::CaseSensitive, cx);
|
||||||
|
@ -794,18 +794,16 @@ impl ProjectSearchBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_focus(pane: &mut Pane, _: &ToggleFocus, cx: &mut ViewContext<Pane>) {
|
fn move_focus_to_results(pane: &mut Pane, _: &ToggleFocus, cx: &mut ViewContext<Pane>) {
|
||||||
if let Some(search_view) = pane
|
if let Some(search_view) = pane
|
||||||
.active_item()
|
.active_item()
|
||||||
.and_then(|item| item.downcast::<ProjectSearchView>())
|
.and_then(|item| item.downcast::<ProjectSearchView>())
|
||||||
{
|
{
|
||||||
search_view.update(cx, |search_view, cx| {
|
search_view.update(cx, |search_view, cx| {
|
||||||
if search_view.query_editor.is_focused(cx) {
|
if search_view.query_editor.is_focused(cx)
|
||||||
if !search_view.model.read(cx).match_ranges.is_empty() {
|
&& !search_view.model.read(cx).match_ranges.is_empty()
|
||||||
search_view.focus_results_editor(cx);
|
{
|
||||||
}
|
search_view.focus_results_editor(cx);
|
||||||
} else {
|
|
||||||
search_view.focus_query_editor(cx);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub trait Setting: 'static {
|
||||||
const KEY: Option<&'static str>;
|
const KEY: Option<&'static str>;
|
||||||
|
|
||||||
/// The type that is stored in an individual JSON file.
|
/// The type that is stored in an individual JSON file.
|
||||||
type FileContent: Clone + Serialize + DeserializeOwned + JsonSchema;
|
type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema;
|
||||||
|
|
||||||
/// The logic for combining together values from one or more JSON files into the
|
/// The logic for combining together values from one or more JSON files into the
|
||||||
/// final value for this setting.
|
/// final value for this setting.
|
||||||
|
@ -460,11 +460,12 @@ impl SettingsStore {
|
||||||
|
|
||||||
// If the global settings file changed, reload the global value for the field.
|
// If the global settings file changed, reload the global value for the field.
|
||||||
if changed_local_path.is_none() {
|
if changed_local_path.is_none() {
|
||||||
setting_value.set_global_value(setting_value.load_setting(
|
if let Some(value) = setting_value
|
||||||
&default_settings,
|
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||||
&user_settings_stack,
|
.log_err()
|
||||||
cx,
|
{
|
||||||
)?);
|
setting_value.set_global_value(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the local values for the setting.
|
// Reload the local values for the setting.
|
||||||
|
@ -495,14 +496,12 @@ impl SettingsStore {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
setting_value.set_local_value(
|
if let Some(value) = setting_value
|
||||||
path.clone(),
|
.load_setting(&default_settings, &user_settings_stack, cx)
|
||||||
setting_value.load_setting(
|
.log_err()
|
||||||
&default_settings,
|
{
|
||||||
&user_settings_stack,
|
setting_value.set_local_value(path.clone(), value);
|
||||||
cx,
|
}
|
||||||
)?,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -536,7 +535,12 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
|
||||||
|
|
||||||
fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
|
fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
|
||||||
if let Some(key) = T::KEY {
|
if let Some(key) = T::KEY {
|
||||||
json = json.get(key).unwrap_or(&serde_json::Value::Null);
|
if let Some(value) = json.get(key) {
|
||||||
|
json = value;
|
||||||
|
} else {
|
||||||
|
let value = T::FileContent::default();
|
||||||
|
return Ok(DeserializedSetting(Box::new(value)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let value = T::FileContent::deserialize(json)?;
|
let value = T::FileContent::deserialize(json)?;
|
||||||
Ok(DeserializedSetting(Box::new(value)))
|
Ok(DeserializedSetting(Box::new(value)))
|
||||||
|
@ -826,37 +830,6 @@ mod tests {
|
||||||
store.register_setting::<UserSettings>(cx);
|
store.register_setting::<UserSettings>(cx);
|
||||||
store.register_setting::<TurboSetting>(cx);
|
store.register_setting::<TurboSetting>(cx);
|
||||||
store.register_setting::<MultiKeySettings>(cx);
|
store.register_setting::<MultiKeySettings>(cx);
|
||||||
|
|
||||||
// error - missing required field in default settings
|
|
||||||
store
|
|
||||||
.set_default_settings(
|
|
||||||
r#"{
|
|
||||||
"user": {
|
|
||||||
"name": "John Doe",
|
|
||||||
"age": 30,
|
|
||||||
"staff": false
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
// error - type error in default settings
|
|
||||||
store
|
|
||||||
.set_default_settings(
|
|
||||||
r#"{
|
|
||||||
"turbo": "the-wrong-type",
|
|
||||||
"user": {
|
|
||||||
"name": "John Doe",
|
|
||||||
"age": 30,
|
|
||||||
"staff": false
|
|
||||||
}
|
|
||||||
}"#,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
// valid default settings.
|
|
||||||
store
|
store
|
||||||
.set_default_settings(
|
.set_default_settings(
|
||||||
r#"{
|
r#"{
|
||||||
|
@ -1126,7 +1099,7 @@ mod tests {
|
||||||
staff: bool,
|
staff: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
|
||||||
struct UserSettingsJson {
|
struct UserSettingsJson {
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
age: Option<u32>,
|
age: Option<u32>,
|
||||||
|
@ -1170,7 +1143,7 @@ mod tests {
|
||||||
key2: String,
|
key2: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
struct MultiKeySettingsJson {
|
struct MultiKeySettingsJson {
|
||||||
key1: Option<String>,
|
key1: Option<String>,
|
||||||
key2: Option<String>,
|
key2: Option<String>,
|
||||||
|
@ -1203,7 +1176,7 @@ mod tests {
|
||||||
Hour24,
|
Hour24,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
struct JournalSettingsJson {
|
struct JournalSettingsJson {
|
||||||
pub path: Option<String>,
|
pub path: Option<String>,
|
||||||
pub hour_format: Option<HourFormat>,
|
pub hour_format: Option<HourFormat>,
|
||||||
|
@ -1223,7 +1196,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
struct LanguageSettings {
|
struct LanguageSettings {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
languages: HashMap<String, LanguageSettingEntry>,
|
languages: HashMap<String, LanguageSettingEntry>,
|
||||||
|
|
|
@ -438,6 +438,19 @@ pub struct ProjectPanelEntry {
|
||||||
pub icon_color: Color,
|
pub icon_color: Color,
|
||||||
pub icon_size: f32,
|
pub icon_size: f32,
|
||||||
pub icon_spacing: f32,
|
pub icon_spacing: f32,
|
||||||
|
pub status: EntryStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct EntryStatus {
|
||||||
|
pub git: GitProjectStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
pub struct GitProjectStatus {
|
||||||
|
pub modified: Color,
|
||||||
|
pub inserted: Color,
|
||||||
|
pub conflict: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Default)]
|
#[derive(Clone, Debug, Deserialize, Default)]
|
||||||
|
@ -662,6 +675,14 @@ pub struct Scrollbar {
|
||||||
pub thumb: ContainerStyle,
|
pub thumb: ContainerStyle,
|
||||||
pub width: f32,
|
pub width: f32,
|
||||||
pub min_height_factor: f32,
|
pub min_height_factor: f32,
|
||||||
|
pub git: GitDiffColors,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
pub struct GitDiffColors {
|
||||||
|
pub inserted: Color,
|
||||||
|
pub modified: Color,
|
||||||
|
pub deleted: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use fs::repository::GitFileStatus;
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
elements::{
|
elements::{
|
||||||
ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label, LabelStyle,
|
ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
|
||||||
MouseEventHandler, ParentElement, Stack, Svg,
|
MouseEventHandler, ParentElement, Stack, Svg,
|
||||||
},
|
},
|
||||||
fonts::TextStyle,
|
fonts::TextStyle,
|
||||||
|
@ -12,11 +11,11 @@ use gpui::{
|
||||||
platform,
|
platform,
|
||||||
platform::MouseButton,
|
platform::MouseButton,
|
||||||
scene::MouseClick,
|
scene::MouseClick,
|
||||||
Action, AnyElement, Element, EventContext, MouseState, View, ViewContext,
|
Action, Element, EventContext, MouseState, View, ViewContext,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{ContainedText, Interactive, Theme};
|
use crate::{ContainedText, Interactive};
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, Default)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
pub struct CheckboxStyle {
|
pub struct CheckboxStyle {
|
||||||
|
@ -253,53 +252,3 @@ where
|
||||||
.constrained()
|
.constrained()
|
||||||
.with_height(style.dimensions().y())
|
.with_height(style.dimensions().y())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FileName {
|
|
||||||
filename: String,
|
|
||||||
git_status: Option<GitFileStatus>,
|
|
||||||
style: FileNameStyle,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct FileNameStyle {
|
|
||||||
template_style: LabelStyle,
|
|
||||||
git_inserted: Color,
|
|
||||||
git_modified: Color,
|
|
||||||
git_deleted: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileName {
|
|
||||||
pub fn new(filename: String, git_status: Option<GitFileStatus>, style: FileNameStyle) -> Self {
|
|
||||||
FileName {
|
|
||||||
filename,
|
|
||||||
git_status,
|
|
||||||
style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style<I: Into<LabelStyle>>(style: I, theme: &Theme) -> FileNameStyle {
|
|
||||||
FileNameStyle {
|
|
||||||
template_style: style.into(),
|
|
||||||
git_inserted: theme.editor.diff.inserted,
|
|
||||||
git_modified: theme.editor.diff.modified,
|
|
||||||
git_deleted: theme.editor.diff.deleted,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<V: View> gpui::elements::Component<V> for FileName {
|
|
||||||
fn render(&self, _: &mut V, _: &mut ViewContext<V>) -> AnyElement<V> {
|
|
||||||
// Prepare colors for git statuses
|
|
||||||
let mut filename_text_style = self.style.template_style.text.clone();
|
|
||||||
filename_text_style.color = self
|
|
||||||
.git_status
|
|
||||||
.as_ref()
|
|
||||||
.map(|status| match status {
|
|
||||||
GitFileStatus::Added => self.style.git_inserted,
|
|
||||||
GitFileStatus::Modified => self.style.git_modified,
|
|
||||||
GitFileStatus::Conflict => self.style.git_deleted,
|
|
||||||
})
|
|
||||||
.unwrap_or(self.style.template_style.text.color);
|
|
||||||
|
|
||||||
Label::new(self.filename.clone(), filename_text_style).into_any()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub struct WorkspaceSettings {
|
||||||
pub git: GitSettings,
|
pub git: GitSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct WorkspaceSettingsContent {
|
pub struct WorkspaceSettingsContent {
|
||||||
pub active_pane_magnification: Option<f32>,
|
pub active_pane_magnification: Option<f32>,
|
||||||
pub confirm_quit: Option<bool>,
|
pub confirm_quit: Option<bool>,
|
||||||
|
|
|
@ -2087,6 +2087,7 @@ mod tests {
|
||||||
workspace::init(app_state.clone(), cx);
|
workspace::init(app_state.clone(), cx);
|
||||||
language::init(cx);
|
language::init(cx);
|
||||||
editor::init(cx);
|
editor::init(cx);
|
||||||
|
project_panel::init_settings(cx);
|
||||||
pane::init(cx);
|
pane::init(cx);
|
||||||
project_panel::init(cx);
|
project_panel::init(cx);
|
||||||
terminal_view::init(cx);
|
terminal_view::init(cx);
|
||||||
|
|
|
@ -69,9 +69,12 @@ async function main() {
|
||||||
let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1];
|
let releaseNotes = (pullRequest.body || "").split("Release Notes:")[1];
|
||||||
|
|
||||||
if (releaseNotes) {
|
if (releaseNotes) {
|
||||||
releaseNotes = releaseNotes.trim();
|
releaseNotes = releaseNotes.trim().split("\n")
|
||||||
console.log(" Release Notes:");
|
console.log(" Release Notes:");
|
||||||
console.log(` ${releaseNotes}`);
|
|
||||||
|
for (const line of releaseNotes) {
|
||||||
|
console.log(` ${line}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log()
|
console.log()
|
||||||
|
|
|
@ -6,6 +6,8 @@ import hoverPopover from "./hoverPopover"
|
||||||
import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax"
|
import { SyntaxHighlightStyle, buildSyntax } from "../themes/common/syntax"
|
||||||
|
|
||||||
export default function editor(colorScheme: ColorScheme) {
|
export default function editor(colorScheme: ColorScheme) {
|
||||||
|
const { isLight } = colorScheme
|
||||||
|
|
||||||
let layer = colorScheme.highest
|
let layer = colorScheme.highest
|
||||||
|
|
||||||
const autocompleteItem = {
|
const autocompleteItem = {
|
||||||
|
@ -97,12 +99,18 @@ export default function editor(colorScheme: ColorScheme) {
|
||||||
foldBackground: foreground(layer, "variant"),
|
foldBackground: foreground(layer, "variant"),
|
||||||
},
|
},
|
||||||
diff: {
|
diff: {
|
||||||
deleted: foreground(layer, "negative"),
|
deleted: isLight
|
||||||
modified: foreground(layer, "warning"),
|
? colorScheme.ramps.red(0.5).hex()
|
||||||
inserted: foreground(layer, "positive"),
|
: colorScheme.ramps.red(0.4).hex(),
|
||||||
|
modified: isLight
|
||||||
|
? colorScheme.ramps.yellow(0.3).hex()
|
||||||
|
: colorScheme.ramps.yellow(0.5).hex(),
|
||||||
|
inserted: isLight
|
||||||
|
? colorScheme.ramps.green(0.4).hex()
|
||||||
|
: colorScheme.ramps.green(0.5).hex(),
|
||||||
removedWidthEm: 0.275,
|
removedWidthEm: 0.275,
|
||||||
widthEm: 0.22,
|
widthEm: 0.15,
|
||||||
cornerRadius: 0.2,
|
cornerRadius: 0.05,
|
||||||
},
|
},
|
||||||
/** Highlights matching occurences of what is under the cursor
|
/** Highlights matching occurences of what is under the cursor
|
||||||
* as well as matched brackets
|
* as well as matched brackets
|
||||||
|
@ -234,12 +242,27 @@ export default function editor(colorScheme: ColorScheme) {
|
||||||
border: border(layer, "variant", { left: true }),
|
border: border(layer, "variant", { left: true }),
|
||||||
},
|
},
|
||||||
thumb: {
|
thumb: {
|
||||||
background: withOpacity(background(layer, "inverted"), 0.4),
|
background: withOpacity(background(layer, "inverted"), 0.3),
|
||||||
border: {
|
border: {
|
||||||
width: 1,
|
width: 1,
|
||||||
color: borderColor(layer, "variant"),
|
color: borderColor(layer, "variant"),
|
||||||
},
|
top: false,
|
||||||
|
right: true,
|
||||||
|
left: true,
|
||||||
|
bottom: false,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
git: {
|
||||||
|
deleted: isLight
|
||||||
|
? withOpacity(colorScheme.ramps.red(0.5).hex(), 0.8)
|
||||||
|
: withOpacity(colorScheme.ramps.red(0.4).hex(), 0.8),
|
||||||
|
modified: isLight
|
||||||
|
? withOpacity(colorScheme.ramps.yellow(0.5).hex(), 0.8)
|
||||||
|
: withOpacity(colorScheme.ramps.yellow(0.4).hex(), 0.8),
|
||||||
|
inserted: isLight
|
||||||
|
? withOpacity(colorScheme.ramps.green(0.5).hex(), 0.8)
|
||||||
|
: withOpacity(colorScheme.ramps.green(0.4).hex(), 0.8),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
compositionMark: {
|
compositionMark: {
|
||||||
underline: {
|
underline: {
|
||||||
|
|
|
@ -3,6 +3,8 @@ import { withOpacity } from "../utils/color"
|
||||||
import { background, border, foreground, text } from "./components"
|
import { background, border, foreground, text } from "./components"
|
||||||
|
|
||||||
export default function projectPanel(colorScheme: ColorScheme) {
|
export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
|
const { isLight } = colorScheme
|
||||||
|
|
||||||
let layer = colorScheme.middle
|
let layer = colorScheme.middle
|
||||||
|
|
||||||
let baseEntry = {
|
let baseEntry = {
|
||||||
|
@ -12,6 +14,20 @@ export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
iconSpacing: 8,
|
iconSpacing: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let status = {
|
||||||
|
git: {
|
||||||
|
modified: isLight
|
||||||
|
? colorScheme.ramps.yellow(0.6).hex()
|
||||||
|
: colorScheme.ramps.yellow(0.5).hex(),
|
||||||
|
inserted: isLight
|
||||||
|
? colorScheme.ramps.green(0.45).hex()
|
||||||
|
: colorScheme.ramps.green(0.5).hex(),
|
||||||
|
conflict: isLight
|
||||||
|
? colorScheme.ramps.red(0.6).hex()
|
||||||
|
: colorScheme.ramps.red(0.5).hex()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let entry = {
|
let entry = {
|
||||||
...baseEntry,
|
...baseEntry,
|
||||||
text: text(layer, "mono", "variant", { size: "sm" }),
|
text: text(layer, "mono", "variant", { size: "sm" }),
|
||||||
|
@ -28,6 +44,7 @@ export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
background: background(layer, "active"),
|
background: background(layer, "active"),
|
||||||
text: text(layer, "mono", "active", { size: "sm" }),
|
text: text(layer, "mono", "active", { size: "sm" }),
|
||||||
},
|
},
|
||||||
|
status
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -62,6 +79,7 @@ export default function projectPanel(colorScheme: ColorScheme) {
|
||||||
text: text(layer, "mono", "on", { size: "sm" }),
|
text: text(layer, "mono", "on", { size: "sm" }),
|
||||||
background: withOpacity(background(layer, "on"), 0.9),
|
background: withOpacity(background(layer, "on"), 0.9),
|
||||||
border: border(layer),
|
border: border(layer),
|
||||||
|
status
|
||||||
},
|
},
|
||||||
ignoredEntry: {
|
ignoredEntry: {
|
||||||
...entry,
|
...entry,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue