Compare commits
1 Commits
shadow-imp
...
jump-into-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2673bb667 |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -94,10 +94,6 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
|
||||
|
||||
# To support writing comments that they will certainly be revisited.
|
||||
- name: Check for todo! and FIXME comments
|
||||
run: script/check-todos
|
||||
|
||||
- name: Run style checks
|
||||
uses: ./.github/actions/check_style
|
||||
|
||||
|
||||
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -83,9 +83,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alacritty_terminal"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b9241459831fee2f22fcda52ddaf9e449b6627334a0f40f13a1b3344018060"
|
||||
version = "0.24.1-dev"
|
||||
source = "git+https://github.com/alacritty/alacritty?rev=91d034ff8b53867143c005acfaa14609147c9a2c#91d034ff8b53867143c005acfaa14609147c9a2c"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.6.0",
|
||||
@@ -494,7 +493,6 @@ dependencies = [
|
||||
"project",
|
||||
"proto",
|
||||
"rand 0.8.5",
|
||||
"release_channel",
|
||||
"rope",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -3682,7 +3680,6 @@ dependencies = [
|
||||
"ctor",
|
||||
"editor",
|
||||
"env_logger 0.11.5",
|
||||
"feature_flags",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
@@ -6985,7 +6982,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7698,7 +7695,6 @@ dependencies = [
|
||||
"sum_tree",
|
||||
"text",
|
||||
"theme",
|
||||
"tree-sitter",
|
||||
"util",
|
||||
]
|
||||
|
||||
@@ -13979,7 +13975,6 @@ dependencies = [
|
||||
"futures-lite 1.13.0",
|
||||
"git2",
|
||||
"globset",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
@@ -14984,7 +14979,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -337,7 +337,7 @@ zeta = { path = "crates/zeta" }
|
||||
#
|
||||
|
||||
aho-corasick = "1.1"
|
||||
alacritty_terminal = "0.24"
|
||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "91d034ff8b53867143c005acfaa14609147c9a2c" }
|
||||
any_vec = "0.14"
|
||||
anyhow = "1.0.86"
|
||||
arrayvec = { version = "0.7.4", features = ["serde"] }
|
||||
|
||||
@@ -50,7 +50,6 @@ parking_lot.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
release_channel.workspace = true
|
||||
rope.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
@@ -205,10 +205,10 @@ impl ActiveThread {
|
||||
|
||||
let context = self.thread.read(cx).context_for_message(message_id);
|
||||
|
||||
let (role_icon, role_name, role_color) = match message.role {
|
||||
Role::User => (IconName::Person, "You", Color::Muted),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant", Color::Accent),
|
||||
Role::System => (IconName::Settings, "System", Color::Default),
|
||||
let (role_icon, role_name) = match message.role {
|
||||
Role::User => (IconName::Person, "You"),
|
||||
Role::Assistant => (IconName::ZedAssistant, "Assistant"),
|
||||
Role::System => (IconName::Settings, "System"),
|
||||
};
|
||||
|
||||
div()
|
||||
@@ -234,13 +234,9 @@ impl ActiveThread {
|
||||
.child(
|
||||
Icon::new(role_icon)
|
||||
.size(IconSize::XSmall)
|
||||
.color(role_color),
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.child(
|
||||
Label::new(role_name)
|
||||
.size(LabelSize::XSmall)
|
||||
.color(role_color),
|
||||
),
|
||||
.child(Label::new(role_name).size(LabelSize::XSmall)),
|
||||
),
|
||||
)
|
||||
.child(v_flex().px_2().py_1().text_ui(cx).child(markdown.clone()))
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
mod active_thread;
|
||||
mod assistant_model_selector;
|
||||
mod assistant_panel;
|
||||
mod assistant_settings;
|
||||
mod buffer_codegen;
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
use fs::Fs;
|
||||
use gpui::View;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use settings::update_settings_file;
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, ButtonLike, PopoverMenuHandle, Tooltip};
|
||||
|
||||
use crate::{assistant_settings::AssistantSettings, ToggleModelSelector};
|
||||
|
||||
pub struct AssistantModelSelector {
|
||||
selector: View<LanguageModelSelector>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
}
|
||||
|
||||
impl AssistantModelSelector {
|
||||
pub(crate) fn new(
|
||||
fs: Arc<dyn Fs>,
|
||||
menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
menu_handle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AssistantModelSelector {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.selector.focus_handle(cx).clone();
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(match active_model {
|
||||
Some(model) => h_flex()
|
||||
.child(
|
||||
Label::new(model.name().0)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
_ => Label::new("No model selected")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.menu_handle.clone())
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ use gpui::{
|
||||
WeakModel, WeakView,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use release_channel::ReleaseChannel;
|
||||
use ui::{prelude::*, ListItem, ListItemSpacing};
|
||||
use util::ResultExt;
|
||||
use workspace::Workspace;
|
||||
@@ -51,27 +50,23 @@ impl ContextPicker {
|
||||
confirm_behavior: ConfirmBehavior,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let mut entries = Vec::new();
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
icon: IconName::File,
|
||||
});
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
// The directory context picker isn't fully implemented yet, so limit it
|
||||
// to development builds.
|
||||
if release_channel == ReleaseChannel::Dev {
|
||||
entries.push(ContextPickerEntry {
|
||||
let mut entries = vec![
|
||||
ContextPickerEntry {
|
||||
name: "File".into(),
|
||||
kind: ContextKind::File,
|
||||
icon: IconName::File,
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "Folder".into(),
|
||||
kind: ContextKind::Directory,
|
||||
icon: IconName::Folder,
|
||||
});
|
||||
}
|
||||
entries.push(ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
});
|
||||
},
|
||||
ContextPickerEntry {
|
||||
name: "Fetch".into(),
|
||||
kind: ContextKind::FetchedUrl,
|
||||
icon: IconName::Globe,
|
||||
},
|
||||
];
|
||||
|
||||
if thread_store.is_some() {
|
||||
entries.push(ContextPickerEntry {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// TODO: Remove this when we finish the implementation.
|
||||
// TODO: Remove this once we've implemented the functionality.
|
||||
#![allow(unused)]
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
use fuzzy::PathMatch;
|
||||
@@ -13,7 +11,6 @@ use ui::{prelude::*, ListItem};
|
||||
use util::ResultExt as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::context::ContextKind;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
|
||||
@@ -78,65 +75,6 @@ impl DirectoryContextPickerDelegate {
|
||||
selected_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn search(
|
||||
&mut self,
|
||||
query: String,
|
||||
cancellation_flag: Arc<AtomicBool>,
|
||||
workspace: &View<Workspace>,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Task<Vec<PathMatch>> {
|
||||
if query.is_empty() {
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().read(cx);
|
||||
let directory_matches = project.worktrees(cx).flat_map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
let path_prefix: Arc<str> = worktree.root_name().into();
|
||||
worktree.directories(false, 0).map(move |entry| PathMatch {
|
||||
score: 0.,
|
||||
positions: Vec::new(),
|
||||
worktree_id: worktree.id().to_usize(),
|
||||
path: entry.path.clone(),
|
||||
path_prefix: path_prefix.clone(),
|
||||
distance_to_relative_ancestor: 0,
|
||||
is_dir: true,
|
||||
})
|
||||
});
|
||||
|
||||
Task::ready(directory_matches.collect())
|
||||
} else {
|
||||
let worktrees = workspace.read(cx).visible_worktrees(cx).collect::<Vec<_>>();
|
||||
let candidate_sets = worktrees
|
||||
.into_iter()
|
||||
.map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
|
||||
PathMatchCandidateSet {
|
||||
snapshot: worktree.snapshot(),
|
||||
include_ignored: worktree
|
||||
.root_entry()
|
||||
.map_or(false, |entry| entry.is_ignored),
|
||||
include_root_name: true,
|
||||
candidates: project::Candidates::Directories,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let executor = cx.background_executor().clone();
|
||||
cx.foreground_executor().spawn(async move {
|
||||
fuzzy::match_path_sets(
|
||||
candidate_sets.as_slice(),
|
||||
query.as_str(),
|
||||
None,
|
||||
false,
|
||||
100,
|
||||
&cancellation_flag,
|
||||
executor,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
@@ -150,7 +88,7 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
@@ -158,65 +96,17 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
"Search folders…".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return Task::ready(());
|
||||
};
|
||||
|
||||
let search_task = self.search(query, Arc::<AtomicBool>::default(), &workspace, cx);
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let mut paths = search_task.await;
|
||||
let empty_path = Path::new("");
|
||||
paths.retain(|path_match| path_match.path.as_ref() != empty_path);
|
||||
|
||||
this.update(&mut cx, |this, _cx| {
|
||||
this.delegate.matches = paths;
|
||||
})
|
||||
.log_err();
|
||||
})
|
||||
fn update_matches(&mut self, _query: String, _cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
// TODO: Implement this once we fix the issues with the file context picker.
|
||||
Task::ready(())
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
let mat = &self.matches[self.selected_index];
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
let Some(project) = workspace
|
||||
.upgrade()
|
||||
.map(|workspace| workspace.read(cx).project().clone())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let path = mat.path.clone();
|
||||
let worktree_id = WorktreeId::from_usize(mat.worktree_id);
|
||||
let confirm_behavior = self.confirm_behavior;
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let mut text = String::new();
|
||||
|
||||
// TODO: Add the files from the selected directory.
|
||||
|
||||
this.delegate
|
||||
.context_store
|
||||
.update(cx, |context_store, cx| {
|
||||
context_store.insert_context(
|
||||
ContextKind::Directory,
|
||||
path.to_string_lossy().to_string(),
|
||||
text,
|
||||
);
|
||||
})?;
|
||||
|
||||
match confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => this.delegate.dismissed(cx),
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
})??;
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx)
|
||||
// TODO: Implement this once we fix the issues with the file context picker.
|
||||
match self.confirm_behavior {
|
||||
ConfirmBehavior::KeepOpen => {}
|
||||
ConfirmBehavior::Close => self.dismissed(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
@@ -230,18 +120,10 @@ impl PickerDelegate for DirectoryContextPickerDelegate {
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_ix: usize,
|
||||
_selected: bool,
|
||||
_cx: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let path_match = &self.matches[ix];
|
||||
let directory_name = path_match.path.to_string_lossy().to_string();
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(h_flex().gap_2().child(Label::new(directory_name))),
|
||||
)
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ impl PickerDelegate for ThreadContextPickerDelegate {
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.toggle_state(selected)
|
||||
.child(Label::new(thread.summary.clone())),
|
||||
.child(thread.summary.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::buffer_codegen::BufferCodegen;
|
||||
use crate::context_picker::ContextPicker;
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
use crate::terminal_codegen::TerminalCodegen;
|
||||
use crate::thread_store::ThreadStore;
|
||||
use crate::{CycleNextInlineAssist, CyclePreviousInlineAssist};
|
||||
use crate::{ToggleContextPicker, ToggleModelSelector};
|
||||
use crate::ToggleContextPicker;
|
||||
use crate::{
|
||||
assistant_settings::AssistantSettings, CycleNextInlineAssist, CyclePreviousInlineAssist,
|
||||
};
|
||||
use client::ErrorExt;
|
||||
use collections::VecDeque;
|
||||
use editor::{
|
||||
@@ -21,9 +22,9 @@ use gpui::{
|
||||
WeakModel, WeakView, WindowContext,
|
||||
};
|
||||
use language_model::{LanguageModel, LanguageModelRegistry};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use parking_lot::Mutex;
|
||||
use settings::Settings;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use std::cmp;
|
||||
use std::sync::Arc;
|
||||
use theme::ThemeSettings;
|
||||
@@ -38,8 +39,7 @@ pub struct PromptEditor<T> {
|
||||
mode: PromptEditorMode,
|
||||
context_strip: View<ContextStrip>,
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: View<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
edited_since_done: bool,
|
||||
prompt_history: VecDeque<String>,
|
||||
prompt_history_ix: Option<usize>,
|
||||
@@ -72,28 +72,23 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
|
||||
gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => {
|
||||
// Give the equivalent of the same left-padding that we're using on the right
|
||||
Pixels::from(24.0)
|
||||
}
|
||||
PromptEditorMode::Terminal { .. } => Pixels::ZERO,
|
||||
};
|
||||
|
||||
buttons.extend(self.render_buttons(cx));
|
||||
|
||||
v_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.border_y_1()
|
||||
.border_color(cx.theme().status().info_border)
|
||||
.size_full()
|
||||
.pt_1()
|
||||
.pb_2()
|
||||
.py(cx.line_height() / 2.5)
|
||||
.child(
|
||||
h_flex()
|
||||
.key_context("PromptEditor")
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.block_mouse_down()
|
||||
.cursor(CursorStyle::Arrow)
|
||||
.on_action(cx.listener(Self::toggle_context_picker))
|
||||
.on_action(cx.listener(Self::toggle_model_selector))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
@@ -105,7 +100,27 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
.w(spacing)
|
||||
.justify_center()
|
||||
.gap_2()
|
||||
.child(self.render_close_button(cx))
|
||||
.child(LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
IconButton::new("context", IconName::SettingsAlt)
|
||||
.shape(IconButtonShape::Square)
|
||||
.icon_size(IconSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
format!(
|
||||
"Using {}",
|
||||
LanguageModelRegistry::read_global(cx)
|
||||
.active_model()
|
||||
.map(|model| model.name().0)
|
||||
.unwrap_or_else(|| "No model selected".into()),
|
||||
),
|
||||
None,
|
||||
"Change Model",
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
))
|
||||
.map(|el| {
|
||||
let CodegenStatus::Error(error) = self.codegen_status(cx) else {
|
||||
return el;
|
||||
@@ -157,26 +172,13 @@ impl<T: 'static> Render for PromptEditor<T> {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(h_flex().gap_2().pr_6().children(buttons)),
|
||||
),
|
||||
.child(div().flex_1().child(self.render_editor(cx)))
|
||||
.child(h_flex().gap_2().pr_6().children(buttons)),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.child(h_flex().w(spacing).justify_between().gap_2())
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.pl_1()
|
||||
.pr_6()
|
||||
.justify_between()
|
||||
.child(div().pl_1().child(self.context_strip.clone()))
|
||||
.child(self.model_selector.clone()),
|
||||
),
|
||||
.child(h_flex().w(spacing).justify_center().gap_2())
|
||||
.child(self.context_strip.clone()),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -309,10 +311,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
self.context_picker_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||
self.model_selector_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match self.codegen_status(cx) {
|
||||
CodegenStatus::Idle | CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
@@ -402,44 +400,73 @@ impl<T: 'static> PromptEditor<T> {
|
||||
|
||||
match codegen_status {
|
||||
CodegenStatus::Idle => {
|
||||
vec![Button::new("start", mode.start_label())
|
||||
.icon(IconName::Return)
|
||||
.label_size(LabelSize::Small)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)))
|
||||
.into_any_element()]
|
||||
vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)),
|
||||
)
|
||||
.into_any_element(),
|
||||
Button::new("start", mode.start_label())
|
||||
.icon(IconName::Return)
|
||||
.icon_color(Color::Muted)
|
||||
.on_click(
|
||||
cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StartRequested)),
|
||||
)
|
||||
.into_any_element(),
|
||||
]
|
||||
}
|
||||
CodegenStatus::Pending => vec![IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_interrupt(),
|
||||
Some(&menu::Cancel),
|
||||
"Changes won't be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||
.into_any_element()],
|
||||
CodegenStatus::Pending => vec![
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Cancel Assist", cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element(),
|
||||
IconButton::new("stop", IconName::Stop)
|
||||
.icon_color(Color::Error)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_interrupt(),
|
||||
Some(&menu::Cancel),
|
||||
"Changes won't be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::StopRequested)))
|
||||
.into_any_element(),
|
||||
],
|
||||
CodegenStatus::Done | CodegenStatus::Error(_) => {
|
||||
let cancel = IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::for_action("Cancel Assist", &menu::Cancel, cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element();
|
||||
|
||||
let has_error = matches!(codegen_status, CodegenStatus::Error(_));
|
||||
if has_error || self.edited_since_done {
|
||||
vec![IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_restart(),
|
||||
Some(&menu::Confirm),
|
||||
"Changes will be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}))
|
||||
.into_any_element()]
|
||||
vec![
|
||||
cancel,
|
||||
IconButton::new("restart", IconName::RotateCw)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta(
|
||||
mode.tooltip_restart(),
|
||||
Some(&menu::Confirm),
|
||||
"Changes will be discarded",
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(|_, _, cx| {
|
||||
cx.emit(PromptEditorEvent::StartRequested);
|
||||
}))
|
||||
.into_any_element(),
|
||||
]
|
||||
} else {
|
||||
let accept = IconButton::new("accept", IconName::Check)
|
||||
.icon_color(Color::Info)
|
||||
@@ -455,6 +482,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
match &self.mode {
|
||||
PromptEditorMode::Terminal { .. } => vec![
|
||||
accept,
|
||||
cancel,
|
||||
IconButton::new("confirm", IconName::Play)
|
||||
.icon_color(Color::Info)
|
||||
.shape(IconButtonShape::Square)
|
||||
@@ -470,7 +498,7 @@ impl<T: 'static> PromptEditor<T> {
|
||||
}))
|
||||
.into_any_element(),
|
||||
],
|
||||
PromptEditorMode::Buffer { .. } => vec![accept],
|
||||
PromptEditorMode::Buffer { .. } => vec![accept, cancel],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -499,15 +527,6 @@ impl<T: 'static> PromptEditor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_close_button(&self, cx: &ViewContext<Self>) -> AnyElement {
|
||||
IconButton::new("cancel", IconName::Close)
|
||||
.icon_color(Color::Muted)
|
||||
.shape(IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Close Assistant", cx))
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(PromptEditorEvent::CancelRequested)))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_cycle_controls(&self, codegen: &BufferCodegen, cx: &ViewContext<Self>) -> AnyElement {
|
||||
let disabled = matches!(codegen.status(cx), CodegenStatus::Idle);
|
||||
|
||||
@@ -776,7 +795,6 @@ impl PromptEditor<BufferCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let mut this: PromptEditor<BufferCodegen> = PromptEditor {
|
||||
editor: prompt_editor.clone(),
|
||||
@@ -791,10 +809,19 @@ impl PromptEditor<BufferCodegen> {
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
@@ -915,7 +942,6 @@ impl PromptEditor<TerminalCodegen> {
|
||||
editor
|
||||
});
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let mut this = Self {
|
||||
editor: prompt_editor.clone(),
|
||||
@@ -930,10 +956,19 @@ impl PromptEditor<TerminalCodegen> {
|
||||
)
|
||||
}),
|
||||
context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
edited_since_done: false,
|
||||
prompt_history,
|
||||
prompt_history_ix: None,
|
||||
|
||||
@@ -7,17 +7,17 @@ use gpui::{
|
||||
WeakView,
|
||||
};
|
||||
use language_model::{LanguageModelRegistry, LanguageModelRequestTool};
|
||||
use language_model_selector::LanguageModelSelector;
|
||||
use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};
|
||||
use rope::Point;
|
||||
use settings::Settings;
|
||||
use settings::{update_settings_file, Settings};
|
||||
use theme::ThemeSettings;
|
||||
use ui::{
|
||||
prelude::*, ButtonLike, ElevationIndex, KeyBinding, PopoverMenu, PopoverMenuHandle,
|
||||
SwitchWithLabel,
|
||||
prelude::*, ButtonLike, CheckboxWithLabel, ElevationIndex, KeyBinding, PopoverMenu,
|
||||
PopoverMenuHandle, Tooltip,
|
||||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::assistant_model_selector::AssistantModelSelector;
|
||||
use crate::assistant_settings::AssistantSettings;
|
||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||
use crate::context_store::ContextStore;
|
||||
use crate::context_strip::ContextStrip;
|
||||
@@ -33,8 +33,8 @@ pub struct MessageEditor {
|
||||
context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
inline_context_picker: View<ContextPicker>,
|
||||
inline_context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
|
||||
model_selector: View<AssistantModelSelector>,
|
||||
model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
language_model_selector: View<LanguageModelSelector>,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
|
||||
use_tools: bool,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
@@ -50,7 +50,6 @@ impl MessageEditor {
|
||||
let context_store = cx.new_model(|_cx| ContextStore::new());
|
||||
let context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let inline_context_picker_menu_handle = PopoverMenuHandle::default();
|
||||
let model_selector_menu_handle = PopoverMenuHandle::default();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(10, cx);
|
||||
@@ -93,17 +92,27 @@ impl MessageEditor {
|
||||
context_picker_menu_handle,
|
||||
inline_context_picker,
|
||||
inline_context_picker_menu_handle,
|
||||
model_selector: cx.new_view(|cx| {
|
||||
AssistantModelSelector::new(fs, model_selector_menu_handle.clone(), cx)
|
||||
language_model_selector: cx.new_view(|cx| {
|
||||
let fs = fs.clone();
|
||||
LanguageModelSelector::new(
|
||||
move |model, cx| {
|
||||
update_settings_file::<AssistantSettings>(
|
||||
fs.clone(),
|
||||
cx,
|
||||
move |settings, _cx| settings.set_model(model.clone()),
|
||||
);
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
model_selector_menu_handle,
|
||||
language_model_selector_menu_handle: PopoverMenuHandle::default(),
|
||||
use_tools: false,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
|
||||
self.model_selector_menu_handle.toggle(cx)
|
||||
self.language_model_selector_menu_handle.toggle(cx);
|
||||
}
|
||||
|
||||
fn toggle_context_picker(&mut self, _: &ToggleContextPicker, cx: &mut ViewContext<Self>) {
|
||||
@@ -194,6 +203,50 @@ impl MessageEditor {
|
||||
let editor_focus_handle = self.editor.focus_handle(cx);
|
||||
cx.focus(&editor_focus_handle);
|
||||
}
|
||||
|
||||
fn render_language_model_selector(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
let focus_handle = self.language_model_selector.focus_handle(cx).clone();
|
||||
|
||||
LanguageModelSelectorPopoverMenu::new(
|
||||
self.language_model_selector.clone(),
|
||||
ButtonLike::new("active-model")
|
||||
.style(ButtonStyle::Subtle)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.gap_0p5()
|
||||
.child(
|
||||
div()
|
||||
.overflow_x_hidden()
|
||||
.flex_grow()
|
||||
.whitespace_nowrap()
|
||||
.child(match active_model {
|
||||
Some(model) => h_flex()
|
||||
.child(
|
||||
Label::new(model.name().0)
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any_element(),
|
||||
_ => Label::new("No model selected")
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Icon::new(IconName::ChevronDown)
|
||||
.color(Color::Muted)
|
||||
.size(IconSize::XSmall),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
|
||||
}),
|
||||
)
|
||||
.with_handle(self.language_model_selector_menu_handle.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for MessageEditor {
|
||||
@@ -256,7 +309,7 @@ impl Render for MessageEditor {
|
||||
.child(
|
||||
h_flex()
|
||||
.justify_between()
|
||||
.child(SwitchWithLabel::new(
|
||||
.child(CheckboxWithLabel::new(
|
||||
"use-tools",
|
||||
Label::new("Tools"),
|
||||
self.use_tools.into(),
|
||||
@@ -268,19 +321,22 @@ impl Render for MessageEditor {
|
||||
}),
|
||||
))
|
||||
.child(
|
||||
h_flex().gap_1().child(self.model_selector.clone()).child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
}),
|
||||
),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(self.render_language_model_selector(cx))
|
||||
.child(
|
||||
ButtonLike::new("chat")
|
||||
.style(ButtonStyle::Filled)
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new("Submit"))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Chat, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Chat, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,17 +39,10 @@ impl EventEmitter<ToolbarItemEvent> for Breadcrumbs {}
|
||||
impl Render for Breadcrumbs {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
const MAX_SEGMENTS: usize = 12;
|
||||
|
||||
let element = h_flex()
|
||||
.id("breadcrumb-container")
|
||||
.flex_grow()
|
||||
.overflow_x_scroll()
|
||||
.text_ui(cx);
|
||||
|
||||
let element = h_flex().text_ui(cx);
|
||||
let Some(active_item) = self.active_item.as_ref() else {
|
||||
return element;
|
||||
};
|
||||
|
||||
let Some(mut segments) = active_item.breadcrumbs(cx.theme(), cx) else {
|
||||
return element;
|
||||
};
|
||||
@@ -59,7 +52,6 @@ impl Render for Breadcrumbs {
|
||||
prefix_end_ix,
|
||||
segments.len().saturating_sub(MAX_SEGMENTS / 2),
|
||||
);
|
||||
|
||||
if suffix_start_ix > prefix_end_ix {
|
||||
segments.splice(
|
||||
prefix_end_ix..suffix_start_ix,
|
||||
@@ -90,7 +82,6 @@ impl Render for Breadcrumbs {
|
||||
});
|
||||
|
||||
let breadcrumbs_stack = h_flex().gap_1().children(breadcrumbs);
|
||||
|
||||
match active_item
|
||||
.downcast::<Editor>()
|
||||
.map(|editor| editor.downgrade())
|
||||
@@ -111,14 +102,14 @@ impl Render for Breadcrumbs {
|
||||
if let Some(editor) = editor.upgrade() {
|
||||
let focus_handle = editor.read(cx).focus_handle(cx);
|
||||
Tooltip::for_action_in(
|
||||
"Show Symbol Outline",
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
&focus_handle,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
Tooltip::for_action(
|
||||
"Show Symbol Outline",
|
||||
"Show symbol outline",
|
||||
&editor::actions::ToggleOutline,
|
||||
cx,
|
||||
)
|
||||
|
||||
@@ -252,7 +252,7 @@ spec:
|
||||
value: "${AUTO_JOIN_CHANNEL_ID}"
|
||||
securityContext:
|
||||
capabilities:
|
||||
# TODO - Switch to the more restrictive `PERFMON` capability.
|
||||
# FIXME - Switch to the more restrictive `PERFMON` capability.
|
||||
# This capability isn't yet available in a stable version of Debian.
|
||||
add: ["SYS_ADMIN"]
|
||||
terminationGracePeriodSeconds: 10
|
||||
|
||||
@@ -18,7 +18,6 @@ collections.workspace = true
|
||||
ctor.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
feature_flags.workspace = true
|
||||
gpui.workspace = true
|
||||
language.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
@@ -12,9 +12,9 @@ use editor::{
|
||||
display_map::{BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, RenderBlock},
|
||||
highlight_diagnostic_message,
|
||||
scroll::Autoscroll,
|
||||
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, ToOffset,
|
||||
Anchor, AnchorRangeExt, Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer,
|
||||
RangeToAnchorExt, ToOffset,
|
||||
};
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use gpui::{
|
||||
actions, div, svg, AnyElement, AnyView, AppContext, Context, EventEmitter, FocusHandle,
|
||||
FocusableView, Global, HighlightStyle, InteractiveElement, IntoElement, Model, ParentElement,
|
||||
@@ -22,8 +22,8 @@ use gpui::{
|
||||
WeakView, WindowContext,
|
||||
};
|
||||
use language::{
|
||||
Bias, Buffer, BufferRow, BufferSnapshot, Diagnostic, DiagnosticEntry, DiagnosticSeverity,
|
||||
Point, Selection, SelectionGoal, ToTreeSitterPoint,
|
||||
Bias, Buffer, BufferId, Diagnostic, DiagnosticEntry, DiagnosticSeverity, OffsetRangeExt, Point,
|
||||
Selection, SelectionGoal,
|
||||
};
|
||||
use lsp::LanguageServerId;
|
||||
use project::{DiagnosticSummary, Project, ProjectPath};
|
||||
@@ -31,17 +31,16 @@ use project_diagnostics_settings::ProjectDiagnosticsSettings;
|
||||
use settings::Settings;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cmp,
|
||||
cmp::Ordering,
|
||||
mem,
|
||||
ops::{Range, RangeInclusive},
|
||||
ops::Range,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use theme::ActiveTheme;
|
||||
pub use toolbar_controls::ToolbarControls;
|
||||
use ui::{h_flex, prelude::*, Icon, IconName, Label};
|
||||
use util::ResultExt;
|
||||
use util::{maybe, RangeExt, ResultExt};
|
||||
use workspace::{
|
||||
item::{BreadcrumbText, Item, ItemEvent, ItemHandle, TabContentParams},
|
||||
searchable::SearchableItemHandle,
|
||||
@@ -259,26 +258,69 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
|
||||
fn deploy(workspace: &mut Workspace, _: &Deploy, cx: &mut ViewContext<Workspace>) {
|
||||
if let Some(existing) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
workspace.activate_item(&existing, true, true, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.view().downgrade();
|
||||
let diagnostic_to_select = maybe!({
|
||||
let active_item = workspace.active_item(cx)?;
|
||||
// Already on diagnostics, so don't update position / selection within.
|
||||
if active_item.downcast::<ProjectDiagnosticsEditor>().is_some() {
|
||||
None
|
||||
} else {
|
||||
let active_editor = active_item.act_as::<Editor>(cx)?.read(cx);
|
||||
let snapshot = active_editor.buffer().read(cx).snapshot(cx);
|
||||
let newest_selection = active_editor.selections.newest::<usize>(cx).range();
|
||||
let mut diagnostics =
|
||||
snapshot.diagnostics_in_range(newest_selection.clone(), false);
|
||||
let diagnostic_entry: DiagnosticEntry<usize> = dbg!(diagnostics.next())?;
|
||||
let buffer_id = diagnostic_entry
|
||||
.range
|
||||
.to_anchors(&snapshot)
|
||||
.start
|
||||
.buffer_id?;
|
||||
Some((buffer_id, diagnostic_entry.range, newest_selection))
|
||||
}
|
||||
});
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
let diagnostics =
|
||||
if let Some(diagnostics) = workspace.item_of_type::<ProjectDiagnosticsEditor>(cx) {
|
||||
// todo! dedupe
|
||||
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.set_selection_that_intersects_diagnostic(
|
||||
buffer_id,
|
||||
diagnostic_range,
|
||||
selection_range,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
workspace.activate_item(&diagnostics, true, true, cx);
|
||||
} else {
|
||||
let workspace_handle = cx.view().downgrade();
|
||||
|
||||
let include_warnings = match cx.try_global::<IncludeWarnings>() {
|
||||
Some(include_warnings) => include_warnings.0,
|
||||
None => ProjectDiagnosticsSettings::get_global(cx).include_warnings,
|
||||
};
|
||||
|
||||
let diagnostics = cx.new_view(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
if let Some((buffer_id, diagnostic_range, selection_range)) = diagnostic_to_select {
|
||||
diagnostics.update(cx, |diagnostics, cx| {
|
||||
diagnostics.set_selection_that_intersects_diagnostic(
|
||||
buffer_id,
|
||||
diagnostic_range,
|
||||
selection_range,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||
};
|
||||
|
||||
let diagnostics = cx.new_view(|cx| {
|
||||
ProjectDiagnosticsEditor::new(
|
||||
workspace.project().clone(),
|
||||
include_warnings,
|
||||
workspace_handle,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(diagnostics), None, true, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_warnings(&mut self, _: &ToggleWarnings, cx: &mut ViewContext<Self>) {
|
||||
@@ -425,28 +467,31 @@ impl ProjectDiagnosticsEditor {
|
||||
blocks: Default::default(),
|
||||
block_count: 0,
|
||||
};
|
||||
let mut pending_range: Option<(Range<Point>, Range<Point>, usize)> = None;
|
||||
let mut pending_range: Option<(Range<Point>, usize)> = None;
|
||||
let mut is_first_excerpt_for_group = true;
|
||||
for (ix, entry) in group.entries.iter().map(Some).chain([None]).enumerate() {
|
||||
let resolved_entry = entry.map(|e| e.resolve::<Point>(&snapshot));
|
||||
let expanded_range = resolved_entry.as_ref().map(|entry| {
|
||||
context_range_for_entry(entry, self.context, &snapshot, cx)
|
||||
});
|
||||
if let Some((range, context_range, start_ix)) = &mut pending_range {
|
||||
if let Some(expanded_range) = expanded_range.clone() {
|
||||
// If the entries are overlapping or next to each-other, merge them into one excerpt.
|
||||
if context_range.end.row + 1 >= expanded_range.start.row {
|
||||
context_range.end = context_range.end.max(expanded_range.end);
|
||||
if let Some((range, start_ix)) = &mut pending_range {
|
||||
if let Some(entry) = resolved_entry.as_ref() {
|
||||
if entry.range.start.row <= range.end.row + 1 + self.context * 2 {
|
||||
range.end = range.end.max(entry.range.end);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let excerpt_start =
|
||||
Point::new(range.start.row.saturating_sub(self.context), 0);
|
||||
let excerpt_end = snapshot.clip_point(
|
||||
Point::new(range.end.row + self.context, u32::MAX),
|
||||
Bias::Left,
|
||||
);
|
||||
|
||||
let excerpt_id = excerpts
|
||||
.insert_excerpts_after(
|
||||
prev_excerpt_id,
|
||||
buffer.clone(),
|
||||
[ExcerptRange {
|
||||
context: context_range.clone(),
|
||||
context: excerpt_start..excerpt_end,
|
||||
primary: Some(range.clone()),
|
||||
}],
|
||||
cx,
|
||||
@@ -503,9 +548,8 @@ impl ProjectDiagnosticsEditor {
|
||||
pending_range.take();
|
||||
}
|
||||
|
||||
if let Some(entry) = resolved_entry.as_ref() {
|
||||
let range = entry.range.clone();
|
||||
pending_range = Some((range, expanded_range.unwrap(), ix));
|
||||
if let Some(entry) = resolved_entry {
|
||||
pending_range = Some((entry.range.clone(), ix));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -633,6 +677,30 @@ impl ProjectDiagnosticsEditor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_selection_that_intersects_diagnostic(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
diagnostic_range: Range<usize>,
|
||||
selection_range: Range<usize>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let buffer = editor.buffer().read(cx);
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
snapshot.excerpts_for_range(range)
|
||||
/*
|
||||
let excerpts = buffer.excerpts_for_buffer_id(buffer_id, cx);
|
||||
for (excerpt_id, excerpt_range) in excerpts {
|
||||
let excerpt_range = excerpt_range.context.to_offset(snapshot.excerpt_containing(range));
|
||||
if excerpt_range.contains_inclusive(&diagnostic_range.to_offset(&snapshot)) {}
|
||||
}
|
||||
*/
|
||||
// editor.change_selections(Some(Autoscroll::center()), cx, |selections| {
|
||||
// selections.select_ranges(vec![diagnostic_range]);
|
||||
// })
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn check_invariants(&self, cx: &mut ViewContext<Self>) {
|
||||
let mut excerpts = Vec::new();
|
||||
@@ -924,169 +992,3 @@ fn compare_diagnostics(
|
||||
})
|
||||
.then_with(|| old.diagnostic.message.cmp(&new.diagnostic.message))
|
||||
}
|
||||
|
||||
const DIAGNOSTIC_EXPANSION_ROW_LIMIT: u32 = 32;
|
||||
|
||||
fn context_range_for_entry(
|
||||
entry: &DiagnosticEntry<Point>,
|
||||
context: u32,
|
||||
snapshot: &BufferSnapshot,
|
||||
cx: &AppContext,
|
||||
) -> Range<Point> {
|
||||
if cx.is_staff() {
|
||||
if let Some(rows) = heuristic_syntactic_expand(
|
||||
entry.range.clone(),
|
||||
DIAGNOSTIC_EXPANSION_ROW_LIMIT,
|
||||
snapshot,
|
||||
cx,
|
||||
) {
|
||||
return Range {
|
||||
start: Point::new(*rows.start(), 0),
|
||||
end: snapshot.clip_point(Point::new(*rows.end(), u32::MAX), Bias::Left),
|
||||
};
|
||||
}
|
||||
}
|
||||
Range {
|
||||
start: Point::new(entry.range.start.row.saturating_sub(context), 0),
|
||||
end: snapshot.clip_point(
|
||||
Point::new(entry.range.end.row + context, u32::MAX),
|
||||
Bias::Left,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expands the input range using syntax information from TreeSitter. This expansion will be limited
|
||||
/// to the specified `max_row_count`.
|
||||
///
|
||||
/// If there is a containing outline item that is less than `max_row_count`, it will be returned.
|
||||
/// Otherwise fairly arbitrary heuristics are applied to attempt to return a logical block of code.
|
||||
fn heuristic_syntactic_expand<'a>(
|
||||
input_range: Range<Point>,
|
||||
max_row_count: u32,
|
||||
snapshot: &'a BufferSnapshot,
|
||||
cx: &'a AppContext,
|
||||
) -> Option<RangeInclusive<BufferRow>> {
|
||||
let input_row_count = input_range.end.row - input_range.start.row;
|
||||
if input_row_count > max_row_count {
|
||||
return None;
|
||||
}
|
||||
|
||||
// If the outline node contains the diagnostic and is small enough, just use that.
|
||||
let outline_range = snapshot.outline_range_containing(input_range.clone());
|
||||
if let Some(outline_range) = outline_range.clone() {
|
||||
// Remove blank lines from start and end
|
||||
if let Some(start_row) = (outline_range.start.row..outline_range.end.row)
|
||||
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
|
||||
{
|
||||
if let Some(end_row) = (outline_range.start.row..outline_range.end.row + 1)
|
||||
.rev()
|
||||
.find(|row| !snapshot.line_indent_for_row(*row).is_line_blank())
|
||||
{
|
||||
let row_count = end_row.saturating_sub(start_row);
|
||||
if row_count <= max_row_count {
|
||||
return Some(RangeInclusive::new(
|
||||
outline_range.start.row,
|
||||
outline_range.end.row,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut node = snapshot.syntax_ancestor(input_range.clone())?;
|
||||
loop {
|
||||
let node_start = Point::from_ts_point(node.start_position());
|
||||
let node_end = Point::from_ts_point(node.end_position());
|
||||
let node_range = node_start..node_end;
|
||||
let row_count = node_end.row - node_start.row + 1;
|
||||
|
||||
// Stop if we've exceeded the row count or reached an outline node. Then, find the interval
|
||||
// of node children which contains the query range. For example, this allows just returning
|
||||
// the header of a declaration rather than the entire declaration.
|
||||
if row_count > max_row_count || outline_range == Some(node_range.clone()) {
|
||||
let mut cursor = node.walk();
|
||||
let mut included_child_start = None;
|
||||
let mut included_child_end = None;
|
||||
let mut previous_end = node_start;
|
||||
if cursor.goto_first_child() {
|
||||
loop {
|
||||
let child_node = cursor.node();
|
||||
let child_range = previous_end..Point::from_ts_point(child_node.end_position());
|
||||
if included_child_start.is_none() && child_range.contains(&input_range.start) {
|
||||
included_child_start = Some(child_range.start);
|
||||
}
|
||||
if child_range.contains(&input_range.end) {
|
||||
included_child_end = Some(child_range.end);
|
||||
}
|
||||
previous_end = child_range.end;
|
||||
if !cursor.goto_next_sibling() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let end = included_child_end.unwrap_or(node_range.end);
|
||||
if let Some(start) = included_child_start {
|
||||
let row_count = end.row - start.row;
|
||||
if row_count < max_row_count {
|
||||
return Some(RangeInclusive::new(start.row, end.row));
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"Expanding to ancestor started on {} node exceeding row limit of {max_row_count}.",
|
||||
node.grammar_name()
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let node_name = node.grammar_name();
|
||||
let node_row_range = RangeInclusive::new(node_range.start.row, node_range.end.row);
|
||||
if node_name.ends_with("block") {
|
||||
return Some(node_row_range);
|
||||
} else if node_name.ends_with("statement") || node_name.ends_with("declaration") {
|
||||
// Expand to the nearest dedent or blank line for statements and declarations.
|
||||
let tab_size = snapshot.settings_at(node_range.start, cx).tab_size.get();
|
||||
let indent_level = snapshot
|
||||
.line_indent_for_row(node_range.start.row)
|
||||
.len(tab_size);
|
||||
let rows_remaining = max_row_count.saturating_sub(row_count);
|
||||
let Some(start_row) = (node_range.start.row.saturating_sub(rows_remaining)
|
||||
..node_range.start.row)
|
||||
.rev()
|
||||
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
|
||||
else {
|
||||
return Some(node_row_range);
|
||||
};
|
||||
let rows_remaining = max_row_count.saturating_sub(node_range.end.row - start_row);
|
||||
let Some(end_row) = (node_range.end.row + 1
|
||||
..cmp::min(
|
||||
node_range.end.row + rows_remaining + 1,
|
||||
snapshot.row_count(),
|
||||
))
|
||||
.find(|row| is_line_blank_or_indented_less(indent_level, *row, tab_size, snapshot))
|
||||
else {
|
||||
return Some(node_row_range);
|
||||
};
|
||||
return Some(RangeInclusive::new(start_row, end_row));
|
||||
}
|
||||
|
||||
// TODO: doing this instead of walking a cursor as that doesn't work - why?
|
||||
let Some(parent) = node.parent() else {
|
||||
log::info!(
|
||||
"Expanding to ancestor reached the top node, so using default context line count.",
|
||||
);
|
||||
return None;
|
||||
};
|
||||
node = parent;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_line_blank_or_indented_less(
|
||||
indent_level: u32,
|
||||
row: u32,
|
||||
tab_size: u32,
|
||||
snapshot: &BufferSnapshot,
|
||||
) -> bool {
|
||||
let line_indent = snapshot.line_indent_for_row(row);
|
||||
line_indent.is_line_blank() || line_indent.len(tab_size) < indent_level
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::cell::RefCell;
|
||||
use std::{cmp::Reverse, ops::Range, rc::Rc};
|
||||
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
div, px, uniform_list, AnyElement, BackgroundExecutor, Div, FontWeight, ListSizingBehavior,
|
||||
@@ -10,13 +13,6 @@ use lsp::LanguageServerId;
|
||||
use multi_buffer::{Anchor, ExcerptId};
|
||||
use ordered_float::OrderedFloat;
|
||||
use project::{CodeAction, Completion, TaskSourceKind};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp::{min, Reverse},
|
||||
iter,
|
||||
ops::Range,
|
||||
rc::Rc,
|
||||
};
|
||||
use task::ResolvedTask;
|
||||
use ui::{prelude::*, Color, IntoElement, ListItem, Pixels, Popover, Styled};
|
||||
use util::ResultExt;
|
||||
@@ -162,7 +158,6 @@ pub struct CompletionsMenu {
|
||||
scroll_handle: UniformListScrollHandle,
|
||||
resolve_completions: bool,
|
||||
show_completion_documentation: bool,
|
||||
last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -198,7 +193,6 @@ impl CompletionsMenu {
|
||||
selected_item: 0,
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: true,
|
||||
last_rendered_range: RefCell::new(None).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +249,6 @@ impl CompletionsMenu {
|
||||
scroll_handle: UniformListScrollHandle::new(),
|
||||
resolve_completions: false,
|
||||
show_completion_documentation: false,
|
||||
last_rendered_range: RefCell::new(None).into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +257,11 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.update_selection_index(0, provider, cx);
|
||||
self.selected_item = 0;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_selected_completion(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_prev(
|
||||
@@ -272,7 +269,15 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.update_selection_index(self.prev_match_index(), provider, cx);
|
||||
if self.selected_item > 0 {
|
||||
self.selected_item -= 1;
|
||||
} else {
|
||||
self.selected_item = self.entries.len() - 1;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_selected_completion(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_next(
|
||||
@@ -280,7 +285,15 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.update_selection_index(self.next_match_index(), provider, cx);
|
||||
if self.selected_item + 1 < self.entries.len() {
|
||||
self.selected_item += 1;
|
||||
} else {
|
||||
self.selected_item = 0;
|
||||
}
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_selected_completion(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn select_last(
|
||||
@@ -288,38 +301,11 @@ impl CompletionsMenu {
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
self.update_selection_index(self.entries.len() - 1, provider, cx);
|
||||
}
|
||||
|
||||
fn update_selection_index(
|
||||
&mut self,
|
||||
match_index: usize,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
if self.selected_item != match_index {
|
||||
self.selected_item = match_index;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_visible_completions(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn prev_match_index(&self) -> usize {
|
||||
if self.selected_item > 0 {
|
||||
self.selected_item - 1
|
||||
} else {
|
||||
self.entries.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn next_match_index(&self) -> usize {
|
||||
if self.selected_item + 1 < self.entries.len() {
|
||||
self.selected_item + 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
self.selected_item = self.entries.len() - 1;
|
||||
self.scroll_handle
|
||||
.scroll_to_item(self.selected_item, ScrollStrategy::Top);
|
||||
self.resolve_selected_completion(provider, cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn show_inline_completion_hint(&mut self, hint: InlineCompletionMenuHint) {
|
||||
@@ -344,7 +330,7 @@ impl CompletionsMenu {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_visible_completions(
|
||||
pub fn resolve_selected_completion(
|
||||
&mut self,
|
||||
provider: Option<&dyn CompletionProvider>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
@@ -356,76 +342,24 @@ impl CompletionsMenu {
|
||||
return;
|
||||
};
|
||||
|
||||
// Attempt to resolve completions for every item that will be displayed. This matters
|
||||
// because single line documentation may be displayed inline with the completion.
|
||||
//
|
||||
// When navigating to the very beginning or end of completions, `last_rendered_range` may
|
||||
// have no overlap with the completions that will be displayed, so instead use a range based
|
||||
// on the last rendered count.
|
||||
const APPROXIMATE_VISIBLE_COUNT: usize = 12;
|
||||
let last_rendered_range = self.last_rendered_range.borrow().clone();
|
||||
let visible_count = last_rendered_range
|
||||
.clone()
|
||||
.map_or(APPROXIMATE_VISIBLE_COUNT, |range| range.count());
|
||||
let entry_range = if self.selected_item == 0 {
|
||||
0..min(visible_count, self.entries.len())
|
||||
} else if self.selected_item == self.entries.len() - 1 {
|
||||
self.entries.len().saturating_sub(visible_count)..self.entries.len()
|
||||
} else {
|
||||
last_rendered_range.map_or(0..0, |range| {
|
||||
min(range.start, self.entries.len())..min(range.end, self.entries.len())
|
||||
})
|
||||
};
|
||||
match &self.entries[self.selected_item] {
|
||||
CompletionEntry::Match(entry) => {
|
||||
let completion_index = entry.candidate_id;
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.buffer.clone(),
|
||||
vec![completion_index],
|
||||
self.completions.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
// Expand the range to resolve more completions than are predicted to be visible, to reduce
|
||||
// jank on navigation.
|
||||
const EXTRA_TO_RESOLVE: usize = 4;
|
||||
let entry_indices = util::iterate_expanded_and_wrapped_usize_range(
|
||||
entry_range.clone(),
|
||||
EXTRA_TO_RESOLVE,
|
||||
EXTRA_TO_RESOLVE,
|
||||
self.entries.len(),
|
||||
);
|
||||
|
||||
// Avoid work by sometimes filtering out completions that already have documentation.
|
||||
// This filtering doesn't happen if the completions are currently being updated.
|
||||
let completions = self.completions.borrow();
|
||||
let candidate_ids = entry_indices
|
||||
.flat_map(|i| Self::entry_candidate_id(&self.entries[i]))
|
||||
.filter(|i| completions[*i].documentation.is_none());
|
||||
|
||||
// Current selection is always resolved even if it already has documentation, to handle
|
||||
// out-of-spec language servers that return more results later.
|
||||
let candidate_ids = match Self::entry_candidate_id(&self.entries[self.selected_item]) {
|
||||
None => candidate_ids.collect::<Vec<usize>>(),
|
||||
Some(selected_candidate_id) => iter::once(selected_candidate_id)
|
||||
.chain(candidate_ids.filter(|id| *id != selected_candidate_id))
|
||||
.collect::<Vec<usize>>(),
|
||||
};
|
||||
|
||||
if candidate_ids.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let resolve_task = provider.resolve_completions(
|
||||
self.buffer.clone(),
|
||||
candidate_ids,
|
||||
self.completions.clone(),
|
||||
cx,
|
||||
);
|
||||
|
||||
cx.spawn(move |editor, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
editor.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
cx.spawn(move |editor, mut cx| async move {
|
||||
if let Some(true) = resolve_task.await.log_err() {
|
||||
editor.update(&mut cx, |_, cx| cx.notify()).ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn entry_candidate_id(entry: &CompletionEntry) -> Option<usize> {
|
||||
match entry {
|
||||
CompletionEntry::Match(entry) => Some(entry.candidate_id),
|
||||
CompletionEntry::InlineCompletionHint { .. } => None,
|
||||
CompletionEntry::InlineCompletionHint { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,14 +408,12 @@ impl CompletionsMenu {
|
||||
let selected_item = self.selected_item;
|
||||
let completions = self.completions.clone();
|
||||
let matches = self.entries.clone();
|
||||
let last_rendered_range = self.last_rendered_range.clone();
|
||||
let style = style.clone();
|
||||
let list = uniform_list(
|
||||
cx.view().clone(),
|
||||
"completions",
|
||||
matches.len(),
|
||||
move |_editor, range, cx| {
|
||||
last_rendered_range.borrow_mut().replace(range.clone());
|
||||
let start_ix = range.start;
|
||||
let completions_guard = completions.borrow_mut();
|
||||
|
||||
|
||||
@@ -3742,7 +3742,7 @@ impl Editor {
|
||||
|
||||
if editor.focus_handle.is_focused(cx) && menu.is_some() {
|
||||
let mut menu = menu.unwrap();
|
||||
menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
|
||||
menu.resolve_selected_completion(editor.completion_provider.as_deref(), cx);
|
||||
|
||||
if editor.show_inline_completions_in_menu(cx) {
|
||||
if let Some(hint) = editor.inline_completion_menu_hint(cx) {
|
||||
@@ -8774,10 +8774,9 @@ impl Editor {
|
||||
.map(|selection| {
|
||||
let old_range = selection.start..selection.end;
|
||||
let mut new_range = old_range.clone();
|
||||
let mut new_node = None;
|
||||
while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
|
||||
while let Some(containing_range) =
|
||||
buffer.range_for_syntax_ancestor(new_range.clone())
|
||||
{
|
||||
new_node = Some(node);
|
||||
new_range = containing_range;
|
||||
if !display_map.intersects_fold(new_range.start)
|
||||
&& !display_map.intersects_fold(new_range.end)
|
||||
@@ -8786,17 +8785,6 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(node) = new_node {
|
||||
// Log the ancestor, to support using this action as a way to explore TreeSitter
|
||||
// nodes. Parent and grandparent are also logged because this operation will not
|
||||
// visit nodes that have the same range as their parent.
|
||||
log::info!("Node: {node:?}");
|
||||
let parent = node.parent();
|
||||
log::info!("Parent: {parent:?}");
|
||||
let grandparent = parent.and_then(|x| x.parent());
|
||||
log::info!("Grandparent: {grandparent:?}");
|
||||
}
|
||||
|
||||
selected_larger_node |= new_range != old_range;
|
||||
Selection {
|
||||
id: selection.id,
|
||||
|
||||
@@ -25,18 +25,14 @@ use language::{
|
||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||
use multi_buffer::MultiBufferIndentGuide;
|
||||
use parking_lot::Mutex;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use project::{buffer_store::BufferChangeSet, FakeFs};
|
||||
use project::{
|
||||
lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
|
||||
project_settings::{LspSettings, ProjectSettings},
|
||||
};
|
||||
use serde_json::{self, json};
|
||||
use std::sync::atomic::{self, AtomicBool, AtomicUsize};
|
||||
use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
|
||||
use std::{
|
||||
iter,
|
||||
sync::atomic::{self, AtomicUsize},
|
||||
};
|
||||
use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
|
||||
use unindent::Unindent;
|
||||
use util::{
|
||||
@@ -10791,62 +10787,6 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches(
|
||||
async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let item_0 = lsp::CompletionItem {
|
||||
label: "abs".into(),
|
||||
insert_text: Some("abs".into()),
|
||||
data: Some(json!({ "very": "special"})),
|
||||
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: "abs".to_string(),
|
||||
insert: lsp::Range::default(),
|
||||
replace: lsp::Range::default(),
|
||||
},
|
||||
)),
|
||||
..lsp::CompletionItem::default()
|
||||
};
|
||||
let items = iter::once(item_0.clone())
|
||||
.chain((11..51).map(|i| lsp::CompletionItem {
|
||||
label: format!("item_{}", i),
|
||||
insert_text: Some(format!("item_{}", i)),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
|
||||
..lsp::CompletionItem::default()
|
||||
}))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let default_commit_characters = vec!["?".to_string()];
|
||||
let default_data = json!({ "default": "data"});
|
||||
let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
|
||||
let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
|
||||
let default_edit_range = lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 5,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 5,
|
||||
},
|
||||
};
|
||||
|
||||
let item_0_out = lsp::CompletionItem {
|
||||
commit_characters: Some(default_commit_characters.clone()),
|
||||
insert_text_format: Some(default_insert_text_format),
|
||||
..item_0
|
||||
};
|
||||
let items_out = iter::once(item_0_out)
|
||||
.chain(items[1..].iter().map(|item| lsp::CompletionItem {
|
||||
commit_characters: Some(default_commit_characters.clone()),
|
||||
data: Some(default_data.clone()),
|
||||
insert_text_mode: Some(default_insert_text_mode),
|
||||
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: default_edit_range,
|
||||
new_text: item.label.clone(),
|
||||
})),
|
||||
..item.clone()
|
||||
}))
|
||||
.collect::<Vec<lsp::CompletionItem>>();
|
||||
|
||||
let mut cx = EditorLspTestContext::new_rust(
|
||||
lsp::ServerCapabilities {
|
||||
completion_provider: Some(lsp::CompletionOptions {
|
||||
@@ -10863,15 +10803,138 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
|
||||
cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
|
||||
cx.simulate_keystroke(".");
|
||||
|
||||
let default_commit_characters = vec!["?".to_string()];
|
||||
let default_data = json!({ "very": "special"});
|
||||
let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
|
||||
let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
|
||||
let default_edit_range = lsp::Range {
|
||||
start: lsp::Position {
|
||||
line: 0,
|
||||
character: 5,
|
||||
},
|
||||
end: lsp::Position {
|
||||
line: 0,
|
||||
character: 5,
|
||||
},
|
||||
};
|
||||
|
||||
let resolve_requests_number = Arc::new(AtomicUsize::new(0));
|
||||
let expect_first_item = Arc::new(AtomicBool::new(true));
|
||||
cx.lsp
|
||||
.server
|
||||
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
|
||||
let closure_default_data = default_data.clone();
|
||||
let closure_resolve_requests_number = resolve_requests_number.clone();
|
||||
let closure_expect_first_item = expect_first_item.clone();
|
||||
let closure_default_commit_characters = default_commit_characters.clone();
|
||||
move |item_to_resolve, _| {
|
||||
closure_resolve_requests_number.fetch_add(1, atomic::Ordering::Release);
|
||||
let default_data = closure_default_data.clone();
|
||||
let default_commit_characters = closure_default_commit_characters.clone();
|
||||
let expect_first_item = closure_expect_first_item.clone();
|
||||
async move {
|
||||
if expect_first_item.load(atomic::Ordering::Acquire) {
|
||||
assert_eq!(
|
||||
item_to_resolve.label, "Some(2)",
|
||||
"Should have selected the first item"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.data,
|
||||
Some(json!({ "very": "special"})),
|
||||
"First item should bring its own data for resolving"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.commit_characters,
|
||||
Some(default_commit_characters),
|
||||
"First item had no own commit characters and should inherit the default ones"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
item_to_resolve.text_edit,
|
||||
Some(lsp::CompletionTextEdit::InsertAndReplace { .. })
|
||||
),
|
||||
"First item should bring its own edit range for resolving"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.insert_text_format,
|
||||
Some(default_insert_text_format),
|
||||
"First item had no own insert text format and should inherit the default one"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.insert_text_mode,
|
||||
Some(lsp::InsertTextMode::ADJUST_INDENTATION),
|
||||
"First item should bring its own insert text mode for resolving"
|
||||
);
|
||||
Ok(item_to_resolve)
|
||||
} else {
|
||||
assert_eq!(
|
||||
item_to_resolve.label, "vec![2]",
|
||||
"Should have selected the last item"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.data,
|
||||
Some(default_data),
|
||||
"Last item has no own resolve data and should inherit the default one"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.commit_characters,
|
||||
Some(default_commit_characters),
|
||||
"Last item had no own commit characters and should inherit the default ones"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.text_edit,
|
||||
Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
|
||||
range: default_edit_range,
|
||||
new_text: "vec![2]".to_string()
|
||||
})),
|
||||
"Last item had no own edit range and should inherit the default one"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.insert_text_format,
|
||||
Some(lsp::InsertTextFormat::PLAIN_TEXT),
|
||||
"Last item should bring its own insert text format for resolving"
|
||||
);
|
||||
assert_eq!(
|
||||
item_to_resolve.insert_text_mode,
|
||||
Some(default_insert_text_mode),
|
||||
"Last item had no own insert text mode and should inherit the default one"
|
||||
);
|
||||
|
||||
Ok(item_to_resolve)
|
||||
}
|
||||
}
|
||||
}
|
||||
}).detach();
|
||||
|
||||
let completion_data = default_data.clone();
|
||||
let completion_characters = default_commit_characters.clone();
|
||||
cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
|
||||
let default_data = completion_data.clone();
|
||||
let default_commit_characters = completion_characters.clone();
|
||||
let items = items.clone();
|
||||
async move {
|
||||
Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
|
||||
items,
|
||||
items: vec![
|
||||
lsp::CompletionItem {
|
||||
label: "Some(2)".into(),
|
||||
insert_text: Some("Some(2)".into()),
|
||||
data: Some(json!({ "very": "special"})),
|
||||
insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
|
||||
text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
|
||||
lsp::InsertReplaceEdit {
|
||||
new_text: "Some(2)".to_string(),
|
||||
insert: lsp::Range::default(),
|
||||
replace: lsp::Range::default(),
|
||||
},
|
||||
)),
|
||||
..lsp::CompletionItem::default()
|
||||
},
|
||||
lsp::CompletionItem {
|
||||
label: "vec![2]".into(),
|
||||
insert_text: Some("vec![2]".into()),
|
||||
insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
|
||||
..lsp::CompletionItem::default()
|
||||
},
|
||||
],
|
||||
item_defaults: Some(lsp::CompletionListItemDefaults {
|
||||
data: Some(default_data.clone()),
|
||||
commit_characters: Some(default_commit_characters.clone()),
|
||||
@@ -10888,21 +10951,6 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
|
||||
.next()
|
||||
.await;
|
||||
|
||||
let resolved_items = Arc::new(Mutex::new(Vec::new()));
|
||||
cx.lsp
|
||||
.server
|
||||
.on_request::<lsp::request::ResolveCompletionItem, _, _>({
|
||||
let closure_resolved_items = resolved_items.clone();
|
||||
move |item_to_resolve, _| {
|
||||
let closure_resolved_items = closure_resolved_items.clone();
|
||||
async move {
|
||||
closure_resolved_items.lock().push(item_to_resolve.clone());
|
||||
Ok(item_to_resolve)
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.condition(|editor, _| editor.context_menu_visible())
|
||||
.await;
|
||||
cx.run_until_parked();
|
||||
@@ -10911,56 +10959,39 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo
|
||||
match menu.as_ref().expect("should have the completions menu") {
|
||||
CodeContextMenu::Completions(completions_menu) => {
|
||||
assert_eq!(
|
||||
completions_menu
|
||||
.entries
|
||||
.iter()
|
||||
.flat_map(|c| match c {
|
||||
CompletionEntry::Match(mat) => Some(mat.string.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<String>>(),
|
||||
items_out
|
||||
.iter()
|
||||
.map(|completion| completion.label.clone())
|
||||
.collect::<Vec<String>>()
|
||||
completion_menu_entries(&completions_menu.entries),
|
||||
vec!["Some(2)", "vec![2]"]
|
||||
);
|
||||
}
|
||||
CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
|
||||
}
|
||||
});
|
||||
// Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
|
||||
// with 4 from the end.
|
||||
assert_eq!(
|
||||
*resolved_items.lock(),
|
||||
[
|
||||
&items_out[0..16],
|
||||
&items_out[items_out.len() - 4..items_out.len()]
|
||||
]
|
||||
.concat()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<lsp::CompletionItem>>()
|
||||
resolve_requests_number.load(atomic::Ordering::Acquire),
|
||||
1,
|
||||
"While there are 2 items in the completion list, only 1 resolve request should have been sent, for the selected item"
|
||||
);
|
||||
resolved_items.lock().clear();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.context_menu_prev(&ContextMenuPrev, cx);
|
||||
editor.context_menu_first(&ContextMenuFirst, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
// Completions that have already been resolved are skipped.
|
||||
assert_eq!(
|
||||
*resolved_items.lock(),
|
||||
[
|
||||
// Selected item is always resolved even if it was resolved before.
|
||||
&items_out[items_out.len() - 1..items_out.len()],
|
||||
&items_out[items_out.len() - 16..items_out.len() - 4]
|
||||
]
|
||||
.concat()
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect::<Vec<lsp::CompletionItem>>()
|
||||
resolve_requests_number.load(atomic::Ordering::Acquire),
|
||||
2,
|
||||
"After re-selecting the first item, another resolve request should have been sent"
|
||||
);
|
||||
|
||||
expect_first_item.store(false, atomic::Ordering::Release);
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.context_menu_last(&ContextMenuLast, cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
resolve_requests_number.load(atomic::Ordering::Acquire),
|
||||
3,
|
||||
"After selecting the other item, another resolve request should have been sent"
|
||||
);
|
||||
resolved_items.lock().clear();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
||||
@@ -1032,7 +1032,6 @@ impl EditorElement {
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
em_width: Pixels,
|
||||
em_advance: Pixels,
|
||||
autoscroll_containing_element: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<CursorLayout> {
|
||||
@@ -1059,7 +1058,7 @@ impl EditorElement {
|
||||
let mut block_width =
|
||||
cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x;
|
||||
if block_width == Pixels::ZERO {
|
||||
block_width = em_advance;
|
||||
block_width = em_width;
|
||||
}
|
||||
let block_text = if let CursorShape::Block = selection.cursor_shape {
|
||||
snapshot
|
||||
@@ -6247,7 +6246,6 @@ impl Element for EditorElement {
|
||||
scroll_pixel_position,
|
||||
line_height,
|
||||
em_width,
|
||||
em_advance,
|
||||
autoscroll_containing_element,
|
||||
cx,
|
||||
);
|
||||
|
||||
@@ -429,7 +429,7 @@ fn show_hover(
|
||||
})
|
||||
.or_else(|| {
|
||||
let snapshot = &snapshot.buffer_snapshot;
|
||||
let offset_range = snapshot.syntax_ancestor(anchor..anchor)?.1;
|
||||
let offset_range = snapshot.range_for_syntax_ancestor(anchor..anchor)?;
|
||||
Some(
|
||||
snapshot.anchor_before(offset_range.start)
|
||||
..snapshot.anchor_after(offset_range.end),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::Editor;
|
||||
use collections::HashMap;
|
||||
use gpui::{Model, WindowContext};
|
||||
use language::Buffer;
|
||||
use language::Language;
|
||||
@@ -22,7 +20,6 @@ where
|
||||
return None;
|
||||
};
|
||||
let multibuffer = editor.buffer().read(cx);
|
||||
let mut language_servers_for = HashMap::default();
|
||||
editor
|
||||
.selections
|
||||
.disjoint_anchors()
|
||||
@@ -31,36 +28,27 @@ where
|
||||
.filter_map(|selection| Some((selection.start.buffer_id?, selection.start)))
|
||||
.filter_map(|(buffer_id, trigger_anchor)| {
|
||||
let buffer = multibuffer.buffer(buffer_id)?;
|
||||
let server_id = *match language_servers_for.entry(buffer_id) {
|
||||
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
|
||||
Entry::Vacant(vacant_entry) => {
|
||||
let language_server_id = project
|
||||
.read(cx)
|
||||
.language_servers_for_local_buffer(buffer.read(cx), cx)
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == language_server_name {
|
||||
Some(server.server_id())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
vacant_entry.insert(language_server_id)
|
||||
}
|
||||
}
|
||||
.as_ref()?;
|
||||
|
||||
Some((buffer, trigger_anchor, server_id))
|
||||
})
|
||||
.find_map(|(buffer, trigger_anchor, server_id)| {
|
||||
let language = buffer.read(cx).language_at(trigger_anchor.text_anchor)?;
|
||||
if !filter_language(&language) {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&language),
|
||||
server_id,
|
||||
buffer.clone(),
|
||||
))
|
||||
Some((trigger_anchor, language, buffer))
|
||||
})
|
||||
.find_map(|(trigger_anchor, language, buffer)| {
|
||||
project
|
||||
.read(cx)
|
||||
.language_servers_for_local_buffer(buffer.read(cx), cx)
|
||||
.find_map(|(adapter, server)| {
|
||||
if adapter.name.0.as_ref() == language_server_name {
|
||||
Some((
|
||||
trigger_anchor,
|
||||
Arc::clone(&language),
|
||||
server.server_id(),
|
||||
buffer.clone(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ impl FileIcons {
|
||||
pub fn get_icon(path: &Path, cx: &AppContext) -> Option<SharedString> {
|
||||
let this = cx.try_global::<Self>()?;
|
||||
|
||||
// TODO: Associate a type with the languages and have the file's language
|
||||
// override these associations
|
||||
// FIXME: Associate a type with the languages and have the file's language
|
||||
// override these associations
|
||||
maybe!({
|
||||
let suffix = path.icon_stem_or_suffix()?;
|
||||
|
||||
|
||||
@@ -1,16 +1,4 @@
|
||||
use anyhow::Result;
|
||||
use collections::HashMap;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use git::repository::GitFileStatus;
|
||||
use gpui::{
|
||||
actions, prelude::*, uniform_list, Action, AppContext, AsyncWindowContext, ClickEvent,
|
||||
CursorStyle, EventEmitter, FocusHandle, FocusableView, KeyContext,
|
||||
ListHorizontalSizingBehavior, ListSizingBehavior, Model, Modifiers, ModifiersChangedEvent,
|
||||
MouseButton, Stateful, Task, UniformListScrollHandle, View, WeakView,
|
||||
};
|
||||
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use std::{
|
||||
cell::OnceCell,
|
||||
collections::HashSet,
|
||||
@@ -20,10 +8,19 @@ use std::{
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use git::repository::GitFileStatus;
|
||||
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::*;
|
||||
use project::{Entry, EntryKind, Fs, Project, ProjectEntryId, WorktreeId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings as _;
|
||||
use ui::{
|
||||
prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Scrollbar, ScrollbarState, Tooltip,
|
||||
};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use workspace::dock::{DockPosition, Panel, PanelEvent};
|
||||
use workspace::Workspace;
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
black, bounce, div, ease_in_out, percentage, prelude::*, px, rgb, size, svg, Animation,
|
||||
AnimationExt as _, App, AppContext, AssetSource, Bounds, SharedString, Transformation,
|
||||
ViewContext, WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct Assets {}
|
||||
|
||||
@@ -42,7 +37,7 @@ impl Render for AnimationExample {
|
||||
div()
|
||||
.flex()
|
||||
.bg(rgb(0x2e7d32))
|
||||
.size(px(300.0))
|
||||
.size(Length::Definite(Pixels(300.0).into()))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, size, App, AppContext, Bounds, SharedString, ViewContext,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct HelloWorld {
|
||||
text: SharedString,
|
||||
@@ -14,7 +11,7 @@ impl Render for HelloWorld {
|
||||
.flex_col()
|
||||
.gap_3()
|
||||
.bg(rgb(0x505050))
|
||||
.size(px(500.0))
|
||||
.size(Length::Definite(Pixels(500.0).into()))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
actions, div, img, prelude::*, px, rgb, size, App, AppContext, AssetSource, Bounds,
|
||||
ImageSource, KeyBinding, Menu, MenuItem, Point, SharedString, SharedUri, TitlebarOptions,
|
||||
ViewContext, WindowBounds, WindowContext, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
use std::fs;
|
||||
|
||||
struct Assets {
|
||||
base: PathBuf,
|
||||
@@ -60,7 +55,7 @@ impl RenderOnce for ImageContainer {
|
||||
.size_full()
|
||||
.gap_4()
|
||||
.child(self.text)
|
||||
.child(img(self.src).size(px(256.0))),
|
||||
.child(img(self.src).w(px(256.0)).h(px(256.0))),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -80,7 +75,7 @@ impl Render for ImageShowcase {
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.gap_8()
|
||||
.bg(rgb(0xffffff))
|
||||
.bg(rgb(0xFFFFFF))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use gpui::{
|
||||
actions, black, div, fill, hsla, opaque_grey, point, prelude::*, px, relative, rgb, rgba, size,
|
||||
white, yellow, App, AppContext, Bounds, ClipboardItem, CursorStyle, ElementId,
|
||||
ElementInputHandler, FocusHandle, FocusableView, GlobalElementId, KeyBinding, Keystroke,
|
||||
LayoutId, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point,
|
||||
ShapedLine, SharedString, Style, TextRun, UTF16Selection, UnderlineStyle, View, ViewContext,
|
||||
ViewInputHandler, WindowBounds, WindowContext, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
use unicode_segmentation::*;
|
||||
|
||||
actions!(
|
||||
@@ -470,7 +463,7 @@ impl Element for TextElement {
|
||||
bounds.bottom(),
|
||||
),
|
||||
),
|
||||
rgba(0x3311ff30),
|
||||
rgba(0x3311FF30),
|
||||
)),
|
||||
None,
|
||||
)
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use std::{fs, path::PathBuf, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
div, hsla, img, point, prelude::*, px, rgb, size, svg, App, AppContext, AssetSource, Bounds,
|
||||
BoxShadow, ClickEvent, SharedString, Task, Timer, ViewContext, WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct Assets {
|
||||
base: PathBuf,
|
||||
@@ -80,7 +76,7 @@ impl Render for HelloWorld {
|
||||
.flex()
|
||||
.flex_row()
|
||||
.size_full()
|
||||
.bg(rgb(0xe0e0e0))
|
||||
.bg(rgb(0xE0E0E0))
|
||||
.text_xl()
|
||||
.child(
|
||||
div()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use gpui::{
|
||||
actions, div, prelude::*, rgb, App, AppContext, Menu, MenuItem, ViewContext, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct SetMenus;
|
||||
|
||||
|
||||
@@ -1,574 +1,22 @@
|
||||
use gpui::{
|
||||
div, hsla, point, prelude::*, px, relative, rgb, size, App, AppContext, Bounds, BoxShadow, Div,
|
||||
SharedString, ViewContext, WindowBounds, WindowOptions,
|
||||
};
|
||||
|
||||
use smallvec::smallvec;
|
||||
use gpui::*;
|
||||
|
||||
struct Shadow {}
|
||||
|
||||
impl Shadow {
|
||||
fn base() -> Div {
|
||||
div()
|
||||
.size_16()
|
||||
.bg(rgb(0xffffff))
|
||||
.rounded_full()
|
||||
.border_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
|
||||
}
|
||||
|
||||
fn square() -> Div {
|
||||
div()
|
||||
.size_16()
|
||||
.bg(rgb(0xffffff))
|
||||
.border_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
|
||||
}
|
||||
|
||||
fn rounded_small() -> Div {
|
||||
div()
|
||||
.size_16()
|
||||
.bg(rgb(0xffffff))
|
||||
.rounded(px(4.))
|
||||
.border_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
|
||||
}
|
||||
|
||||
fn rounded_medium() -> Div {
|
||||
div()
|
||||
.size_16()
|
||||
.bg(rgb(0xffffff))
|
||||
.rounded(px(8.))
|
||||
.border_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
|
||||
}
|
||||
|
||||
fn rounded_large() -> Div {
|
||||
div()
|
||||
.size_16()
|
||||
.bg(rgb(0xffffff))
|
||||
.rounded(px(12.))
|
||||
.border_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 0.1))
|
||||
}
|
||||
}
|
||||
|
||||
fn example(label: impl Into<SharedString>, example: impl IntoElement) -> impl IntoElement {
|
||||
let label = label.into();
|
||||
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.w(relative(1. / 6.))
|
||||
.border_r_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.flex_1()
|
||||
.py_12()
|
||||
.child(example),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.border_t_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.p_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.child(label),
|
||||
)
|
||||
}
|
||||
|
||||
impl Render for Shadow {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.id("shadow-example")
|
||||
.overflow_y_scroll()
|
||||
.flex()
|
||||
.bg(rgb(0xffffff))
|
||||
.size_full()
|
||||
.text_xs()
|
||||
.child(div().flex().flex_col().w_full().children(vec![
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.flex_row()
|
||||
.children(vec![
|
||||
example(
|
||||
"Square",
|
||||
Shadow::square()
|
||||
.shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded 4",
|
||||
Shadow::rounded_small()
|
||||
.shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded 8",
|
||||
Shadow::rounded_medium()
|
||||
.shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded 16",
|
||||
Shadow::rounded_large()
|
||||
.shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Circle",
|
||||
Shadow::base()
|
||||
.shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.w_full()
|
||||
.children(vec![
|
||||
example("None", Shadow::base()),
|
||||
// Small shadow
|
||||
example("Small", Shadow::base().shadow_sm()),
|
||||
// Medium shadow
|
||||
example("Medium", Shadow::base().shadow_md()),
|
||||
// Large shadow
|
||||
example("Large", Shadow::base().shadow_lg()),
|
||||
example("Extra Large", Shadow::base().shadow_xl()),
|
||||
example("2X Large", Shadow::base().shadow_2xl()),
|
||||
]),
|
||||
// Horizontal list of increasing blur radii
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Blur 0",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(0.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Blur 2",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Blur 4",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(4.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Blur 8",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Blur 16",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(16.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Horizontal list of increasing spread radii
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Spread 0",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Spread 2",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Spread 4",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(4.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Spread 8",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(8.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Spread 16",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(16.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Square spread examples
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Square Spread 0",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Square Spread 8",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(8.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Square Spread 16",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(16.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Rounded large spread examples
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Rounded Large Spread 0",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Spread 8",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(8.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Spread 16",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.0, 0.0, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(16.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Directional shadows
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Left",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(-8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Right",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Top",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(-8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Bottom",
|
||||
Shadow::base().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Square directional shadows
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Square Left",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(-8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Square Right",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Square Top",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(-8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Square Bottom",
|
||||
Shadow::square().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Rounded large directional shadows
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Rounded Large Left",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(-8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Right",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(8.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Top",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(-8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Bottom",
|
||||
Shadow::rounded_large().shadow(smallvec![BoxShadow {
|
||||
color: hsla(0.0, 0.5, 0.5, 0.3),
|
||||
offset: point(px(0.), px(8.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(0.),
|
||||
}]),
|
||||
),
|
||||
]),
|
||||
// Multiple shadows for different shapes
|
||||
div()
|
||||
.border_b_1()
|
||||
.border_color(hsla(0.0, 0.0, 0.0, 1.0))
|
||||
.flex()
|
||||
.children(vec![
|
||||
example(
|
||||
"Circle Multiple",
|
||||
Shadow::base().shadow(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
|
||||
offset: point(px(0.), px(-12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
|
||||
offset: point(px(12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
|
||||
offset: point(px(0.), px(12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
|
||||
offset: point(px(-12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
]),
|
||||
),
|
||||
example(
|
||||
"Square Multiple",
|
||||
Shadow::square().shadow(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
|
||||
offset: point(px(0.), px(-12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
|
||||
offset: point(px(12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
|
||||
offset: point(px(0.), px(12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
|
||||
offset: point(px(-12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
]),
|
||||
),
|
||||
example(
|
||||
"Rounded Large Multiple",
|
||||
Shadow::rounded_large().shadow(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red
|
||||
offset: point(px(0.), px(-12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow
|
||||
offset: point(px(12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green
|
||||
offset: point(px(0.), px(12.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
BoxShadow {
|
||||
color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue
|
||||
offset: point(px(-12.), px(0.)),
|
||||
blur_radius: px(8.),
|
||||
spread_radius: px(2.),
|
||||
},
|
||||
]),
|
||||
),
|
||||
]),
|
||||
]))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.child(div().size_8().shadow_sm())
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new().run(|cx: &mut AppContext| {
|
||||
let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx);
|
||||
let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
@@ -577,7 +25,5 @@ fn main() {
|
||||
|cx| cx.new_view(|_cx| Shadow {}),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,337 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Shadow Examples</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 1);
|
||||
}
|
||||
.example {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: calc(100% / 6);
|
||||
border-right: 1px solid rgba(0, 0, 0, 1);
|
||||
}
|
||||
.box {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 48px 0;
|
||||
}
|
||||
.label {
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(0, 0, 0, 1);
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Shapes */
|
||||
.square {
|
||||
}
|
||||
.rounded-small {
|
||||
border-radius: 4px;
|
||||
}
|
||||
.rounded-medium {
|
||||
border-radius: 8px;
|
||||
}
|
||||
.rounded-large {
|
||||
border-radius: 12px;
|
||||
}
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Shadows */
|
||||
.shadow-sm {
|
||||
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.shadow-md {
|
||||
box-shadow:
|
||||
0 4px 6px -1px rgba(0, 0, 0, 0.1),
|
||||
0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.shadow-lg {
|
||||
box-shadow:
|
||||
0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
.shadow-xl {
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
.shadow-2xl {
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
/* Blur radii */
|
||||
.blur-0 {
|
||||
box-shadow: 0 8px 0 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.blur-2 {
|
||||
box-shadow: 0 8px 2px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.blur-4 {
|
||||
box-shadow: 0 8px 4px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.blur-8 {
|
||||
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.blur-16 {
|
||||
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Spread radii */
|
||||
.spread-0 {
|
||||
box-shadow: 0 8px 8px 0 rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.spread-2 {
|
||||
box-shadow: 0 8px 8px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.spread-4 {
|
||||
box-shadow: 0 8px 8px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.spread-8 {
|
||||
box-shadow: 0 8px 8px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.spread-16 {
|
||||
box-shadow: 0 8px 8px 16px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Directional shadows */
|
||||
.shadow-left {
|
||||
box-shadow: -8px 0 8px 0 rgba(128, 0, 128, 0.3);
|
||||
}
|
||||
.shadow-right {
|
||||
box-shadow: 8px 0 8px 0 rgba(128, 0, 128, 0.3);
|
||||
}
|
||||
.shadow-top {
|
||||
box-shadow: 0 -8px 8px 0 rgba(128, 0, 128, 0.3);
|
||||
}
|
||||
.shadow-bottom {
|
||||
box-shadow: 0 8px 8px 0 rgba(128, 0, 128, 0.3);
|
||||
}
|
||||
|
||||
/* Multiple shadows */
|
||||
.shadow-multiple {
|
||||
box-shadow:
|
||||
0 -12px 8px 2px rgba(255, 0, 0, 0.3),
|
||||
12px 0 8px 2px rgba(255, 255, 0, 0.3),
|
||||
0 12px 8px 2px rgba(0, 255, 0, 0.3),
|
||||
-12px 0 8px 2px rgba(0, 0, 255, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box square shadow-bottom"></div>
|
||||
<div class="label">Square</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-small shadow-bottom"></div>
|
||||
<div class="label">Rounded 4</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-medium shadow-bottom"></div>
|
||||
<div class="label">Rounded 8</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-bottom"></div>
|
||||
<div class="label">Rounded 16</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-bottom"></div>
|
||||
<div class="label">Circle</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box circle"></div>
|
||||
<div class="label">None</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-sm"></div>
|
||||
<div class="label">Small</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-md"></div>
|
||||
<div class="label">Medium</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-lg"></div>
|
||||
<div class="label">Large</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-xl"></div>
|
||||
<div class="label">Extra Large</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-2xl"></div>
|
||||
<div class="label">2X Large</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box circle blur-0"></div>
|
||||
<div class="label">Blur 0</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle blur-2"></div>
|
||||
<div class="label">Blur 2</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle blur-4"></div>
|
||||
<div class="label">Blur 4</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle blur-8"></div>
|
||||
<div class="label">Blur 8</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle blur-16"></div>
|
||||
<div class="label">Blur 16</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box circle spread-0"></div>
|
||||
<div class="label">Spread 0</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle spread-2"></div>
|
||||
<div class="label">Spread 2</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle spread-4"></div>
|
||||
<div class="label">Spread 4</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle spread-8"></div>
|
||||
<div class="label">Spread 8</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle spread-16"></div>
|
||||
<div class="label">Spread 16</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box square spread-0"></div>
|
||||
<div class="label">Square Spread 0</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square spread-8"></div>
|
||||
<div class="label">Square Spread 8</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square spread-16"></div>
|
||||
<div class="label">Square Spread 16</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box rounded-large spread-0"></div>
|
||||
<div class="label">Rounded Large Spread 0</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large spread-8"></div>
|
||||
<div class="label">Rounded Large Spread 8</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large spread-16"></div>
|
||||
<div class="label">Rounded Large Spread 16</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box circle shadow-left"></div>
|
||||
<div class="label">Left</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-right"></div>
|
||||
<div class="label">Right</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-top"></div>
|
||||
<div class="label">Top</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box circle shadow-bottom"></div>
|
||||
<div class="label">Bottom</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box square shadow-left"></div>
|
||||
<div class="label">Square Left</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square shadow-right"></div>
|
||||
<div class="label">Square Right</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square shadow-top"></div>
|
||||
<div class="label">Square Top</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square shadow-bottom"></div>
|
||||
<div class="label">Square Bottom</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-left"></div>
|
||||
<div class="label">Rounded Large Left</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-right"></div>
|
||||
<div class="label">Rounded Large Right</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-top"></div>
|
||||
<div class="label">Rounded Large Top</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-bottom"></div>
|
||||
<div class="label">Rounded Large Bottom</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="example">
|
||||
<div class="box circle shadow-multiple"></div>
|
||||
<div class="label">Circle Multiple</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box square shadow-multiple"></div>
|
||||
<div class="label">Square Multiple</div>
|
||||
</div>
|
||||
<div class="example">
|
||||
<div class="box rounded-large shadow-multiple"></div>
|
||||
<div class="label">Rounded Large Multiple</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,11 +1,7 @@
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, size, svg, App, AppContext, AssetSource, Bounds, SharedString,
|
||||
ViewContext, WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
use std::fs;
|
||||
|
||||
struct Assets {
|
||||
base: PathBuf,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, size, App, AppContext, Bounds, ViewContext, WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct HelloWorld {}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, size, uniform_list, App, AppContext, Bounds, ViewContext,
|
||||
WindowBounds, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct UniformListExample {}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, size, App, AppContext, Bounds, SharedString, Timer, ViewContext,
|
||||
WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder as _;
|
||||
|
||||
struct SubWindow {
|
||||
custom_titlebar: bool,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
use gpui::{
|
||||
div, point, prelude::*, px, rgb, App, AppContext, Bounds, DisplayId, Hsla, Pixels,
|
||||
SharedString, Size, ViewContext, WindowBackgroundAppearance, WindowBounds, WindowKind,
|
||||
WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
|
||||
struct WindowContent {
|
||||
text: SharedString,
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use gpui::{
|
||||
black, canvas, div, green, point, prelude::*, px, rgb, size, transparent_black, white, App,
|
||||
AppContext, Bounds, CursorStyle, Decorations, Hsla, MouseButton, Pixels, Point, ResizeEdge,
|
||||
Size, ViewContext, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowOptions,
|
||||
};
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder;
|
||||
|
||||
struct WindowShadow {}
|
||||
|
||||
// Things to do:
|
||||
// 1. We need a way of calculating which edge or corner the mouse is on,
|
||||
// and then dispatch on that
|
||||
// 2. We need to improve the shadow rendering significantly
|
||||
// 3. We need to implement the techniques in here in Zed
|
||||
/*
|
||||
Things to do:
|
||||
1. We need a way of calculating which edge or corner the mouse is on,
|
||||
and then dispatch on that
|
||||
2. We need to improve the shadow rendering significantly
|
||||
3. We need to implement the techniques in here in Zed
|
||||
*/
|
||||
|
||||
impl Render for WindowShadow {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
@@ -129,7 +128,7 @@ impl Render for WindowShadow {
|
||||
div()
|
||||
.flex()
|
||||
.bg(white())
|
||||
.size(px(300.0))
|
||||
.size(Length::Definite(Pixels(300.0).into()))
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.shadow_lg()
|
||||
|
||||
@@ -420,9 +420,6 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||
fn get_raw_handle(&self) -> windows::HWND;
|
||||
|
||||
// Linux specific methods
|
||||
fn inner_window_bounds(&self) -> WindowBounds {
|
||||
self.window_bounds()
|
||||
}
|
||||
fn request_decorations(&self, _decorations: WindowDecorations) {}
|
||||
fn show_window_menu(&self, _position: Point<Pixels>) {}
|
||||
fn start_window_move(&self) {}
|
||||
|
||||
@@ -651,7 +651,7 @@ impl CursorStyle {
|
||||
// and https://github.com/KDE/breeze (KDE). Both of them seem to be also derived from
|
||||
// Web CSS cursor names: https://developer.mozilla.org/en-US/docs/Web/CSS/cursor#values
|
||||
match self {
|
||||
CursorStyle::Arrow => "left_ptr",
|
||||
CursorStyle::Arrow => "arrow",
|
||||
CursorStyle::IBeam => "text",
|
||||
CursorStyle::Crosshair => "crosshair",
|
||||
CursorStyle::ClosedHand => "grabbing",
|
||||
|
||||
@@ -781,19 +781,6 @@ impl PlatformWindow for WaylandWindow {
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_window_bounds(&self) -> WindowBounds {
|
||||
let state = self.borrow();
|
||||
if state.fullscreen {
|
||||
WindowBounds::Fullscreen(state.window_bounds)
|
||||
} else if state.maximized {
|
||||
WindowBounds::Maximized(state.window_bounds)
|
||||
} else {
|
||||
let inset = state.inset.unwrap_or(px(0.));
|
||||
drop(state);
|
||||
WindowBounds::Windowed(self.bounds().inset(inset))
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
self.borrow().bounds.size
|
||||
}
|
||||
|
||||
@@ -1100,30 +1100,6 @@ impl PlatformWindow for X11Window {
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_window_bounds(&self) -> WindowBounds {
|
||||
let state = self.0.state.borrow();
|
||||
if self.is_maximized() {
|
||||
WindowBounds::Maximized(state.bounds)
|
||||
} else {
|
||||
let mut bounds = state.bounds;
|
||||
let [left, right, top, bottom] = state.last_insets;
|
||||
|
||||
let [left, right, top, bottom] = [
|
||||
Pixels((left as f32) / state.scale_factor),
|
||||
Pixels((right as f32) / state.scale_factor),
|
||||
Pixels((top as f32) / state.scale_factor),
|
||||
Pixels((bottom as f32) / state.scale_factor),
|
||||
];
|
||||
|
||||
bounds.origin.x += left;
|
||||
bounds.origin.y += top;
|
||||
bounds.size.width -= left + right;
|
||||
bounds.size.height -= top + bottom;
|
||||
|
||||
WindowBounds::Windowed(bounds)
|
||||
}
|
||||
}
|
||||
|
||||
fn content_size(&self) -> Size<Pixels> {
|
||||
// We divide by the scale factor here because this value is queried to determine how much to draw,
|
||||
// but it will be multiplied later by the scale to adjust for scaling.
|
||||
|
||||
@@ -180,13 +180,14 @@ vertex ShadowVertexOutput shadow_vertex(
|
||||
float2 unit_vertex = unit_vertices[unit_vertex_id];
|
||||
Shadow shadow = shadows[shadow_id];
|
||||
|
||||
// Calculate the expanded bounds
|
||||
float expansion = max(3. * shadow.blur_radius, 1.0);
|
||||
float margin = 3. * shadow.blur_radius;
|
||||
// Set the bounds of the shadow and adjust its size based on the shadow's
|
||||
// spread radius to achieve the spreading effect
|
||||
Bounds_ScaledPixels bounds = shadow.bounds;
|
||||
bounds.origin.x -= expansion;
|
||||
bounds.origin.y -= expansion;
|
||||
bounds.size.width += 2. * expansion;
|
||||
bounds.size.height += 2. * expansion;
|
||||
bounds.origin.x -= margin;
|
||||
bounds.origin.y -= margin;
|
||||
bounds.size.width += 2. * margin;
|
||||
bounds.size.height += 2. * margin;
|
||||
|
||||
float4 device_position =
|
||||
to_device_position(unit_vertex, bounds, viewport_size);
|
||||
@@ -204,32 +205,46 @@ vertex ShadowVertexOutput shadow_vertex(
|
||||
fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
|
||||
constant Shadow *shadows
|
||||
[[buffer(ShadowInputIndex_Shadows)]]) {
|
||||
Shadow shadow = shadows[input.shadow_id];
|
||||
Shadow shadow = shadows[input.shadow_id];
|
||||
|
||||
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
|
||||
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
|
||||
float2 half_size = size / 2.;
|
||||
float2 center = origin + half_size;
|
||||
float2 point = input.position.xy - center;
|
||||
float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
|
||||
float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
|
||||
float2 half_size = size / 2.;
|
||||
float2 center = origin + half_size;
|
||||
float2 point = input.position.xy - center;
|
||||
float corner_radius;
|
||||
if (point.x < 0.) {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_left;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_left;
|
||||
}
|
||||
} else {
|
||||
if (point.y < 0.) {
|
||||
corner_radius = shadow.corner_radii.top_right;
|
||||
} else {
|
||||
corner_radius = shadow.corner_radii.bottom_right;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate distance from the edge of the shape
|
||||
float2 d = abs(point) - half_size + shadow.corner_radii.top_left;
|
||||
float corner_distance = length(max(d, 0.)) + min(max(d.x, d.y), 0.);
|
||||
float distance = corner_distance - shadow.corner_radii.top_left;
|
||||
// The signal is only non-zero in a limited range, so don't waste samples
|
||||
float low = point.y - half_size.y;
|
||||
float high = point.y + half_size.y;
|
||||
float start = clamp(-3. * shadow.blur_radius, low, high);
|
||||
float end = clamp(3. * shadow.blur_radius, low, high);
|
||||
|
||||
// Apply spread (reduced effect and maintaining circular shape)
|
||||
float spread_factor = 0.5; // Adjust this to fine-tune the spread effect
|
||||
distance -= shadow.spread_radius * spread_factor;
|
||||
distance = length(max(float2(distance, 0.), 0.)) + min(distance, 0.);
|
||||
// Accumulate samples (we can get away with surprisingly few samples)
|
||||
float step = (end - start) / 4.;
|
||||
float y = start + step * 0.5;
|
||||
float alpha = 0.;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
|
||||
corner_radius, half_size) *
|
||||
gaussian(y, shadow.blur_radius) * step;
|
||||
y += step;
|
||||
}
|
||||
|
||||
// Improved blur calculation
|
||||
float blur_amount = shadow.blur_radius * 0.5;
|
||||
float alpha = smoothstep(blur_amount, -blur_amount, distance);
|
||||
|
||||
// Apply a smoother falloff
|
||||
alpha = pow(alpha, 1.3);
|
||||
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
return input.color * float4(1., 1., 1., alpha);
|
||||
}
|
||||
|
||||
struct UnderlineVertexOutput {
|
||||
|
||||
@@ -357,7 +357,7 @@ impl From<ImageFormat> for image::ImageFormat {
|
||||
ImageFormat::Jpeg => image::ImageFormat::Jpeg,
|
||||
ImageFormat::Webp => image::ImageFormat::WebP,
|
||||
ImageFormat::Gif => image::ImageFormat::Gif,
|
||||
// TODO: ImageFormat::Svg
|
||||
// ImageFormat::Svg => todo!(),
|
||||
ImageFormat::Bmp => image::ImageFormat::Bmp,
|
||||
ImageFormat::Tiff => image::ImageFormat::Tiff,
|
||||
_ => unreachable!(),
|
||||
|
||||
@@ -493,12 +493,10 @@ impl From<Underline> for Primitive {
|
||||
pub(crate) struct Shadow {
|
||||
pub order: DrawOrder,
|
||||
pub blur_radius: ScaledPixels,
|
||||
pub spread_radius: ScaledPixels,
|
||||
pub bounds: Bounds<ScaledPixels>,
|
||||
pub corner_radii: Corners<ScaledPixels>,
|
||||
pub content_mask: ContentMask<ScaledPixels>,
|
||||
pub color: Hsla,
|
||||
pub offset: Point<ScaledPixels>,
|
||||
}
|
||||
|
||||
impl From<Shadow> for Primitive {
|
||||
|
||||
@@ -999,11 +999,6 @@ impl<'a> WindowContext<'a> {
|
||||
self.window.platform_window.window_bounds()
|
||||
}
|
||||
|
||||
/// Return the `WindowBounds` excluding insets (Wayland and X11)
|
||||
pub fn inner_window_bounds(&self) -> WindowBounds {
|
||||
self.window.platform_window.inner_window_bounds()
|
||||
}
|
||||
|
||||
/// Dispatch the given action on the currently focused element.
|
||||
pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
|
||||
let focus_handle = self.focused();
|
||||
@@ -2286,8 +2281,6 @@ impl<'a> WindowContext<'a> {
|
||||
content_mask: content_mask.scale(scale_factor),
|
||||
corner_radii: corner_radii.scale(scale_factor),
|
||||
color: shadow.color.opacity(opacity),
|
||||
spread_radius: shadow.spread_radius.scale(scale_factor),
|
||||
offset: shadow.offset.scale(scale_factor),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ pub fn box_shadow_style_methods(input: TokenStream) -> TokenStream {
|
||||
|
||||
self.style().box_shadow = Some(smallvec![
|
||||
BoxShadow {
|
||||
color: hsla(0., 0., 0., 0.1),
|
||||
color: hsla(0.5, 0., 0., 0.1),
|
||||
offset: point(px(0.), px(4.)),
|
||||
blur_radius: px(6.),
|
||||
spread_radius: px(-1.),
|
||||
|
||||
@@ -68,7 +68,7 @@ pub use text::{
|
||||
use theme::SyntaxTheme;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use util::RandomCharIter;
|
||||
use util::{debug_panic, maybe, RangeExt};
|
||||
use util::{debug_panic, RangeExt};
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub use {tree_sitter_rust, tree_sitter_typescript};
|
||||
@@ -2923,13 +2923,10 @@ impl BufferSnapshot {
|
||||
(start..end, word_kind)
|
||||
}
|
||||
|
||||
/// Returns the closest syntax node enclosing the given range.
|
||||
pub fn syntax_ancestor<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
) -> Option<tree_sitter::Node<'a>> {
|
||||
/// Returns the range for the closes syntax node enclosing the given range.
|
||||
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let mut result: Option<tree_sitter::Node<'a>> = None;
|
||||
let mut result: Option<Range<usize>> = None;
|
||||
'outer: for layer in self
|
||||
.syntax
|
||||
.layers_for_range(range.clone(), &self.text, true)
|
||||
@@ -2959,7 +2956,7 @@ impl BufferSnapshot {
|
||||
}
|
||||
|
||||
let left_node = cursor.node();
|
||||
let mut layer_result = left_node;
|
||||
let mut layer_result = left_node.byte_range();
|
||||
|
||||
// For an empty range, try to find another node immediately to the right of the range.
|
||||
if left_node.end_byte() == range.start {
|
||||
@@ -2982,13 +2979,13 @@ impl BufferSnapshot {
|
||||
// If both nodes are the same in that regard, favor the right one.
|
||||
if let Some(right_node) = right_node {
|
||||
if right_node.is_named() || !left_node.is_named() {
|
||||
layer_result = right_node;
|
||||
layer_result = right_node.byte_range();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(previous_result) = &result {
|
||||
if previous_result.byte_range().len() < layer_result.byte_range().len() {
|
||||
if previous_result.len() < layer_result.len() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -3031,48 +3028,6 @@ impl BufferSnapshot {
|
||||
Some(items)
|
||||
}
|
||||
|
||||
pub fn outline_range_containing<T: ToOffset>(&self, range: Range<T>) -> Option<Range<Point>> {
|
||||
let range = range.to_offset(self);
|
||||
let mut matches = self.syntax.matches(range.clone(), &self.text, |grammar| {
|
||||
grammar.outline_config.as_ref().map(|c| &c.query)
|
||||
});
|
||||
let configs = matches
|
||||
.grammars()
|
||||
.iter()
|
||||
.map(|g| g.outline_config.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
while let Some(mat) = matches.peek() {
|
||||
let config = &configs[mat.grammar_index];
|
||||
let containing_item_node = maybe!({
|
||||
let item_node = mat.captures.iter().find_map(|cap| {
|
||||
if cap.index == config.item_capture_ix {
|
||||
Some(cap.node)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
let item_byte_range = item_node.byte_range();
|
||||
if item_byte_range.end < range.start || item_byte_range.start > range.end {
|
||||
None
|
||||
} else {
|
||||
Some(item_node)
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(item_node) = containing_item_node {
|
||||
return Some(
|
||||
Point::from_ts_point(item_node.start_position())
|
||||
..Point::from_ts_point(item_node.end_position()),
|
||||
);
|
||||
}
|
||||
|
||||
matches.advance();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn outline_items_containing<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
|
||||
@@ -1104,32 +1104,20 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
|
||||
let snapshot = buffer.snapshot();
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.syntax_ancestor(empty_range_at(text, "|"))
|
||||
.unwrap()
|
||||
.byte_range(),
|
||||
range_of(text, "|")
|
||||
snapshot.range_for_syntax_ancestor(empty_range_at(text, "|")),
|
||||
Some(range_of(text, "|"))
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.syntax_ancestor(range_of(text, "|"))
|
||||
.unwrap()
|
||||
.byte_range(),
|
||||
range_of(text, "|c|")
|
||||
snapshot.range_for_syntax_ancestor(range_of(text, "|")),
|
||||
Some(range_of(text, "|c|"))
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.syntax_ancestor(range_of(text, "|c|"))
|
||||
.unwrap()
|
||||
.byte_range(),
|
||||
range_of(text, "|c| {}")
|
||||
snapshot.range_for_syntax_ancestor(range_of(text, "|c|")),
|
||||
Some(range_of(text, "|c| {}"))
|
||||
);
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.syntax_ancestor(range_of(text, "|c| {}"))
|
||||
.unwrap()
|
||||
.byte_range(),
|
||||
range_of(text, "(|c| {})")
|
||||
snapshot.range_for_syntax_ancestor(range_of(text, "|c| {}")),
|
||||
Some(range_of(text, "(|c| {})"))
|
||||
);
|
||||
|
||||
buffer
|
||||
|
||||
@@ -78,7 +78,7 @@ pub use language_registry::{
|
||||
};
|
||||
pub use lsp::LanguageServerId;
|
||||
pub use outline::*;
|
||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, ToTreeSitterPoint, TreeSitterOptions};
|
||||
pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer, TreeSitterOptions};
|
||||
pub use text::{AnchorRangeExt, LineEnding};
|
||||
pub use tree_sitter::{Node, Parser, Tree, TreeCursor};
|
||||
|
||||
@@ -385,15 +385,6 @@ pub trait LspAdapter: 'static + Send + Sync {
|
||||
None
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
_version: &(dyn 'static + Send + Any),
|
||||
_container_dir: &PathBuf,
|
||||
_delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
@@ -525,23 +516,14 @@ async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>
|
||||
.fetch_latest_server_version(delegate.as_ref())
|
||||
.await?;
|
||||
|
||||
if let Some(binary) = adapter
|
||||
.check_if_version_installed(latest_version.as_ref(), &container_dir, delegate.as_ref())
|
||||
.await
|
||||
{
|
||||
log::info!("language server {:?} is already installed", name.0);
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::None);
|
||||
Ok(binary)
|
||||
} else {
|
||||
log::info!("downloading language server {:?}", name.0);
|
||||
delegate.update_status(adapter.name(), LanguageServerBinaryStatus::Downloading);
|
||||
let binary = adapter
|
||||
.fetch_server_binary(latest_version, container_dir, delegate.as_ref())
|
||||
.await;
|
||||
log::info!("downloading language server {:?}", name.0);
|
||||
delegate.update_status(adapter.name(), LanguageServerBinaryStatus::Downloading);
|
||||
let binary = adapter
|
||||
.fetch_server_binary(latest_version, container_dir, delegate.as_ref())
|
||||
.await;
|
||||
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::None);
|
||||
binary
|
||||
}
|
||||
delegate.update_status(name.clone(), LanguageServerBinaryStatus::None);
|
||||
binary
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
|
||||
@@ -1845,7 +1845,7 @@ impl Drop for QueryCursorHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToTreeSitterPoint {
|
||||
pub(crate) trait ToTreeSitterPoint {
|
||||
fn to_ts_point(self) -> tree_sitter::Point;
|
||||
fn from_ts_point(point: tree_sitter::Point) -> Self;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ pub struct CssLspAdapter {
|
||||
}
|
||||
|
||||
impl CssLspAdapter {
|
||||
const PACKAGE_NAME: &str = "vscode-langservers-extracted";
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
CssLspAdapter { node }
|
||||
}
|
||||
@@ -57,13 +56,18 @@ impl LspAdapter for CssLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let package_name = "vscode-langservers-extracted";
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::PACKAGE_NAME, latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
@@ -72,31 +76,6 @@ impl LspAdapter for CssLspAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
|
||||
@@ -64,8 +64,6 @@ pub struct JsonLspAdapter {
|
||||
}
|
||||
|
||||
impl JsonLspAdapter {
|
||||
const PACKAGE_NAME: &str = "vscode-langservers-extracted";
|
||||
|
||||
pub fn new(node: NodeRuntime, languages: Arc<LanguageRegistry>) -> Self {
|
||||
Self {
|
||||
node,
|
||||
@@ -144,36 +142,11 @@ impl LspAdapter for JsonLspAdapter {
|
||||
) -> Result<Box<dyn 'static + Send + Any>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version(Self::PACKAGE_NAME)
|
||||
.npm_package_latest_version("vscode-langservers-extracted")
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
@@ -182,13 +155,18 @@ impl LspAdapter for JsonLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let package_name = "vscode-langservers-extracted";
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::PACKAGE_NAME, latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
|
||||
@@ -117,48 +117,30 @@ impl LspAdapter for PythonLspAdapter {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
Self::SERVER_NAME.as_ref(),
|
||||
&server_path,
|
||||
&container_dir,
|
||||
&version,
|
||||
&latest_version,
|
||||
)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
|
||||
@@ -34,7 +34,6 @@ pub struct TailwindLspAdapter {
|
||||
impl TailwindLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName =
|
||||
LanguageServerName::new_static("tailwindcss-language-server");
|
||||
const PACKAGE_NAME: &str = "@tailwindcss/language-server";
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
TailwindLspAdapter { node }
|
||||
@@ -53,7 +52,7 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version(Self::PACKAGE_NAME)
|
||||
.npm_package_latest_version("@tailwindcss/language-server")
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
@@ -66,13 +65,18 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let package_name = "@tailwindcss/language-server";
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::PACKAGE_NAME, latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
@@ -81,31 +85,6 @@ impl LspAdapter for TailwindLspAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
|
||||
@@ -73,7 +73,6 @@ impl TypeScriptLspAdapter {
|
||||
const NEW_SERVER_PATH: &'static str = "node_modules/typescript-language-server/lib/cli.mjs";
|
||||
const SERVER_NAME: LanguageServerName =
|
||||
LanguageServerName::new_static("typescript-language-server");
|
||||
const PACKAGE_NAME: &str = "typescript";
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
TypeScriptLspAdapter { node }
|
||||
}
|
||||
@@ -115,36 +114,6 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
}) as Box<_>)
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<TypeScriptVersions>().unwrap();
|
||||
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
Self::PACKAGE_NAME,
|
||||
&server_path,
|
||||
&container_dir,
|
||||
version.typescript_version.as_str(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: typescript_server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
@@ -153,22 +122,32 @@ impl LspAdapter for TypeScriptLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<TypeScriptVersions>().unwrap();
|
||||
let server_path = container_dir.join(Self::NEW_SERVER_PATH);
|
||||
let package_name = "typescript";
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
package_name,
|
||||
&server_path,
|
||||
&container_dir,
|
||||
&[
|
||||
(
|
||||
Self::PACKAGE_NAME,
|
||||
latest_version.typescript_version.as_str(),
|
||||
),
|
||||
(
|
||||
"typescript-language-server",
|
||||
latest_version.server_version.as_str(),
|
||||
),
|
||||
],
|
||||
latest_version.typescript_version.as_str(),
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[
|
||||
(package_name, latest_version.typescript_version.as_str()),
|
||||
(
|
||||
"typescript-language-server",
|
||||
latest_version.server_version.as_str(),
|
||||
),
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
|
||||
@@ -31,7 +31,6 @@ pub struct YamlLspAdapter {
|
||||
|
||||
impl YamlLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("yaml-language-server");
|
||||
const PACKAGE_NAME: &str = "yaml-language-server";
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
YamlLspAdapter { node }
|
||||
}
|
||||
@@ -62,13 +61,18 @@ impl LspAdapter for YamlLspAdapter {
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let package_name = "yaml-language-server";
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::PACKAGE_NAME, latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
self.node
|
||||
.npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
@@ -77,31 +81,6 @@ impl LspAdapter for YamlLspAdapter {
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(Self::PACKAGE_NAME, &server_path, &container_dir, &version)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
|
||||
@@ -54,7 +54,7 @@ They can also be detected automatically, for example https://zed.dev/blog.
|
||||
## Images
|
||||
Images are like links, but with an exclamation mark `!` in front.
|
||||
|
||||
```markdown
|
||||
```todo!
|
||||

|
||||
```
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use assets::Assets;
|
||||
use gpui::{rgb, App, KeyBinding, Length, StyleRefinement, View, WindowOptions};
|
||||
use gpui::*;
|
||||
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
|
||||
use markdown::{Markdown, MarkdownStyle};
|
||||
use node_runtime::NodeRuntime;
|
||||
|
||||
@@ -39,7 +39,6 @@ smallvec.workspace = true
|
||||
sum_tree.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
tree-sitter.workspace = true
|
||||
util.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1527,13 +1527,21 @@ impl MultiBuffer {
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
|
||||
self.excerpts_for_buffer_id(buffer.read(cx).remote_id(), cx)
|
||||
}
|
||||
|
||||
pub fn excerpts_for_buffer_id(
|
||||
&self,
|
||||
buffer_id: BufferId,
|
||||
cx: &AppContext,
|
||||
) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
|
||||
let mut excerpts = Vec::new();
|
||||
let snapshot = self.read(cx);
|
||||
let buffers = self.buffers.borrow();
|
||||
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(&());
|
||||
for locator in buffers
|
||||
.get(&buffer.read(cx).remote_id())
|
||||
.get(&buffer_id)
|
||||
.map(|state| &state.excerpts)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -3920,16 +3928,15 @@ impl MultiBufferSnapshot {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn syntax_ancestor<T: ToOffset>(
|
||||
&self,
|
||||
range: Range<T>,
|
||||
) -> Option<(tree_sitter::Node, Range<usize>)> {
|
||||
pub fn range_for_syntax_ancestor<T: ToOffset>(&self, range: Range<T>) -> Option<Range<usize>> {
|
||||
let range = range.start.to_offset(self)..range.end.to_offset(self);
|
||||
let excerpt = self.excerpt_containing(range.clone())?;
|
||||
let node = excerpt
|
||||
|
||||
let ancestor_buffer_range = excerpt
|
||||
.buffer()
|
||||
.syntax_ancestor(excerpt.map_range_to_buffer(range))?;
|
||||
Some((node, excerpt.map_range_from_buffer(node.byte_range())))
|
||||
.range_for_syntax_ancestor(excerpt.map_range_to_buffer(range))?;
|
||||
|
||||
Some(excerpt.map_range_from_buffer(ancestor_buffer_range))
|
||||
}
|
||||
|
||||
pub fn outline(&self, theme: Option<&SyntaxTheme>) -> Option<Outline<Anchor>> {
|
||||
|
||||
@@ -1668,7 +1668,7 @@ impl BufferStore {
|
||||
.log_err();
|
||||
}
|
||||
|
||||
// TODO(max): do something
|
||||
// todo!(max): do something
|
||||
// client
|
||||
// .send(proto::UpdateStagedText {
|
||||
// project_id,
|
||||
|
||||
@@ -3142,7 +3142,7 @@ impl LspStore {
|
||||
fn request_workspace_config_refresh(&mut self) {
|
||||
*self._maintain_workspace_config.1.borrow_mut() = ();
|
||||
}
|
||||
|
||||
// todo!
|
||||
pub fn prettier_store(&self) -> Option<Model<PrettierStore>> {
|
||||
self.as_local().map(|local| local.prettier_store.clone())
|
||||
}
|
||||
|
||||
@@ -3200,7 +3200,7 @@ impl ProjectPanel {
|
||||
item_colors.default
|
||||
};
|
||||
|
||||
let bg_hover_color = if self.mouse_down || is_marked || is_active {
|
||||
let bg_hover_color = if self.mouse_down {
|
||||
item_colors.marked_active
|
||||
} else {
|
||||
item_colors.hover
|
||||
@@ -3224,7 +3224,9 @@ impl ProjectPanel {
|
||||
.border_1()
|
||||
.border_r_2()
|
||||
.border_color(border_color)
|
||||
.hover(|style| style.bg(bg_hover_color))
|
||||
.when(!is_marked && !is_active, |div| {
|
||||
div.hover(|style| style.bg(bg_hover_color))
|
||||
})
|
||||
.when(is_local, |div| {
|
||||
div.on_drag_move::<ExternalPaths>(cx.listener(
|
||||
move |this, event: &DragMoveEvent<ExternalPaths>, cx| {
|
||||
|
||||
@@ -515,7 +515,7 @@ impl project::ProjectItem for NotebookItem {
|
||||
Ok(nbformat::Notebook::V4(notebook)) => notebook,
|
||||
// 4.1 - 4.4 are converted to 4.5
|
||||
Ok(nbformat::Notebook::Legacy(legacy_notebook)) => {
|
||||
// TODO: Decide if we want to mutate the notebook by including Cell IDs
|
||||
// todo!(): Decide if we want to mutate the notebook by including Cell IDs
|
||||
// and any other conversions
|
||||
let notebook = nbformat::upgrade_legacy_notebook(legacy_notebook)?;
|
||||
notebook
|
||||
|
||||
@@ -57,7 +57,7 @@ impl OutputContent for MarkdownView {
|
||||
|
||||
fn buffer_content(&mut self, cx: &mut WindowContext) -> Option<Model<Buffer>> {
|
||||
let buffer = cx.new_model(|cx| {
|
||||
// TODO: Bring in the language registry so we can set the language to markdown
|
||||
// todo!(): Bring in the language registry so we can set the language to markdown
|
||||
let mut buffer = Buffer::local(self.raw_text.clone(), cx)
|
||||
.with_language(language::PLAIN_TEXT.clone(), cx);
|
||||
buffer.set_capability(language::Capability::ReadOnly, cx);
|
||||
|
||||
@@ -610,7 +610,7 @@ impl Session {
|
||||
|
||||
// Start a new kernel
|
||||
this.update(&mut cx, |session, cx| {
|
||||
// TODO: Differentiate between restart and restart+clear-outputs
|
||||
// todo!(): Differentiate between restart and restart+clear-outputs
|
||||
session.clear_outputs(cx);
|
||||
session.start_kernel(cx);
|
||||
})
|
||||
|
||||
@@ -1183,10 +1183,10 @@ impl Terminal {
|
||||
}
|
||||
|
||||
let motion: Option<ViMotion> = match key.as_str() {
|
||||
"h" | "left" => Some(ViMotion::Left),
|
||||
"j" | "down" => Some(ViMotion::Down),
|
||||
"k" | "up" => Some(ViMotion::Up),
|
||||
"l" | "right" => Some(ViMotion::Right),
|
||||
"h" => Some(ViMotion::Left),
|
||||
"j" => Some(ViMotion::Down),
|
||||
"k" => Some(ViMotion::Up),
|
||||
"l" => Some(ViMotion::Right),
|
||||
"w" => Some(ViMotion::WordRight),
|
||||
"b" if !keystroke.modifiers.control => Some(ViMotion::WordLeft),
|
||||
"e" => Some(ViMotion::WordRightEnd),
|
||||
|
||||
@@ -318,24 +318,21 @@ impl TitleBar {
|
||||
Some(
|
||||
ButtonLike::new("ssh-server-icon")
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Server)
|
||||
.size(IconSize::XSmall)
|
||||
.color(icon_color),
|
||||
Some(Indicator::dot().color(indicator_color)),
|
||||
)
|
||||
.indicator_border_color(Some(cx.theme().colors().title_bar_background))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.max_w_32()
|
||||
.child(
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Server)
|
||||
.size(IconSize::XSmall)
|
||||
.color(icon_color),
|
||||
Some(Indicator::dot().color(indicator_color)),
|
||||
)
|
||||
.indicator_border_color(Some(cx.theme().colors().title_bar_background))
|
||||
.into_any_element(),
|
||||
)
|
||||
.child(
|
||||
Label::new(nickname.clone())
|
||||
.size(LabelSize::Small)
|
||||
.text_ellipsis(),
|
||||
),
|
||||
.overflow_hidden()
|
||||
.text_ellipsis()
|
||||
.child(Label::new(nickname.clone()).size(LabelSize::Small)),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::with_meta("Remote Project", Some(&OpenRemote), meta.clone(), cx)
|
||||
|
||||
@@ -282,52 +282,6 @@ impl RenderOnce for Switch {
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Switch`] that has a [`Label`].
|
||||
#[derive(IntoElement)]
|
||||
pub struct SwitchWithLabel {
|
||||
id: ElementId,
|
||||
label: Label,
|
||||
checked: ToggleState,
|
||||
on_click: Arc<dyn Fn(&ToggleState, &mut WindowContext) + 'static>,
|
||||
}
|
||||
|
||||
impl SwitchWithLabel {
|
||||
pub fn new(
|
||||
id: impl Into<ElementId>,
|
||||
label: Label,
|
||||
checked: ToggleState,
|
||||
on_click: impl Fn(&ToggleState, &mut WindowContext) + 'static,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
label,
|
||||
checked,
|
||||
on_click: Arc::new(on_click),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for SwitchWithLabel {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap(DynamicSpacing::Base08.rems(cx))
|
||||
.child(Switch::new(self.id.clone(), self.checked).on_click({
|
||||
let on_click = self.on_click.clone();
|
||||
move |checked, cx| {
|
||||
(on_click)(checked, cx);
|
||||
}
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.id(SharedString::from(format!("{}-label", self.id)))
|
||||
.on_click(move |_event, cx| {
|
||||
(self.on_click)(&self.checked.inverse(), cx);
|
||||
})
|
||||
.child(self.label),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Checkbox {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
|
||||
@@ -453,32 +407,3 @@ impl ComponentPreview for CheckboxWithLabel {
|
||||
])]
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for SwitchWithLabel {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A switch with an associated label, allowing users to select an option while providing a descriptive text."
|
||||
}
|
||||
|
||||
fn examples(_: &mut WindowContext) -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Off",
|
||||
SwitchWithLabel::new(
|
||||
"switch_with_label_unselected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Unselected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"On",
|
||||
SwitchWithLabel::new(
|
||||
"switch_with_label_selected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Selected,
|
||||
|_, _| {},
|
||||
),
|
||||
),
|
||||
])]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use gpui::Axis;
|
||||
|
||||
use crate::prelude::*;
|
||||
use gpui::*;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct ToolStrip {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
//! It can't be located in the `ui` crate because it depends on `editor`.
|
||||
//!
|
||||
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use gpui::{AppContext, FocusHandle, FocusableView, FontStyle, Hsla, TextStyle, View};
|
||||
use editor::*;
|
||||
use gpui::*;
|
||||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::prelude::*;
|
||||
use ui::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum FieldLabelLayout {
|
||||
|
||||
@@ -24,7 +24,6 @@ futures-lite.workspace = true
|
||||
futures.workspace = true
|
||||
git2 = { workspace = true, optional = true }
|
||||
globset.workspace = true
|
||||
itertools.workspace = true
|
||||
log.workspace = true
|
||||
rand = { workspace = true, optional = true }
|
||||
regex.workspace = true
|
||||
|
||||
@@ -8,7 +8,6 @@ pub mod test;
|
||||
|
||||
use futures::Future;
|
||||
|
||||
use itertools::Either;
|
||||
use regex::Regex;
|
||||
use std::sync::{LazyLock, OnceLock};
|
||||
use std::{
|
||||
@@ -200,35 +199,6 @@ pub fn measure<R>(label: &str, f: impl FnOnce() -> R) -> R {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iterate_expanded_and_wrapped_usize_range(
|
||||
range: Range<usize>,
|
||||
additional_before: usize,
|
||||
additional_after: usize,
|
||||
wrap_length: usize,
|
||||
) -> impl Iterator<Item = usize> {
|
||||
let start_wraps = range.start < additional_before;
|
||||
let end_wraps = wrap_length < range.end + additional_after;
|
||||
if start_wraps && end_wraps {
|
||||
Either::Left(0..wrap_length)
|
||||
} else if start_wraps {
|
||||
let wrapped_start = (range.start + wrap_length).saturating_sub(additional_before);
|
||||
if wrapped_start <= range.end {
|
||||
Either::Left(0..wrap_length)
|
||||
} else {
|
||||
Either::Right((0..range.end + additional_after).chain(wrapped_start..wrap_length))
|
||||
}
|
||||
} else if end_wraps {
|
||||
let wrapped_end = range.end + additional_after - wrap_length;
|
||||
if range.start <= wrapped_end {
|
||||
Either::Left(0..wrap_length)
|
||||
} else {
|
||||
Either::Right((0..wrapped_end).chain(range.start - additional_before..wrap_length))
|
||||
}
|
||||
} else {
|
||||
Either::Left((range.start - additional_before)..(range.end + additional_after))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResultExt<E> {
|
||||
type Ok;
|
||||
|
||||
@@ -764,48 +734,4 @@ Line 2
|
||||
Line 3"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iterate_expanded_and_wrapped_usize_range() {
|
||||
// Neither wrap
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(2..4, 1, 1, 8).collect::<Vec<usize>>(),
|
||||
(1..5).collect::<Vec<usize>>()
|
||||
);
|
||||
// Start wraps
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(2..4, 3, 1, 8).collect::<Vec<usize>>(),
|
||||
((0..5).chain(7..8)).collect::<Vec<usize>>()
|
||||
);
|
||||
// Start wraps all the way around
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(2..4, 5, 1, 8).collect::<Vec<usize>>(),
|
||||
(0..8).collect::<Vec<usize>>()
|
||||
);
|
||||
// Start wraps all the way around and past 0
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(2..4, 10, 1, 8).collect::<Vec<usize>>(),
|
||||
(0..8).collect::<Vec<usize>>()
|
||||
);
|
||||
// End wraps
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(3..5, 1, 4, 8).collect::<Vec<usize>>(),
|
||||
(0..1).chain(2..8).collect::<Vec<usize>>()
|
||||
);
|
||||
// End wraps all the way around
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(3..5, 1, 5, 8).collect::<Vec<usize>>(),
|
||||
(0..8).collect::<Vec<usize>>()
|
||||
);
|
||||
// End wraps all the way around and past the end
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(3..5, 1, 10, 8).collect::<Vec<usize>>(),
|
||||
(0..8).collect::<Vec<usize>>()
|
||||
);
|
||||
// Both start and end wrap
|
||||
assert_eq!(
|
||||
iterate_expanded_and_wrapped_usize_range(3..5, 4, 4, 8).collect::<Vec<usize>>(),
|
||||
(0..8).collect::<Vec<usize>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,6 +585,8 @@ impl Motion {
|
||||
| NextLineStart
|
||||
| PreviousLineStart
|
||||
| StartOfLineDownward
|
||||
| SentenceBackward
|
||||
| SentenceForward
|
||||
| StartOfParagraph
|
||||
| EndOfParagraph
|
||||
| WindowTop
|
||||
@@ -609,8 +611,6 @@ impl Motion {
|
||||
| Left
|
||||
| Backspace
|
||||
| Right
|
||||
| SentenceBackward
|
||||
| SentenceForward
|
||||
| Space
|
||||
| StartOfLine { .. }
|
||||
| EndOfLineDownward
|
||||
|
||||
@@ -28,27 +28,23 @@ impl Vim {
|
||||
original_columns.insert(selection.id, original_head.column());
|
||||
motion.expand_selection(map, selection, times, true, &text_layout_details);
|
||||
|
||||
let start_point = selection.start.to_point(map);
|
||||
let next_line = map
|
||||
.buffer_snapshot
|
||||
.clip_point(Point::new(start_point.row + 1, 0), Bias::Left)
|
||||
.to_display_point(map);
|
||||
match motion {
|
||||
// Motion::NextWordStart on an empty line should delete it.
|
||||
Motion::NextWordStart { .. }
|
||||
Motion::NextWordStart { .. } => {
|
||||
if selection.is_empty()
|
||||
&& map
|
||||
.buffer_snapshot
|
||||
.line_len(MultiBufferRow(start_point.row))
|
||||
== 0 =>
|
||||
{
|
||||
selection.end = next_line
|
||||
}
|
||||
// Sentence motions, when done from start of line, include the newline
|
||||
Motion::SentenceForward | Motion::SentenceBackward
|
||||
if selection.start.column() == 0 =>
|
||||
{
|
||||
selection.end = next_line
|
||||
.line_len(MultiBufferRow(selection.start.to_point(map).row))
|
||||
== 0
|
||||
{
|
||||
selection.end = map
|
||||
.buffer_snapshot
|
||||
.clip_point(
|
||||
Point::new(selection.start.to_point(map).row + 1, 0),
|
||||
Bias::Left,
|
||||
)
|
||||
.to_display_point(map)
|
||||
}
|
||||
}
|
||||
Motion::EndOfDocument {} => {
|
||||
// Deleting until the end of the document includes the last line, including
|
||||
@@ -608,62 +604,4 @@ mod test {
|
||||
cx.simulate("d t x", "ˇax").await.assert_matches();
|
||||
cx.simulate("d t x", "aˇx").await.assert_matches();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_delete_sentence(cx: &mut gpui::TestAppContext) {
|
||||
let mut cx = NeovimBackedTestContext::new(cx).await;
|
||||
cx.simulate(
|
||||
"d )",
|
||||
indoc! {"
|
||||
Fiˇrst. Second. Third.
|
||||
Fourth.
|
||||
"},
|
||||
)
|
||||
.await
|
||||
.assert_matches();
|
||||
|
||||
cx.simulate(
|
||||
"d )",
|
||||
indoc! {"
|
||||
First. Secˇond. Third.
|
||||
Fourth.
|
||||
"},
|
||||
)
|
||||
.await
|
||||
.assert_matches();
|
||||
|
||||
// Two deletes
|
||||
cx.simulate(
|
||||
"d ) d )",
|
||||
indoc! {"
|
||||
First. Second. Thirˇd.
|
||||
Fourth.
|
||||
"},
|
||||
)
|
||||
.await
|
||||
.assert_matches();
|
||||
|
||||
// Should delete whole line if done on first column
|
||||
cx.simulate(
|
||||
"d )",
|
||||
indoc! {"
|
||||
ˇFirst.
|
||||
Fourth.
|
||||
"},
|
||||
)
|
||||
.await
|
||||
.assert_matches();
|
||||
|
||||
// Backwards it should also delete the whole first line
|
||||
cx.simulate(
|
||||
"d (",
|
||||
indoc! {"
|
||||
First.
|
||||
ˇSecond.
|
||||
Fourth.
|
||||
"},
|
||||
)
|
||||
.await
|
||||
.assert_matches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
{"Put":{"state":"Fiˇrst. Second. Third.\nFourth.\n"}}
|
||||
{"Key":"d"}
|
||||
{"Key":")"}
|
||||
{"Get":{"state":"FiˇSecond. Third.\nFourth.\n","mode":"Normal"}}
|
||||
{"Put":{"state":"First. Secˇond. Third.\nFourth.\n"}}
|
||||
{"Key":"d"}
|
||||
{"Key":")"}
|
||||
{"Get":{"state":"First. SecˇThird.\nFourth.\n","mode":"Normal"}}
|
||||
{"Put":{"state":"First. Second. Thirˇd.\nFourth.\n"}}
|
||||
{"Key":"d"}
|
||||
{"Key":")"}
|
||||
{"Key":"d"}
|
||||
{"Key":")"}
|
||||
{"Get":{"state":"First. Second. Thˇi\n","mode":"Normal"}}
|
||||
{"Put":{"state":"ˇFirst.\nFourth.\n"}}
|
||||
{"Key":"d"}
|
||||
{"Key":")"}
|
||||
{"Get":{"state":"ˇFourth.\n","mode":"Normal"}}
|
||||
{"Put":{"state":"First.\nˇSecond.\nFourth.\n"}}
|
||||
{"Key":"d"}
|
||||
{"Key":"("}
|
||||
{"Get":{"state":"ˇSecond.\nFourth.\n","mode":"Normal"}}
|
||||
@@ -6,7 +6,7 @@ use ui::{
|
||||
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
|
||||
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
|
||||
Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
|
||||
IconDecoration, Indicator, Switch, SwitchWithLabel, Table, TintColor, Tooltip,
|
||||
IconDecoration, Indicator, Switch, Table, TintColor, Tooltip,
|
||||
};
|
||||
|
||||
use crate::{Item, Workspace};
|
||||
@@ -369,17 +369,16 @@ impl ThemePreview {
|
||||
.overflow_scroll()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Switch::render_component_previews(cx))
|
||||
.child(ContentGroup::render_component_previews(cx))
|
||||
.child(IconDecoration::render_component_previews(cx))
|
||||
.child(DecoratedIcon::render_component_previews(cx))
|
||||
.child(Checkbox::render_component_previews(cx))
|
||||
.child(CheckboxWithLabel::render_component_previews(cx))
|
||||
.child(ContentGroup::render_component_previews(cx))
|
||||
.child(DecoratedIcon::render_component_previews(cx))
|
||||
.child(Facepile::render_component_previews(cx))
|
||||
.child(Icon::render_component_previews(cx))
|
||||
.child(IconDecoration::render_component_previews(cx))
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Indicator::render_component_previews(cx))
|
||||
.child(Switch::render_component_previews(cx))
|
||||
.child(SwitchWithLabel::render_component_previews(cx))
|
||||
.child(Icon::render_component_previews(cx))
|
||||
.child(Table::render_component_previews(cx))
|
||||
}
|
||||
|
||||
|
||||
@@ -995,7 +995,7 @@ impl Workspace {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(display) = cx.display() {
|
||||
if let Ok(display_uuid) = display.uuid() {
|
||||
let window_bounds = cx.inner_window_bounds();
|
||||
let window_bounds = cx.window_bounds();
|
||||
if let Some(database_id) = workspace_id {
|
||||
cx.background_executor()
|
||||
.spawn(DB.set_window_open_status(
|
||||
|
||||
@@ -300,6 +300,7 @@ fn show_software_emulation_warning_if_needed(
|
||||
}
|
||||
|
||||
fn initialize_panels(prompt_builder: Arc<PromptBuilder>, cx: &mut ViewContext<Workspace>) {
|
||||
let release_channel = ReleaseChannel::global(cx);
|
||||
let assistant2_feature_flag = cx.wait_for_flag::<feature_flags::Assistant2FeatureFlag>();
|
||||
let git_ui_feature_flag = cx.wait_for_flag::<feature_flags::GitUiFeatureFlag>();
|
||||
|
||||
@@ -360,7 +361,7 @@ fn initialize_panels(prompt_builder: Arc<PromptBuilder>, cx: &mut ViewContext<Wo
|
||||
}
|
||||
})?;
|
||||
|
||||
let is_assistant2_enabled = if cfg!(test) {
|
||||
let is_assistant2_enabled = if cfg!(test) || release_channel != ReleaseChannel::Dev {
|
||||
false
|
||||
} else {
|
||||
assistant2_feature_flag.await
|
||||
|
||||
@@ -386,7 +386,7 @@ fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
|
||||
indicator: None,
|
||||
kernel_name: kernel_name.clone(),
|
||||
kernel_language: kernel_language.clone(),
|
||||
// TODO: Technically not shutdown, but indeterminate
|
||||
// todo!(): Technically not shutdown, but indeterminate
|
||||
status: KernelStatus::Shutdown,
|
||||
// current_delta: Duration::default(),
|
||||
}
|
||||
|
||||
2
docs/theme/css/chrome.css
vendored
2
docs/theme/css/chrome.css
vendored
@@ -327,7 +327,7 @@ pre > code {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* TODO: ACE editors overlap their buttons because ACE does absolute
|
||||
/* FIXME: ACE editors overlap their buttons because ACE does absolute
|
||||
positioning within the code block which breaks padding. The only solution I
|
||||
can think of is to move the padding to the outer pre tag (or insert a div
|
||||
wrapper), but that would require fixing a whole bunch of CSS rules.
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
(#any-of? @variable "workspace")))
|
||||
|
||||
; Terraform specific keywords
|
||||
; TODO: ideally only for identifiers under a `variable` block to minimize false positives
|
||||
; FIXME: ideally only for identifiers under a `variable` block to minimize false positives
|
||||
((identifier) @type
|
||||
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))
|
||||
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
(#any-of? @variable "workspace")))
|
||||
|
||||
; Terraform specific keywords
|
||||
; TODO: ideally only for identifiers under a `variable` block to minimize false positives
|
||||
; FIXME: ideally only for identifiers under a `variable` block to minimize false positives
|
||||
((identifier) @type
|
||||
(#any-of? @type "bool" "string" "number" "object" "tuple" "list" "map" "set" "any"))
|
||||
|
||||
|
||||
@@ -87,8 +87,12 @@ git clean -q -dff
|
||||
git checkout -q -b ${bump_main_branch_name}
|
||||
cargo set-version --package zed --bump minor
|
||||
cargo check -q
|
||||
message="${next_minor_branch_name} dev
|
||||
|
||||
git commit -q --all --message "${next_minor_branch_name} dev"
|
||||
Release Notes:
|
||||
|
||||
- N/A"
|
||||
git commit -q --all --message "$message"
|
||||
|
||||
git checkout -q main
|
||||
|
||||
@@ -104,12 +108,7 @@ Prepared new Zed versions locally. You will need to push the branches and open a
|
||||
${prev_minor_branch_name} \\
|
||||
${bump_main_branch_name}
|
||||
|
||||
echo -e "Release Notes:\n\n-N/A" | gh pr create \\
|
||||
--title "Bump Zed to v${major}.${next_minor}" \\
|
||||
--body-file "-" \\
|
||||
--base main \\
|
||||
--head ${bump_main_branch_name} \\
|
||||
--web
|
||||
gh pr create --base main --head ${bump_main_branch_name}
|
||||
|
||||
# To undo this push:
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Brackets are used around characters so these don't show up in normal search.
|
||||
pattern='tod[o]!|FIXM[E]'
|
||||
result=$(git grep --no-color --ignore-case --line-number --extended-regexp -e $pattern || true)
|
||||
echo "${result}"
|
||||
if [[ -n "${result}" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
Reference in New Issue
Block a user