Compare commits

..

1 Commits

Author SHA1 Message Date
mgsloan@gmail.com
e2673bb667 WIP jumping into diagnostics 2024-12-19 21:21:17 -07:00
86 changed files with 856 additions and 2570 deletions

View File

@@ -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
View File

@@ -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]]

View File

@@ -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"] }

View File

@@ -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

View File

@@ -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()))

View File

@@ -1,5 +1,4 @@
mod active_thread;
mod assistant_model_selector;
mod assistant_panel;
mod assistant_settings;
mod buffer_codegen;

View File

@@ -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())
}
}

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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()),
)
}
}

View File

@@ -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,

View File

@@ -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);
}),
),
),
)
}

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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();

View File

@@ -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,

View File

@@ -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]

View File

@@ -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,
);

View File

@@ -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),

View File

@@ -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
}
})
})
}

View File

@@ -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()?;

View File

@@ -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;

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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,
)

View File

@@ -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()

View File

@@ -1,6 +1,4 @@
use gpui::{
actions, div, prelude::*, rgb, App, AppContext, Menu, MenuItem, ViewContext, WindowOptions,
};
use gpui::*;
struct SetMenus;

View File

@@ -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);
});
}

View File

@@ -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>

View File

@@ -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,

View File

@@ -1,6 +1,4 @@
use gpui::{
div, prelude::*, px, size, App, AppContext, Bounds, ViewContext, WindowBounds, WindowOptions,
};
use gpui::*;
struct HelloWorld {}

View File

@@ -1,7 +1,4 @@
use gpui::{
div, prelude::*, px, rgb, size, uniform_list, App, AppContext, Bounds, ViewContext,
WindowBounds, WindowOptions,
};
use gpui::*;
struct UniformListExample {}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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()

View File

@@ -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) {}

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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!(),

View File

@@ -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 {

View File

@@ -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),
});
}
}

View File

@@ -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.),

View File

@@ -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>,

View File

@@ -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

View File

@@ -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)]

View File

@@ -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;
}

View File

@@ -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,

View File

@@ -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?,

View File

@@ -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(

View File

@@ -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,

View File

@@ -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?,

View File

@@ -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,

View File

@@ -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!
![This is an image](/images/logo.png)
```

View File

@@ -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;

View File

@@ -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]

View File

@@ -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>> {

View File

@@ -1668,7 +1668,7 @@ impl BufferStore {
.log_err();
}
// TODO(max): do something
// todo!(max): do something
// client
// .send(proto::UpdateStagedText {
// project_id,

View File

@@ -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())
}

View File

@@ -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| {

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
})

View File

@@ -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),

View File

@@ -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)

View File

@@ -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,
|_, _| {},
),
),
])]
}
}

View File

@@ -1,8 +1,7 @@
#![allow(missing_docs)]
use gpui::Axis;
use crate::prelude::*;
use gpui::*;
#[derive(IntoElement)]
pub struct ToolStrip {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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>>()
);
}
}

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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"}}

View File

@@ -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))
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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(),
}

View File

@@ -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.

View File

@@ -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"))

View File

@@ -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"))

View File

@@ -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:

View File

@@ -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