Files
zed/crates/settings/src/settings.rs
T
6a6fcbebc4 Add preview to pickers and make them resizable (#59604)
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>
2026-06-19 17:43:07 +00:00

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