Overhauls Zed's pickers to make them resizable and give them a preview. Closes #8279 ### Background The most requested Zed feature has the last year has been a [Telescope like search box](https://github.com/zed-industries/zed/issues/8279) [discussion](https://github.com/zed-industries/zed/discussions/22581). To understand why this is so popular we need to understand search can serve thee goals: - Navigation: fuzzy search is faster & easier then clicking in a file tree - Exploration: example, find a function by a word in its doc comment - Collecting: example, getting a list of functions to change The project search which shows results in a multibuffer is the perfect way to operate on a list of items. Navigation and Exploration need a lot of context around each result and offer fast navigation between them. For both of these live searching is also critical. The `telescope UI` is a picker with a preview to the right or below. It's offered in various editors and IDE's most famously Neovim (through the Telescope plugin), IntelliJ (natively), Helix (natively) and of course VScode (plugins) and it's _many_ forks. While having a UI like that for text search (our project search) is most requested the UX pattern is applied widely, from `find_all_references` to `bookmarks`. It enhances most pickers. Note that we have over 50 different picker modals! The community has tried to build something like this for Zed: - https://github.com/zed-industries/zed/pull/44530 - https://github.com/zed-industries/zed/pull/45307 - https://github.com/zed-industries/zed/pull/46478 - https://github.com/zed-industries/zed/pull/43790 These all became huge PR's that we could not merge for various reasons. This is a really hard feature to integrate in Zed! This PR got started as https://github.com/zed-industries/zed/pull/46478 and supercedes that. ### Design - Extend pickers to support an optional preview with minimal changes to the pickers themselves. - Make pickers resizable. - Complement the existing search do not replace it by having both UI's share the underlying search and allow freely switching between them. - Allow extending the preview to things other then files. - Maintain a clean design on all the pickers. ### Heigh level Implementation overview - Adds an `Option<Preview>` to `Picker` - Gives `PickerDelegate` a method to communicate a preview to the Picker - Overhaul the way pickers are drawn to allow for resizing them. Implemented on the `Shape` and `SizeBouds` structs. - Adds a high level way to draw the `footer` and `editor` so we do not need to change much to the pickers. - Adds a new text finder Picker - Adds a way to take a running search from project search and hand it to the text finder Picker and the other way round - Give the file finder a preview ### Next steps A more detailed list and how to help out will be added to the tracking issue for [Pickes with previews](https://github.com/zed-industries/zed/issues/56037) - Add more previews to more pickers! - Enable selectioning multiple items in pickers and performing actions on those - Open selected items in a multibuffer - Add a way to restore the last picker - Make popovers (picker attached to some menu) resizable as well ## Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content adheres to Zed's UI standards ([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) and [icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md) guidelines) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable ## Showcase TODO (will be done post merge) --- Release Notes: - Added resizing via dragging to all picker modals. - Added a preview to the File finder, the preview can be to the right or below. - Added a Text finder picker with a preview as alternative project search UI. The search is shared and allowes switch between UIs while running. --------- Co-authored-by: ozacod <47009516+ozacod@users.noreply.github.com> Co-authored-by: ozacod <ozacod@users.noreply.github.com> Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
196 lines
5.7 KiB
Rust
196 lines
5.7 KiB
Rust
mod base_keymap_setting;
|
|
mod content_into_gpui;
|
|
mod editable_setting_control;
|
|
mod editorconfig_store;
|
|
mod keymap_file;
|
|
mod settings_file;
|
|
mod settings_store;
|
|
mod vscode_import;
|
|
|
|
pub use settings_macros::RegisterSetting;
|
|
|
|
pub mod settings_content {
|
|
pub use ::settings_content::*;
|
|
}
|
|
|
|
pub mod fallible_options {
|
|
pub use ::settings_content::{FallibleOption, parse_json};
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub mod private {
|
|
pub use crate::settings_store::{RegisteredSetting, SettingValue};
|
|
pub use inventory;
|
|
}
|
|
|
|
use gpui::{App, Global};
|
|
|
|
use rust_embed::RustEmbed;
|
|
use std::env;
|
|
use std::{borrow::Cow, fmt, str};
|
|
use util::asset_str;
|
|
|
|
pub use ::settings_content::*;
|
|
pub use base_keymap_setting::*;
|
|
pub use content_into_gpui::IntoGpui;
|
|
pub use editable_setting_control::*;
|
|
pub use editorconfig_store::{
|
|
Editorconfig, EditorconfigEvent, EditorconfigProperties, EditorconfigStore,
|
|
};
|
|
pub use keymap_file::{
|
|
KeyBindingValidator, KeyBindingValidatorRegistration, KeybindSource, KeybindUpdateOperation,
|
|
KeybindUpdateTarget, KeymapFile, KeymapFileLoadResult,
|
|
};
|
|
pub use settings_file::*;
|
|
pub use settings_json::*;
|
|
pub use settings_store::{
|
|
DefaultSemanticTokenRules, InvalidSettingsError, LSP_SETTINGS_SCHEMA_URL_PREFIX,
|
|
LocalSettingsKind, LocalSettingsPath, MigrationStatus, Settings, SettingsFile,
|
|
SettingsJsonSchemaParams, SettingsKey, SettingsLocation, SettingsParseResult, SettingsStore,
|
|
};
|
|
|
|
pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
|
|
|
|
pub use keymap_file::ActionSequence;
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct ActiveSettingsProfileName(pub String);
|
|
|
|
impl Global for ActiveSettingsProfileName {}
|
|
|
|
pub trait UserSettingsContentExt {
|
|
fn for_profile(&self, cx: &App) -> Option<&SettingsProfile>;
|
|
fn for_release_channel(&self) -> Option<&SettingsContent>;
|
|
fn for_os(&self) -> Option<&SettingsContent>;
|
|
}
|
|
|
|
impl UserSettingsContentExt for UserSettingsContent {
|
|
fn for_profile(&self, cx: &App) -> Option<&SettingsProfile> {
|
|
let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() else {
|
|
return None;
|
|
};
|
|
self.profiles.get(&active_profile.0)
|
|
}
|
|
|
|
fn for_release_channel(&self) -> Option<&SettingsContent> {
|
|
self.release_channel_overrides
|
|
.get_by_key(release_channel::RELEASE_CHANNEL.dev_name())
|
|
}
|
|
|
|
fn for_os(&self) -> Option<&SettingsContent> {
|
|
self.platform_overrides.get_by_key(env::consts::OS)
|
|
}
|
|
}
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord, serde::Serialize)]
|
|
pub struct WorktreeId(usize);
|
|
|
|
impl From<WorktreeId> for usize {
|
|
fn from(value: WorktreeId) -> Self {
|
|
value.0
|
|
}
|
|
}
|
|
|
|
impl WorktreeId {
|
|
pub fn from_usize(handle_id: usize) -> Self {
|
|
Self(handle_id)
|
|
}
|
|
|
|
pub fn from_proto(id: u64) -> Self {
|
|
Self(id as usize)
|
|
}
|
|
|
|
pub fn to_proto(self) -> u64 {
|
|
self.0 as u64
|
|
}
|
|
|
|
pub fn to_usize(self) -> usize {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for WorktreeId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
std::fmt::Display::fmt(&self.0, f)
|
|
}
|
|
}
|
|
|
|
#[derive(RustEmbed)]
|
|
#[folder = "../../assets"]
|
|
#[include = "settings/*"]
|
|
#[include = "keymaps/*"]
|
|
#[exclude = "*.DS_Store"]
|
|
pub struct SettingsAssets;
|
|
|
|
pub fn init(cx: &mut App) {
|
|
let settings = SettingsStore::new(cx, &default_settings());
|
|
cx.set_global(settings);
|
|
SettingsStore::observe_active_settings_profile_name(cx).detach();
|
|
}
|
|
|
|
pub fn default_settings() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/default.json")
|
|
}
|
|
|
|
pub fn default_semantic_token_rules() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/default_semantic_token_rules.json")
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-macos.json";
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-windows.json";
|
|
|
|
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
|
pub const DEFAULT_KEYMAP_PATH: &str = "keymaps/default-linux.json";
|
|
|
|
pub fn default_keymap() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>(DEFAULT_KEYMAP_PATH)
|
|
}
|
|
|
|
pub const VIM_KEYMAP_PATH: &str = "keymaps/vim.json";
|
|
|
|
pub fn vim_keymap() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>(VIM_KEYMAP_PATH)
|
|
}
|
|
|
|
/// Specific keybinding overrides. Loaded after the base keymap so they win over
|
|
/// conflicting base-keymap (and default `Editor`) bindings for the same chords,
|
|
/// while still allowing user keymaps (loaded last) to override them. Shared
|
|
/// across features - prefer adding a context block here over creating another
|
|
/// override keymap file.
|
|
#[cfg(target_os = "macos")]
|
|
pub const SPECIFIC_OVERRIDES_KEYMAP_PATH: &str = "keymaps/specific-overrides-macos.json";
|
|
|
|
#[cfg(not(target_os = "macos"))]
|
|
pub const SPECIFIC_OVERRIDES_KEYMAP_PATH: &str = "keymaps/specific-overrides.json";
|
|
|
|
pub fn initial_user_settings_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_user_settings.json")
|
|
}
|
|
|
|
pub fn initial_server_settings_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_server_settings.json")
|
|
}
|
|
|
|
pub fn initial_project_settings_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_local_settings.json")
|
|
}
|
|
|
|
pub fn initial_keymap_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("keymaps/initial.json")
|
|
}
|
|
|
|
pub fn initial_tasks_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_tasks.json")
|
|
}
|
|
|
|
pub fn initial_debug_tasks_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_debug_tasks.json")
|
|
}
|
|
|
|
pub fn initial_local_debug_tasks_content() -> Cow<'static, str> {
|
|
asset_str::<SettingsAssets>("settings/initial_local_debug_tasks.json")
|
|
}
|