mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-16 00:47:39 +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_setting;
|
||||
mod multibuffer_hint;
|
||||
|
||||
use client::{telemetry::Telemetry, TelemetrySettings};
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use gpui::{
|
||||
svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||
ParentElement, Render, Styled, Subscription, Task, View, ViewContext, VisualContext, WeakView,
|
||||
WindowContext,
|
||||
actions, svg, AnyElement, AppContext, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView, WindowContext,
|
||||
};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::sync::Arc;
|
||||
@ -19,6 +20,9 @@ use workspace::{
|
||||
};
|
||||
|
||||
pub use base_keymap_setting::BaseKeymap;
|
||||
pub use multibuffer_hint::*;
|
||||
|
||||
actions!(welcome, [ResetHints]);
|
||||
|
||||
pub const FIRST_OPEN: &str = "first_open";
|
||||
|
||||
@ -30,6 +34,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
let welcome_page = WelcomePage::new(workspace, 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();
|
||||
|
||||
|
@ -41,7 +41,7 @@ use terminal_view::terminal_panel::{self, TerminalPanel};
|
||||
use util::{asset_str, ResultExt};
|
||||
use uuid::Uuid;
|
||||
use vim::VimModeSetting;
|
||||
use welcome::BaseKeymap;
|
||||
use welcome::{BaseKeymap, MultibufferHint};
|
||||
use workspace::{
|
||||
create_and_open_local_file, notifications::simple_message_notification::MessageNotification,
|
||||
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>) {
|
||||
pane.update(cx, |pane, 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());
|
||||
toolbar.add_item(breadcrumbs, cx);
|
||||
let buffer_search_bar = cx.new_view(search::BufferSearchBar::new);
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
# Using Zed
|
||||
|
||||
- [Multibuffers](./multibuffers.md)
|
||||
- [Assistant Panel](./assistant-panel.md)
|
||||
- [Channels](./channels.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