mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Introducing multibuffers (#14668)
Co-Authored-By: Marshall <marshall@zed.dev> Release Notes: - Added a hint the first few times you open a multibuffer to explain what is going on. Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
parent
53bcc3649a
commit
4852e170ff
142
crates/welcome/src/multibuffer_hint.rs
Normal file
142
crates/welcome/src/multibuffer_hint.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
|
use gpui::{AppContext, Empty, EntityId, EventEmitter};
|
||||||
|
use ui::{prelude::*, ButtonLike, IconButtonShape, Tooltip};
|
||||||
|
use workspace::item::ItemHandle;
|
||||||
|
use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView};
|
||||||
|
|
||||||
|
pub struct MultibufferHint {
|
||||||
|
shown_on: HashSet<EntityId>,
|
||||||
|
active_item: Option<Box<dyn ItemHandle>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUMBER_OF_HINTS: usize = 10;
|
||||||
|
|
||||||
|
const SHOWN_COUNT_KEY: &str = "MULTIBUFFER_HINT_SHOWN_COUNT";
|
||||||
|
|
||||||
|
impl MultibufferHint {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
shown_on: Default::default(),
|
||||||
|
active_item: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MultibufferHint {
|
||||||
|
fn counter() -> &'static AtomicUsize {
|
||||||
|
static SHOWN_COUNT: OnceLock<AtomicUsize> = OnceLock::new();
|
||||||
|
SHOWN_COUNT.get_or_init(|| {
|
||||||
|
let value: usize = KEY_VALUE_STORE
|
||||||
|
.read_kvp(SHOWN_COUNT_KEY)
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.and_then(|v| v.parse().ok())
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
|
AtomicUsize::new(value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shown_count() -> usize {
|
||||||
|
Self::counter().load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_count(cx: &mut AppContext) {
|
||||||
|
Self::set_count(Self::shown_count() + 1, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_count(count: usize, cx: &mut AppContext) {
|
||||||
|
Self::counter().store(count, Ordering::Relaxed);
|
||||||
|
|
||||||
|
db::write_and_log(cx, move || {
|
||||||
|
KEY_VALUE_STORE.write_kvp(SHOWN_COUNT_KEY.to_string(), format!("{}", count))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dismiss(&mut self, cx: &mut AppContext) {
|
||||||
|
Self::set_count(NUMBER_OF_HINTS, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ToolbarItemEvent> for MultibufferHint {}
|
||||||
|
|
||||||
|
impl ToolbarItemView for MultibufferHint {
|
||||||
|
fn set_active_pane_item(
|
||||||
|
&mut self,
|
||||||
|
active_pane_item: Option<&dyn ItemHandle>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> ToolbarItemLocation {
|
||||||
|
if Self::shown_count() > NUMBER_OF_HINTS {
|
||||||
|
return ToolbarItemLocation::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(active_pane_item) = active_pane_item else {
|
||||||
|
return ToolbarItemLocation::Hidden;
|
||||||
|
};
|
||||||
|
|
||||||
|
if active_pane_item.is_singleton(cx) {
|
||||||
|
return ToolbarItemLocation::Hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.shown_on.insert(active_pane_item.item_id()) {
|
||||||
|
Self::increment_count(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.active_item = Some(active_pane_item.boxed_clone());
|
||||||
|
ToolbarItemLocation::Secondary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for MultibufferHint {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let Some(active_item) = self.active_item.as_ref() else {
|
||||||
|
return Empty.into_any_element();
|
||||||
|
};
|
||||||
|
|
||||||
|
if active_item.breadcrumbs(cx.theme(), cx).is_none() {
|
||||||
|
return Empty.into_any_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.px_2()
|
||||||
|
.justify_between()
|
||||||
|
.bg(cx.theme().status().info_background)
|
||||||
|
.rounded_md()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_2()
|
||||||
|
.child(Label::new("You can edit results inline in multibuffers!"))
|
||||||
|
.child(
|
||||||
|
ButtonLike::new("open_docs")
|
||||||
|
.style(ButtonStyle::Transparent)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(Label::new("Read more…"))
|
||||||
|
.child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
|
||||||
|
)
|
||||||
|
.on_click(move |_event, cx| {
|
||||||
|
cx.open_url("https://zed.dev/docs/multibuffers")
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
IconButton::new("dismiss", IconName::Close)
|
||||||
|
.style(ButtonStyle::Transparent)
|
||||||
|
.shape(IconButtonShape::Square)
|
||||||
|
.icon_size(IconSize::Small)
|
||||||
|
.on_click(cx.listener(|this, _event, cx| {
|
||||||
|
this.dismiss(cx);
|
||||||
|
cx.emit(ToolbarItemEvent::ChangeLocation(
|
||||||
|
ToolbarItemLocation::Hidden,
|
||||||
|
))
|
||||||
|
}))
|
||||||
|
.tooltip(move |cx| Tooltip::text("Dismiss this hint", cx)),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,13 @@
|
|||||||
mod base_keymap_picker;
|
mod base_keymap_picker;
|
||||||
mod base_keymap_setting;
|
mod base_keymap_setting;
|
||||||
|
mod multibuffer_hint;
|
||||||
|
|
||||||
use client::{telemetry::Telemetry, TelemetrySettings};
|
use client::{telemetry::Telemetry, TelemetrySettings};
|
||||||
use db::kvp::KEY_VALUE_STORE;
|
use db::kvp::KEY_VALUE_STORE;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
actions, svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
|
||||||
ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
InteractiveElement, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
|
||||||
WindowContext,
|
VisualContext, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -19,6 +20,9 @@ use workspace::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub use base_keymap_setting::BaseKeymap;
|
pub use base_keymap_setting::BaseKeymap;
|
||||||
|
pub use multibuffer_hint::*;
|
||||||
|
|
||||||
|
actions!(welcome, [ResetHints]);
|
||||||
|
|
||||||
pub const FIRST_OPEN: &str = "first_open";
|
pub const FIRST_OPEN: &str = "first_open";
|
||||||
|
|
||||||
@ -30,6 +34,8 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
let welcome_page = WelcomePage::new(workspace, cx);
|
let welcome_page = WelcomePage::new(workspace, cx);
|
||||||
workspace.add_item_to_active_pane(Box::new(welcome_page), None, cx)
|
workspace.add_item_to_active_pane(Box::new(welcome_page), None, cx)
|
||||||
});
|
});
|
||||||
|
workspace
|
||||||
|
.register_action(|_workspace, _: &ResetHints, cx| MultibufferHint::set_count(0, cx));
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ use terminal_view::terminal_panel::{self, TerminalPanel};
|
|||||||
use util::{asset_str, ResultExt};
|
use util::{asset_str, ResultExt};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use vim::VimModeSetting;
|
use vim::VimModeSetting;
|
||||||
use welcome::BaseKeymap;
|
use welcome::{BaseKeymap, MultibufferHint};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
|
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
|
||||||
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
|
open_new, AppState, NewFile, NewWindow, OpenLog, Toast, Workspace, WorkspaceSettings,
|
||||||
@ -495,6 +495,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
|
|||||||
fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
|
fn initialize_pane(workspace: &mut Workspace, pane: &View<Pane>, cx: &mut ViewContext<Workspace>) {
|
||||||
pane.update(cx, |pane, cx| {
|
pane.update(cx, |pane, cx| {
|
||||||
pane.toolbar().update(cx, |toolbar, cx| {
|
pane.toolbar().update(cx, |toolbar, cx| {
|
||||||
|
let multibuffer_hint = cx.new_view(|_| MultibufferHint::new());
|
||||||
|
toolbar.add_item(multibuffer_hint, cx);
|
||||||
let breadcrumbs = cx.new_view(|_| Breadcrumbs::new());
|
let breadcrumbs = cx.new_view(|_| Breadcrumbs::new());
|
||||||
toolbar.add_item(breadcrumbs, cx);
|
toolbar.add_item(breadcrumbs, cx);
|
||||||
let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
|
let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
# Using Zed
|
# Using Zed
|
||||||
|
|
||||||
|
- [Multibuffers](./multibuffers.md)
|
||||||
- [Assistant Panel](./assistant-panel.md)
|
- [Assistant Panel](./assistant-panel.md)
|
||||||
- [Channels](./channels.md)
|
- [Channels](./channels.md)
|
||||||
- [Collaboration](./collaboration.md)
|
- [Collaboration](./collaboration.md)
|
||||||
|
25
docs/src/multibuffers.md
Normal file
25
docs/src/multibuffers.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Multibuffers
|
||||||
|
|
||||||
|
One of the superpowers Zed gives you is the ability to edit multiple files simultaneously. When combined with multiple cursors, this makes wide-ranging refactors significantly faster.
|
||||||
|
|
||||||
|
## Editing in a multibuffer
|
||||||
|
|
||||||
|
Editing a multibuffer is the same as editing a normal file. Changes you make will be reflected in the open copies of that file in the rest of the editor, and you can save all files with `editor: Save` (bound to `cmd-s` on macOS, `ctrl-s` on Windows/Linux, or `:w` in Vim mode).
|
||||||
|
|
||||||
|
When in a multibuffer, it is often useful to use multiple cursors to edit every file simultaneously. If you want to edit a few instances, you can select them with the mouse (`option-click` on macOS, `alt-click` on Window/Linux) or the keyboard. `cmd-d` on macOS, `ctrl-d` on Windows/Linux, or `gl` in Vim mode will select the next match of the word under the cursor.
|
||||||
|
|
||||||
|
When you want to edit all matches you can select them by running the `editor: Select All Matches` command (`cmd-shift-l` on macOS, `ctrl-shift-l` on Windows/Linux, or `g a` in Vim mode).
|
||||||
|
|
||||||
|
## Project search
|
||||||
|
|
||||||
|
To start a search run the `pane: Toggle Search` command (`cmd-shift-f` on macOS, `ctrl-shift-f` on Windows/Linux, or `g/` in Vim mode). After the search has completed, the results will be shown in a new multibuffer. There will be one excerpt for each matching line across the whole project.
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
If you have a language server installed, the diagnostics pane can show you all errors across your project. You can open it by clicking on the icon in the status bar, or running the `diagnostcs: Deploy` command` ('cmd-shift-m` on macOS, `ctrl-shift-m` on Windows/Linux, or `:clist` in Vim mode).
|
||||||
|
|
||||||
|
## Find References
|
||||||
|
|
||||||
|
If you have a language server installed, you can find all references to the symbol under the cursor with the `editor: Find References` command (`cmd-click` on macOS, `ctrl-click` on Windows/Linux, or `g A` in Vim mode.
|
||||||
|
|
||||||
|
Depending on your language server, commands like `editor: Go To Definition` and `editor: Go To Type Definition` will also open a multibuffer if there are multiple possible definitions.
|
Loading…
Reference in New Issue
Block a user