Let LineColumn on StatusBar as clickable to open GoToLineColumn (#9002)

Release Notes:

- Added to let LineColumn on StatusBar as clickable to open
GoToLineColumn.
- Added placeholder to GoToLineColumn input, and show help message on
input changed.

## Screenshot


![go-to-line-column](https://github.com/zed-industries/zed/assets/5518/90a4f644-07d4-4208-8caa-5510e1537f37)
This commit is contained in:
Jason Lee 2024-03-09 05:11:17 +08:00 committed by GitHub
parent 977af37cfe
commit 5abcc1c3c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 144 additions and 103 deletions

View File

@ -7,9 +7,9 @@ use anyhow::{anyhow, Context as _, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
div, point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId,
EventEmitter, IntoElement, Model, ParentElement, Pixels, Render, SharedString, Styled,
Subscription, Task, View, ViewContext, VisualContext, WeakView, WindowContext,
point, AnyElement, AppContext, AsyncWindowContext, Context, Entity, EntityId, EventEmitter,
IntoElement, Model, ParentElement, Pixels, SharedString, Styled, Task, View, ViewContext,
VisualContext, WeakView, WindowContext,
};
use language::{
proto::serialize_anchor as serialize_text_anchor, Bias, Buffer, CharKind, OffsetRangeExt,
@ -21,7 +21,6 @@ use rpc::proto::{self, update_view, PeerId};
use settings::Settings;
use workspace::item::ItemSettings;
use std::fmt::Write;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@ -33,11 +32,8 @@ use std::{
use text::{BufferId, Selection};
use theme::Theme;
use ui::{h_flex, prelude::*, Label};
use util::{paths::PathExt, paths::FILE_ROW_COLUMN_DELIMITER, ResultExt, TryFutureExt};
use workspace::{
item::{BreadcrumbText, FollowEvent, FollowableItemHandle},
StatusItemView,
};
use util::{paths::PathExt, ResultExt, TryFutureExt};
use workspace::item::{BreadcrumbText, FollowEvent, FollowableItemHandle};
use workspace::{
item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem},
searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle},
@ -1199,83 +1195,6 @@ pub fn active_match_index(
}
}
pub struct CursorPosition {
position: Option<Point>,
selected_count: usize,
_observe_active_editor: Option<Subscription>,
}
impl Default for CursorPosition {
fn default() -> Self {
Self::new()
}
}
impl CursorPosition {
pub fn new() -> Self {
Self {
position: None,
selected_count: 0,
_observe_active_editor: None,
}
}
fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
self.selected_count = 0;
let mut last_selection: Option<Selection<usize>> = None;
for selection in editor.selections.all::<usize>(cx) {
self.selected_count += selection.end - selection.start;
if last_selection
.as_ref()
.map_or(true, |last_selection| selection.id > last_selection.id)
{
last_selection = Some(selection);
}
}
self.position = last_selection.map(|s| s.head().to_point(&buffer));
cx.notify();
}
}
impl Render for CursorPosition {
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
div().when_some(self.position, |el, position| {
let mut text = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{}",
position.row + 1,
position.column + 1
);
if self.selected_count > 0 {
write!(text, " ({} selected)", self.selected_count).unwrap();
}
el.child(Label::new(text).size(LabelSize::Small))
})
}
}
impl StatusItemView for CursorPosition {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
self.update_position(editor, cx);
} else {
self.position = None;
self._observe_active_editor = None;
}
cx.notify();
}
}
fn path_for_buffer<'a>(
buffer: &Model<MultiBuffer>,
height: usize,

View File

@ -0,0 +1,100 @@
use editor::{Editor, ToPoint};
use gpui::{Subscription, View, WeakView};
use std::fmt::Write;
use text::{Point, Selection};
use ui::{
div, Button, ButtonCommon, Clickable, FluentBuilder, IntoElement, LabelSize, ParentElement,
Render, Tooltip, ViewContext,
};
use util::paths::FILE_ROW_COLUMN_DELIMITER;
use workspace::{item::ItemHandle, StatusItemView, Workspace};
pub struct CursorPosition {
position: Option<Point>,
selected_count: usize,
workspace: WeakView<Workspace>,
_observe_active_editor: Option<Subscription>,
}
impl CursorPosition {
pub fn new(workspace: &Workspace) -> Self {
Self {
position: None,
selected_count: 0,
workspace: workspace.weak_handle(),
_observe_active_editor: None,
}
}
fn update_position(&mut self, editor: View<Editor>, cx: &mut ViewContext<Self>) {
let editor = editor.read(cx);
let buffer = editor.buffer().read(cx).snapshot(cx);
self.selected_count = 0;
let mut last_selection: Option<Selection<usize>> = None;
for selection in editor.selections.all::<usize>(cx) {
self.selected_count += selection.end - selection.start;
if last_selection
.as_ref()
.map_or(true, |last_selection| selection.id > last_selection.id)
{
last_selection = Some(selection);
}
}
self.position = last_selection.map(|s| s.head().to_point(&buffer));
cx.notify();
}
}
impl Render for CursorPosition {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div().when_some(self.position, |el, position| {
let mut text = format!(
"{}{FILE_ROW_COLUMN_DELIMITER}{}",
position.row + 1,
position.column + 1
);
if self.selected_count > 0 {
write!(text, " ({} selected)", self.selected_count).unwrap();
}
el.child(
Button::new("go-to-line-column", text)
.label_size(LabelSize::Small)
.on_click(cx.listener(|this, _, cx| {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
if let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
{
workspace
.toggle_modal(cx, |cx| crate::GoToLine::new(editor, cx))
}
});
}
}))
.tooltip(|cx| Tooltip::for_action("Go to Line/Column", &crate::Toggle, cx)),
)
})
}
}
impl StatusItemView for CursorPosition {
fn set_active_pane_item(
&mut self,
active_pane_item: Option<&dyn ItemHandle>,
cx: &mut ViewContext<Self>,
) {
if let Some(editor) = active_pane_item.and_then(|item| item.act_as::<Editor>(cx)) {
self._observe_active_editor = Some(cx.observe(&editor, Self::update_position));
self.update_position(editor, cx);
} else {
self.position = None;
self._observe_active_editor = None;
}
cx.notify();
}
}

View File

@ -1,3 +1,5 @@
pub mod cursor_position;
use editor::{display_map::ToDisplayPoint, scroll::Autoscroll, Editor};
use gpui::{
actions, div, prelude::*, AnyWindowHandle, AppContext, DismissEvent, EventEmitter, FocusHandle,
@ -49,20 +51,24 @@ impl GoToLine {
}
pub fn new(active_editor: View<Editor>, cx: &mut ViewContext<Self>) -> Self {
let line_editor = cx.new_view(|cx| Editor::single_line(cx));
let editor = active_editor.read(cx);
let cursor = editor.selections.last::<Point>(cx).head();
let line = cursor.row + 1;
let column = cursor.column + 1;
let line_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
editor.set_placeholder_text(format!("{line}{FILE_ROW_COLUMN_DELIMITER}{column}"), cx);
editor
});
let line_editor_change = cx.subscribe(&line_editor, Self::on_line_editor_event);
let editor = active_editor.read(cx);
let cursor = editor.selections.last::<Point>(cx).head();
let last_line = editor.buffer().read(cx).snapshot(cx).max_point().row;
let scroll_position = active_editor.update(cx, |editor, cx| editor.scroll_position(cx));
let current_text = format!(
"line {} of {} (column {})",
cursor.row + 1,
last_line + 1,
cursor.column + 1,
);
let current_text = format!("line {} of {} (column {})", line, last_line + 1, column);
Self {
line_editor,
@ -116,17 +122,22 @@ impl GoToLine {
}
fn point_from_query(&self, cx: &ViewContext<Self>) -> Option<Point> {
let line_editor = self.line_editor.read(cx).text(cx);
let mut components = line_editor
let (row, column) = self.line_column_from_query(cx);
Some(Point::new(
row?.saturating_sub(1),
column.unwrap_or(0).saturating_sub(1),
))
}
fn line_column_from_query(&self, cx: &ViewContext<Self>) -> (Option<u32>, Option<u32>) {
let input = self.line_editor.read(cx).text(cx);
let mut components = input
.splitn(2, FILE_ROW_COLUMN_DELIMITER)
.map(str::trim)
.fuse();
let row = components.next().and_then(|row| row.parse::<u32>().ok())?;
let row = components.next().and_then(|row| row.parse::<u32>().ok());
let column = components.next().and_then(|col| col.parse::<u32>().ok());
Some(Point::new(
row.saturating_sub(1),
column.unwrap_or(0).saturating_sub(1),
))
(row, column)
}
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
@ -153,6 +164,16 @@ impl GoToLine {
impl Render for GoToLine {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let mut help_text = self.current_text.clone();
let query = self.line_column_from_query(cx);
if let Some(line) = query.0 {
if let Some(column) = query.1 {
help_text = format!("Go to line {line}, column {column}").into();
} else {
help_text = format!("Go to line {line}").into();
}
}
div()
.elevation_2(cx)
.key_context("GoToLine")
@ -181,7 +202,7 @@ impl Render for GoToLine {
.justify_between()
.px_2()
.py_1()
.child(Label::new(self.current_text.clone()).color(Color::Muted)),
.child(Label::new(help_text).color(Color::Muted)),
),
)
}

View File

@ -132,7 +132,8 @@ pub fn initialize_workspace(app_state: Arc<AppState>, cx: &mut AppContext) {
let vim_mode_indicator = cx.new_view(|cx| vim::ModeIndicator::new(cx));
let feedback_button =
cx.new_view(|_| feedback::deploy_feedback_button::DeployFeedbackButton::new(workspace));
let cursor_position = cx.new_view(|_| editor::items::CursorPosition::new());
let cursor_position =
cx.new_view(|_| go_to_line::cursor_position::CursorPosition::new(workspace));
workspace.status_bar().update(cx, |status_bar, cx| {
status_bar.add_left_item(diagnostic_summary, cx);
status_bar.add_left_item(activity_indicator, cx);