@@ -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,
@@ -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();
+ }
+}
@@ -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)),
),
)
}