input: Add Cursor and Selection type and export line number, cursor. (#1012)
This commit is contained in:
parent
5b17281374
commit
21dbaffcf4
10 changed files with 493 additions and 238 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
checkbox::Checkbox,
|
button::{Button, ButtonVariants as _},
|
||||||
dropdown::{Dropdown, DropdownEvent, DropdownState},
|
dropdown::{Dropdown, DropdownEvent, DropdownState},
|
||||||
h_flex,
|
h_flex,
|
||||||
highlighter::{Language, LanguageConfig, LanguageRegistry},
|
highlighter::{Language, LanguageConfig, LanguageRegistry},
|
||||||
input::{InputEvent, InputState, Marker, TabSize, TextInput},
|
input::{InputEvent, InputState, Marker, TabSize, TextInput},
|
||||||
v_flex,
|
v_flex, ActiveTheme, Selectable, Sizable,
|
||||||
};
|
};
|
||||||
use story::Assets;
|
use story::Assets;
|
||||||
|
|
||||||
|
|
@ -185,44 +185,55 @@ impl Render for Example {
|
||||||
self.update_highlighter(window, cx);
|
self.update_highlighter(window, cx);
|
||||||
self.set_markers(window, cx);
|
self.set_markers(window, cx);
|
||||||
|
|
||||||
v_flex()
|
v_flex().size_full().child(
|
||||||
.size_full()
|
v_flex()
|
||||||
.child(
|
.id("source")
|
||||||
h_flex()
|
.w_full()
|
||||||
.p_4()
|
.flex_1()
|
||||||
.pb_0()
|
.p_4()
|
||||||
.gap_4()
|
.gap_2()
|
||||||
.flex_shrink_0()
|
.child(
|
||||||
.items_center()
|
TextInput::new(&self.input_state)
|
||||||
.justify_between()
|
.h_full()
|
||||||
.child(Dropdown::new(&self.language_state).title_prefix("Language: "))
|
.font_family("Monaco")
|
||||||
.child(
|
.text_size(px(12.))
|
||||||
Checkbox::new("line-numbger")
|
.focus_bordered(false),
|
||||||
.checked(self.line_number)
|
)
|
||||||
.on_click(cx.listener(|this, checked: &bool, window, cx| {
|
.child(
|
||||||
this.line_number = *checked;
|
h_flex()
|
||||||
this.input_state.update(cx, |state, cx| {
|
.justify_between()
|
||||||
state.set_line_number(this.line_number, window, cx);
|
.text_sm()
|
||||||
});
|
.text_color(cx.theme().muted_foreground)
|
||||||
cx.notify();
|
.child(
|
||||||
}))
|
h_flex()
|
||||||
.label("Line Number"),
|
.gap_3()
|
||||||
),
|
.child(
|
||||||
)
|
Dropdown::new(&self.language_state)
|
||||||
.child(
|
.menu_width(px(160.))
|
||||||
div()
|
.small(),
|
||||||
.id("source")
|
)
|
||||||
.w_full()
|
.child(
|
||||||
.flex_1()
|
Button::new("line-number")
|
||||||
.p_4()
|
.ghost()
|
||||||
.font_family("Monaco")
|
.label("Line Number")
|
||||||
.text_size(px(12.))
|
.small()
|
||||||
.child(
|
.selected(self.line_number)
|
||||||
TextInput::new(&self.input_state)
|
.on_click(cx.listener(|this, _, window, cx| {
|
||||||
.h_full()
|
this.line_number = !this.line_number;
|
||||||
.focus_bordered(false),
|
this.input_state.update(cx, |state, cx| {
|
||||||
),
|
state.set_line_number(this.line_number, window, cx);
|
||||||
)
|
});
|
||||||
|
cx.notify();
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child({
|
||||||
|
let loc = self.input_state.read(cx).line_column();
|
||||||
|
let cursor = self.input_state.read(cx).cursor();
|
||||||
|
format!("{} ({} c)", loc, cursor.offset())
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -136,6 +136,8 @@ impl Focusable for TextareaStory {
|
||||||
|
|
||||||
impl Render for TextareaStory {
|
impl Render for TextareaStory {
|
||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
let loc = self.textarea.read(cx).line_column();
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.key_context(CONTEXT)
|
.key_context(CONTEXT)
|
||||||
.id("textarea-story")
|
.id("textarea-story")
|
||||||
|
|
@ -152,19 +154,28 @@ impl Render for TextareaStory {
|
||||||
.child(TextInput::new(&self.textarea).h(px(320.)))
|
.child(TextInput::new(&self.textarea).h(px(320.)))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.justify_between()
|
||||||
.child(
|
.child(
|
||||||
Button::new("btn-insert-text")
|
h_flex()
|
||||||
.xsmall()
|
.gap_2()
|
||||||
.label("Insert Text")
|
.child(
|
||||||
.on_click(cx.listener(Self::on_insert_text_to_textarea)),
|
Button::new("btn-insert-text")
|
||||||
|
.xsmall()
|
||||||
|
.label("Insert Text")
|
||||||
|
.on_click(
|
||||||
|
cx.listener(Self::on_insert_text_to_textarea),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Button::new("btn-replace-text")
|
||||||
|
.xsmall()
|
||||||
|
.label("Replace Text")
|
||||||
|
.on_click(
|
||||||
|
cx.listener(Self::on_replace_text_to_textarea),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.child(
|
.child(format!("{}:{}", loc.line, loc.column)),
|
||||||
Button::new("btn-replace-text")
|
|
||||||
.xsmall()
|
|
||||||
.label("Replace Text")
|
|
||||||
.on_click(cx.listener(Self::on_replace_text_to_textarea)),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ webview = ["dep:wry"]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
gpui-component-macros.workspace = true
|
gpui-component-macros.workspace = true
|
||||||
|
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
enum-iterator = "2.1.0"
|
enum-iterator = "2.1.0"
|
||||||
futures-util = "0.3.31"
|
futures-util = "0.3.31"
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
use std::{fmt::Debug, ops::Range};
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use crate::history::HistoryItem;
|
use crate::{history::HistoryItem, input::Selection};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct Change {
|
pub struct Change {
|
||||||
pub(crate) old_range: Range<usize>,
|
pub(crate) old_range: Selection,
|
||||||
pub(crate) old_text: String,
|
pub(crate) old_text: String,
|
||||||
pub(crate) new_range: Range<usize>,
|
pub(crate) new_range: Selection,
|
||||||
pub(crate) new_text: String,
|
pub(crate) new_text: String,
|
||||||
version: usize,
|
version: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Change {
|
impl Change {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
old_range: Range<usize>,
|
old_range: impl Into<Selection>,
|
||||||
old_text: &str,
|
old_text: &str,
|
||||||
new_range: Range<usize>,
|
new_range: impl Into<Selection>,
|
||||||
new_text: &str,
|
new_text: &str,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
old_range,
|
old_range: old_range.into(),
|
||||||
old_text: old_text.to_string(),
|
old_text: old_text.to_string(),
|
||||||
new_range,
|
new_range: new_range.into(),
|
||||||
new_text: new_text.to_string(),
|
new_text: new_text.to_string(),
|
||||||
version: 0,
|
version: 0,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
194
crates/ui/src/input/cursor.rs
Normal file
194
crates/ui/src/input/cursor.rs
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
use std::{
|
||||||
|
cmp::Ordering,
|
||||||
|
fmt,
|
||||||
|
ops::{Add, Deref, Range, Sub},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Cursor of the text.
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
pub struct Cursor {
|
||||||
|
/// The byte offset in the text (zero-based).
|
||||||
|
pub(super) offset: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cursor {
|
||||||
|
pub fn new(offset: usize) -> Self {
|
||||||
|
Self { offset }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the byte offset in the text (zero-based).
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Cursor {}
|
||||||
|
impl PartialEq for Cursor {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.offset == other.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq<usize> for Cursor {
|
||||||
|
fn eq(&self, other: &usize) -> bool {
|
||||||
|
self.offset == *other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq<Cursor> for usize {
|
||||||
|
fn eq(&self, other: &Cursor) -> bool {
|
||||||
|
*self == other.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Cursor {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
self.offset.partial_cmp(&other.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialOrd<usize> for Cursor {
|
||||||
|
fn partial_cmp(&self, other: &usize) -> Option<Ordering> {
|
||||||
|
self.offset.partial_cmp(other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialOrd<Cursor> for usize {
|
||||||
|
fn partial_cmp(&self, other: &Cursor) -> Option<Ordering> {
|
||||||
|
self.partial_cmp(&other.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for Cursor {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(mut self, other: Self) -> Self {
|
||||||
|
self.offset += other.offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Add<usize> for Cursor {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(mut self, other: usize) -> Self {
|
||||||
|
self.offset += other;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Add<Cursor> for usize {
|
||||||
|
type Output = Cursor;
|
||||||
|
|
||||||
|
fn add(self, other: Cursor) -> Cursor {
|
||||||
|
Cursor::new(self + other.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for Cursor {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(mut self, other: Self) -> Self {
|
||||||
|
self.offset -= other.offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Sub<usize> for Cursor {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(mut self, other: usize) -> Self {
|
||||||
|
self.offset -= other;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Sub<Cursor> for usize {
|
||||||
|
type Output = Cursor;
|
||||||
|
|
||||||
|
fn sub(self, other: Cursor) -> Cursor {
|
||||||
|
Cursor::new(self - other.offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Cursor {
|
||||||
|
type Target = usize;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct Selection {
|
||||||
|
pub start: Cursor,
|
||||||
|
pub end: Cursor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selection {
|
||||||
|
pub fn new(start: Cursor, end: Cursor) -> Self {
|
||||||
|
Self { start, end }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.end.offset.saturating_sub(self.start.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.start == self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Range<Cursor>> for Selection {
|
||||||
|
fn from(value: Range<Cursor>) -> Self {
|
||||||
|
Self::new(value.start, value.end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Selection> for Range<Cursor> {
|
||||||
|
fn from(value: Selection) -> Self {
|
||||||
|
value.start..value.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Range<usize>> for Selection {
|
||||||
|
fn from(value: Range<usize>) -> Self {
|
||||||
|
Self::new(Cursor::new(value.start), Cursor::new(value.end))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Selection> for Range<usize> {
|
||||||
|
fn from(value: Selection) -> Self {
|
||||||
|
value.start.offset..value.end.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&Selection> for Range<usize> {
|
||||||
|
fn from(value: &Selection) -> Self {
|
||||||
|
value.start.offset..value.end.offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Line and column position (1-based) in the source code.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
pub struct LineColumn {
|
||||||
|
/// Line number (1-based)
|
||||||
|
pub line: usize,
|
||||||
|
/// Column number (1-based)
|
||||||
|
pub column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(usize, usize)> for LineColumn {
|
||||||
|
fn from(value: (usize, usize)) -> Self {
|
||||||
|
Self {
|
||||||
|
line: value.0.max(1),
|
||||||
|
column: value.1.max(1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LineColumn {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}:{}", self.line, self.column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::input::LineColumn;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_line_column_display() {
|
||||||
|
assert_eq!(LineColumn::from((1, 2)).to_string(), "1:2");
|
||||||
|
assert_eq!(LineColumn::from((10, 10)).to_string(), "10:10");
|
||||||
|
assert_eq!(LineColumn::from((0, 0)).to_string(), "1:1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,14 +21,14 @@ const BOTTOM_MARGIN_ROWS: usize = 1;
|
||||||
const LINE_NUMBER_MARGIN_RIGHT: Pixels = px(10.);
|
const LINE_NUMBER_MARGIN_RIGHT: Pixels = px(10.);
|
||||||
|
|
||||||
pub(super) struct TextElement {
|
pub(super) struct TextElement {
|
||||||
input: Entity<InputState>,
|
state: Entity<InputState>,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextElement {
|
impl TextElement {
|
||||||
pub(super) fn new(input: Entity<InputState>) -> Self {
|
pub(super) fn new(state: Entity<InputState>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
input,
|
state,
|
||||||
placeholder: SharedString::default(),
|
placeholder: SharedString::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,12 +41,12 @@ impl TextElement {
|
||||||
|
|
||||||
fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
|
fn paint_mouse_listeners(&mut self, window: &mut Window, _: &mut App) {
|
||||||
window.on_mouse_event({
|
window.on_mouse_event({
|
||||||
let input = self.input.clone();
|
let state = self.state.clone();
|
||||||
|
|
||||||
move |event: &MouseMoveEvent, _, window, cx| {
|
move |event: &MouseMoveEvent, _, window, cx| {
|
||||||
if event.pressed_button == Some(MouseButton::Left) {
|
if event.pressed_button == Some(MouseButton::Left) {
|
||||||
input.update(cx, |input, cx| {
|
state.update(cx, |state, cx| {
|
||||||
input.on_drag_move(event, window, cx);
|
state.on_drag_move(event, window, cx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,19 +67,19 @@ impl TextElement {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {
|
) -> (Option<Bounds<Pixels>>, Point<Pixels>, Option<usize>) {
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let mut selected_range = input.selected_range.clone();
|
let mut selected_range = state.selected_range;
|
||||||
if let Some(marked_range) = &input.marked_range {
|
if let Some(marked_range) = &state.marked_range {
|
||||||
selected_range = marked_range.end..marked_range.end;
|
selected_range = (marked_range.end..marked_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
let cursor_offset = input.cursor_offset();
|
let cursor = state.cursor();
|
||||||
let mut current_line_index = None;
|
let mut current_line_index = None;
|
||||||
let mut scroll_offset = input.scroll_handle.offset();
|
let mut scroll_offset = state.scroll_handle.offset();
|
||||||
let mut cursor_bounds = None;
|
let mut cursor_bounds = None;
|
||||||
|
|
||||||
// If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.
|
// If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.
|
||||||
let bottom_margin = if input.is_auto_grow() {
|
let bottom_margin = if state.is_auto_grow() {
|
||||||
px(0.) + line_height
|
px(0.) + line_height
|
||||||
} else {
|
} else {
|
||||||
BOTTOM_MARGIN_ROWS * line_height + line_height
|
BOTTOM_MARGIN_ROWS * line_height + line_height
|
||||||
|
|
@ -99,7 +99,8 @@ impl TextElement {
|
||||||
|
|
||||||
let line_origin = point(px(0.), offset_y);
|
let line_origin = point(px(0.), offset_y);
|
||||||
if cursor_pos.is_none() {
|
if cursor_pos.is_none() {
|
||||||
let offset = cursor_offset.saturating_sub(prev_lines_offset);
|
let offset = cursor.offset.saturating_sub(prev_lines_offset);
|
||||||
|
|
||||||
if let Some(pos) = line.position_for_index(offset, line_height) {
|
if let Some(pos) = line.position_for_index(offset, line_height) {
|
||||||
current_line_index = Some(line_ix);
|
current_line_index = Some(line_ix);
|
||||||
cursor_pos = Some(line_origin + pos);
|
cursor_pos = Some(line_origin + pos);
|
||||||
|
|
@ -126,8 +127,8 @@ impl TextElement {
|
||||||
if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =
|
if let (Some(cursor_pos), Some(cursor_start), Some(cursor_end)) =
|
||||||
(cursor_pos, cursor_start, cursor_end)
|
(cursor_pos, cursor_start, cursor_end)
|
||||||
{
|
{
|
||||||
let cursor_moved = input.last_cursor_offset != Some(cursor_offset);
|
let cursor_moved = state.last_cursor != Some(cursor);
|
||||||
let selection_changed = input.last_selected_range != Some(selected_range.clone());
|
let selection_changed = state.last_selected_range != Some(selected_range);
|
||||||
|
|
||||||
if cursor_moved || selection_changed {
|
if cursor_moved || selection_changed {
|
||||||
scroll_offset.x =
|
scroll_offset.x =
|
||||||
|
|
@ -152,7 +153,7 @@ impl TextElement {
|
||||||
scroll_offset.y
|
scroll_offset.y
|
||||||
};
|
};
|
||||||
|
|
||||||
if input.selection_reversed {
|
if state.selection_reversed {
|
||||||
if scroll_offset.x + cursor_start.x < px(0.) {
|
if scroll_offset.x + cursor_start.x < px(0.) {
|
||||||
// selection start is out of left
|
// selection start is out of left
|
||||||
scroll_offset.x = -cursor_start.x;
|
scroll_offset.x = -cursor_start.x;
|
||||||
|
|
@ -173,7 +174,7 @@ impl TextElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if input.show_cursor(window, cx) {
|
if state.show_cursor(window, cx) {
|
||||||
// cursor blink
|
// cursor blink
|
||||||
let cursor_height = line_height;
|
let cursor_height = line_height;
|
||||||
cursor_bounds = Some(Bounds::new(
|
cursor_bounds = Some(Bounds::new(
|
||||||
|
|
@ -200,11 +201,11 @@ impl TextElement {
|
||||||
_: &mut Window,
|
_: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Option<Path<Pixels>> {
|
) -> Option<Path<Pixels>> {
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let mut selected_range = input.selected_range.clone();
|
let mut selected_range = state.selected_range;
|
||||||
if let Some(marked_range) = &input.marked_range {
|
if let Some(marked_range) = &state.marked_range {
|
||||||
if !marked_range.is_empty() {
|
if !marked_range.is_empty() {
|
||||||
selected_range = marked_range.end..marked_range.end;
|
selected_range = (marked_range.end..marked_range.end).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if selected_range.is_empty() {
|
if selected_range.is_empty() {
|
||||||
|
|
@ -368,7 +369,7 @@ impl TextElement {
|
||||||
let theme = LanguageRegistry::global(cx)
|
let theme = LanguageRegistry::global(cx)
|
||||||
.theme(cx.theme().is_dark())
|
.theme(cx.theme().is_dark())
|
||||||
.clone();
|
.clone();
|
||||||
self.input.update(cx, |state, cx| match &state.mode {
|
self.state.update(cx, |state, cx| match &state.mode {
|
||||||
InputMode::CodeEditor {
|
InputMode::CodeEditor {
|
||||||
language,
|
language,
|
||||||
highlighter,
|
highlighter,
|
||||||
|
|
@ -504,19 +505,19 @@ impl Element for TextElement {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (LayoutId, Self::RequestLayoutState) {
|
) -> (LayoutId, Self::RequestLayoutState) {
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
|
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
if self.input.read(cx).is_multi_line() {
|
if state.is_multi_line() {
|
||||||
style.flex_grow = 1.0;
|
style.flex_grow = 1.0;
|
||||||
if let Some(h) = input.mode.height() {
|
if let Some(h) = state.mode.height() {
|
||||||
style.size.height = h.into();
|
style.size.height = h.into();
|
||||||
style.min_size.height = line_height.into();
|
style.min_size.height = line_height.into();
|
||||||
} else {
|
} else {
|
||||||
style.size.height = relative(1.).into();
|
style.size.height = relative(1.).into();
|
||||||
style.min_size.height = (input.mode.rows() * line_height).into();
|
style.min_size.height = (state.mode.rows() * line_height).into();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For single-line inputs, the minimum height should be the line height
|
// For single-line inputs, the minimum height should be the line height
|
||||||
|
|
@ -535,15 +536,15 @@ impl Element for TextElement {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Self::PrepaintState {
|
) -> Self::PrepaintState {
|
||||||
let state = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let line_height = window.line_height();
|
let line_height = window.line_height();
|
||||||
|
|
||||||
let visible_range = self.calculate_visible_range(&state, line_height, bounds.size.height);
|
let visible_range = self.calculate_visible_range(&state, line_height, bounds.size.height);
|
||||||
let highlight_styles = self.highlight_lines(&visible_range, cx);
|
let highlight_styles = self.highlight_lines(&visible_range, cx);
|
||||||
|
|
||||||
let multi_line = self.input.read(cx).is_multi_line();
|
let state = self.state.read(cx);
|
||||||
let input = self.input.read(cx);
|
let multi_line = state.is_multi_line();
|
||||||
let text = input.text.clone();
|
let text = state.text.clone();
|
||||||
let is_empty = text.is_empty();
|
let is_empty = text.is_empty();
|
||||||
let placeholder = self.placeholder.clone();
|
let placeholder = self.placeholder.clone();
|
||||||
let style = window.text_style();
|
let style = window.text_style();
|
||||||
|
|
@ -552,7 +553,7 @@ impl Element for TextElement {
|
||||||
|
|
||||||
let (display_text, text_color) = if is_empty {
|
let (display_text, text_color) = if is_empty {
|
||||||
(placeholder, cx.theme().muted_foreground)
|
(placeholder, cx.theme().muted_foreground)
|
||||||
} else if input.masked {
|
} else if state.masked {
|
||||||
(
|
(
|
||||||
"*".repeat(text.chars().count()).into(),
|
"*".repeat(text.chars().count()).into(),
|
||||||
cx.theme().foreground,
|
cx.theme().foreground,
|
||||||
|
|
@ -581,7 +582,7 @@ impl Element for TextElement {
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let line_number_width = if input.mode.line_number() {
|
let line_number_width = if state.mode.line_number() {
|
||||||
empty_line_number.last().unwrap().width() + LINE_NUMBER_MARGIN_RIGHT
|
empty_line_number.last().unwrap().width() + LINE_NUMBER_MARGIN_RIGHT
|
||||||
} else {
|
} else {
|
||||||
px(0.)
|
px(0.)
|
||||||
|
|
@ -620,7 +621,7 @@ impl Element for TextElement {
|
||||||
|
|
||||||
runs.extend(highlight_styles.iter().map(|(range, style)| {
|
runs.extend(highlight_styles.iter().map(|(range, style)| {
|
||||||
let mut run = text_style.clone().highlight(*style).to_run(range.len());
|
let mut run = text_style.clone().highlight(*style).to_run(range.len());
|
||||||
if let Some(marked_range) = &input.marked_range {
|
if let Some(marked_range) = &state.marked_range {
|
||||||
if range.start >= marked_range.start && range.end <= marked_range.end {
|
if range.start >= marked_range.start && range.end <= marked_range.end {
|
||||||
run.color = marked_run.color;
|
run.color = marked_run.color;
|
||||||
run.strikethrough = marked_run.strikethrough;
|
run.strikethrough = marked_run.strikethrough;
|
||||||
|
|
@ -635,20 +636,20 @@ impl Element for TextElement {
|
||||||
} else {
|
} else {
|
||||||
vec![run]
|
vec![run]
|
||||||
}
|
}
|
||||||
} else if let Some(marked_range) = &input.marked_range {
|
} else if let Some(marked_range) = &state.marked_range {
|
||||||
// IME marked text
|
// IME marked text
|
||||||
vec![
|
vec![
|
||||||
TextRun {
|
TextRun {
|
||||||
len: marked_range.start,
|
len: marked_range.start.offset,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
TextRun {
|
TextRun {
|
||||||
len: marked_range.end - marked_range.start,
|
len: marked_range.end.offset - marked_range.start.offset,
|
||||||
underline: marked_run.underline,
|
underline: marked_run.underline,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
TextRun {
|
TextRun {
|
||||||
len: display_text.len() - marked_range.end,
|
len: display_text.len() - marked_range.end.offset,
|
||||||
..run.clone()
|
..run.clone()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
@ -740,8 +741,8 @@ impl Element for TextElement {
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
let input = self.input.read(cx);
|
let state = self.state.read(cx);
|
||||||
let line_numbers = if input.mode.line_number() {
|
let line_numbers = if state.mode.line_number() {
|
||||||
let mut line_numbers = vec![];
|
let mut line_numbers = vec![];
|
||||||
let run_len = 4;
|
let run_len = 4;
|
||||||
let other_line_runs = vec![TextRun {
|
let other_line_runs = vec![TextRun {
|
||||||
|
|
@ -820,21 +821,21 @@ impl Element for TextElement {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let focus_handle = self.input.read(cx).focus_handle.clone();
|
let focus_handle = self.state.read(cx).focus_handle.clone();
|
||||||
let focused = focus_handle.is_focused(window);
|
let focused = focus_handle.is_focused(window);
|
||||||
let bounds = prepaint.bounds;
|
let bounds = prepaint.bounds;
|
||||||
let selected_range = self.input.read(cx).selected_range.clone();
|
let selected_range = self.state.read(cx).selected_range;
|
||||||
let visible_range = &prepaint.last_layout.visible_range;
|
let visible_range = &prepaint.last_layout.visible_range;
|
||||||
|
|
||||||
window.handle_input(
|
window.handle_input(
|
||||||
&focus_handle,
|
&focus_handle,
|
||||||
ElementInputHandler::new(bounds, self.input.clone()),
|
ElementInputHandler::new(bounds, self.state.clone()),
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set Root focused_input when self is focused
|
// Set Root focused_input when self is focused
|
||||||
if focused {
|
if focused {
|
||||||
let state = self.input.clone();
|
let state = self.state.clone();
|
||||||
if Root::read(window, cx).focused_input.as_ref() != Some(&state) {
|
if Root::read(window, cx).focused_input.as_ref() != Some(&state) {
|
||||||
Root::update(window, cx, |root, _, cx| {
|
Root::update(window, cx, |root, _, cx| {
|
||||||
root.focused_input = Some(state);
|
root.focused_input = Some(state);
|
||||||
|
|
@ -845,7 +846,7 @@ impl Element for TextElement {
|
||||||
|
|
||||||
// And reset focused_input when next_frame start
|
// And reset focused_input when next_frame start
|
||||||
window.on_next_frame({
|
window.on_next_frame({
|
||||||
let state = self.input.clone();
|
let state = self.state.clone();
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
if !focused && Root::read(window, cx).focused_input.as_ref() == Some(&state) {
|
if !focused && Root::read(window, cx).focused_input.as_ref() == Some(&state) {
|
||||||
Root::update(window, cx, |root, _, cx| {
|
Root::update(window, cx, |root, _, cx| {
|
||||||
|
|
@ -866,7 +867,7 @@ impl Element for TextElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mask_offset_y = px(0.);
|
let mut mask_offset_y = px(0.);
|
||||||
if self.input.read(cx).masked {
|
if self.state.read(cx).masked {
|
||||||
// Move down offset for vertical centering the *****
|
// Move down offset for vertical centering the *****
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
mask_offset_y = px(3.);
|
mask_offset_y = px(3.);
|
||||||
|
|
@ -931,15 +932,15 @@ impl Element for TextElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.input.update(cx, |input, cx| {
|
self.state.update(cx, |state, cx| {
|
||||||
input.last_layout = Some(prepaint.last_layout.clone());
|
state.last_layout = Some(prepaint.last_layout.clone());
|
||||||
input.last_bounds = Some(bounds);
|
state.last_bounds = Some(bounds);
|
||||||
input.last_cursor_offset = Some(input.cursor_offset());
|
state.last_cursor = Some(state.cursor());
|
||||||
input.set_input_bounds(input_bounds, cx);
|
state.set_input_bounds(input_bounds, cx);
|
||||||
input.last_selected_range = Some(selected_range);
|
state.last_selected_range = Some(selected_range);
|
||||||
input.scroll_size = prepaint.scroll_size;
|
state.scroll_size = prepaint.scroll_size;
|
||||||
input.line_number_width = prepaint.line_number_width;
|
state.line_number_width = prepaint.line_number_width;
|
||||||
input
|
state
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.set_offset(prepaint.cursor_scroll_offset);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use crate::{highlighter::HighlightTheme, input::InputState};
|
use crate::{
|
||||||
|
highlighter::HighlightTheme,
|
||||||
|
input::{InputState, LineColumn},
|
||||||
|
};
|
||||||
use gpui::{px, HighlightStyle, Hsla, SharedString, UnderlineStyle};
|
use gpui::{px, HighlightStyle, Hsla, SharedString, UnderlineStyle};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
@ -75,24 +78,6 @@ impl Marker {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Line and column position (1-based) in the source code.
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
||||||
pub struct LineColumn {
|
|
||||||
/// Line number (1-based)
|
|
||||||
pub line: usize,
|
|
||||||
/// Column number (1-based)
|
|
||||||
pub column: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(usize, usize)> for LineColumn {
|
|
||||||
fn from(value: (usize, usize)) -> Self {
|
|
||||||
Self {
|
|
||||||
line: value.0.max(1),
|
|
||||||
column: value.1.max(1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Severity of the marker.
|
/// Severity of the marker.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
mod blink_cursor;
|
mod blink_cursor;
|
||||||
mod change;
|
mod change;
|
||||||
mod clear_button;
|
mod clear_button;
|
||||||
|
mod cursor;
|
||||||
mod element;
|
mod element;
|
||||||
mod hover_popover;
|
mod hover_popover;
|
||||||
mod marker;
|
mod marker;
|
||||||
|
|
@ -13,6 +14,7 @@ mod text_input;
|
||||||
mod text_wrapper;
|
mod text_wrapper;
|
||||||
|
|
||||||
pub(crate) use clear_button::*;
|
pub(crate) use clear_button::*;
|
||||||
|
pub(super) use cursor::*;
|
||||||
pub use marker::*;
|
pub use marker::*;
|
||||||
pub use mask_pattern::MaskPattern;
|
pub use mask_pattern::MaskPattern;
|
||||||
pub use mode::TabSize;
|
pub use mode::TabSize;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ use super::{
|
||||||
};
|
};
|
||||||
use crate::input::hover_popover::DiagnosticPopover;
|
use crate::input::hover_popover::DiagnosticPopover;
|
||||||
use crate::input::marker::Marker;
|
use crate::input::marker::Marker;
|
||||||
|
use crate::input::{Cursor, LineColumn, Selection};
|
||||||
use crate::{history::History, scroll::ScrollbarState, Root};
|
use crate::{history::History, scroll::ScrollbarState, Root};
|
||||||
|
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
|
@ -244,19 +245,19 @@ pub struct InputState {
|
||||||
///
|
///
|
||||||
/// - "Hello 世界💝" = 16
|
/// - "Hello 世界💝" = 16
|
||||||
/// - "💝" = 4
|
/// - "💝" = 4
|
||||||
pub(super) selected_range: Range<usize>,
|
pub(super) selected_range: Selection,
|
||||||
/// Range for save the selected word, use to keep word range when drag move.
|
/// Range for save the selected word, use to keep word range when drag move.
|
||||||
pub(super) selected_word_range: Option<Range<usize>>,
|
pub(super) selected_word_range: Option<Selection>,
|
||||||
pub(super) selection_reversed: bool,
|
pub(super) selection_reversed: bool,
|
||||||
/// The marked range is the temporary insert text on IME typing.
|
/// The marked range is the temporary insert text on IME typing.
|
||||||
pub(super) marked_range: Option<Range<usize>>,
|
pub(super) marked_range: Option<Selection>,
|
||||||
pub(super) last_layout: Option<LastLayout>,
|
pub(super) last_layout: Option<LastLayout>,
|
||||||
pub(super) last_cursor_offset: Option<usize>,
|
pub(super) last_cursor: Option<Cursor>,
|
||||||
/// The input container bounds
|
/// The input container bounds
|
||||||
pub(super) input_bounds: Bounds<Pixels>,
|
pub(super) input_bounds: Bounds<Pixels>,
|
||||||
/// The text bounds
|
/// The text bounds
|
||||||
pub(super) last_bounds: Option<Bounds<Pixels>>,
|
pub(super) last_bounds: Option<Bounds<Pixels>>,
|
||||||
pub(super) last_selected_range: Option<Range<usize>>,
|
pub(super) last_selected_range: Option<Selection>,
|
||||||
pub(super) selecting: bool,
|
pub(super) selecting: bool,
|
||||||
pub(super) disabled: bool,
|
pub(super) disabled: bool,
|
||||||
pub(super) masked: bool,
|
pub(super) masked: bool,
|
||||||
|
|
@ -322,7 +323,7 @@ impl InputState {
|
||||||
),
|
),
|
||||||
blink_cursor,
|
blink_cursor,
|
||||||
history,
|
history,
|
||||||
selected_range: 0..0,
|
selected_range: (Cursor::new(0)..Cursor::new(0)).into(),
|
||||||
selected_word_range: None,
|
selected_word_range: None,
|
||||||
selection_reversed: false,
|
selection_reversed: false,
|
||||||
marked_range: None,
|
marked_range: None,
|
||||||
|
|
@ -338,7 +339,7 @@ impl InputState {
|
||||||
last_layout: None,
|
last_layout: None,
|
||||||
last_bounds: None,
|
last_bounds: None,
|
||||||
last_selected_range: None,
|
last_selected_range: None,
|
||||||
last_cursor_offset: None,
|
last_cursor: None,
|
||||||
scroll_handle: ScrollHandle::new(),
|
scroll_handle: ScrollHandle::new(),
|
||||||
scroll_state: ScrollbarState::default(),
|
scroll_state: ScrollbarState::default(),
|
||||||
scroll_size: gpui::size(px(0.), px(0.)),
|
scroll_size: gpui::size(px(0.), px(0.)),
|
||||||
|
|
@ -519,7 +520,7 @@ impl InputState {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Find which line and sub-line the cursor is on and its position
|
// Find which line and sub-line the cursor is on and its position
|
||||||
let (_, _, cursor_pos) = self.line_and_position_for_offset(self.cursor_offset());
|
let (_, _, cursor_pos) = self.line_and_position_for_offset(self.cursor().offset);
|
||||||
|
|
||||||
if let Some(pos) = cursor_pos {
|
if let Some(pos) = cursor_pos {
|
||||||
self.preferred_x_offset = Some(pos.x + bounds.origin.x);
|
self.preferred_x_offset = Some(pos.x + bounds.origin.x);
|
||||||
|
|
@ -570,7 +571,7 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let offset = self.cursor_offset();
|
let offset = self.cursor().offset;
|
||||||
let preferred_x_offset = self.preferred_x_offset;
|
let preferred_x_offset = self.preferred_x_offset;
|
||||||
let line_height = last_layout.line_height;
|
let line_height = last_layout.line_height;
|
||||||
let (current_line, current_sub_line, current_pos) =
|
let (current_line, current_sub_line, current_pos) =
|
||||||
|
|
@ -591,7 +592,7 @@ impl InputState {
|
||||||
// Handle moving above the first line
|
// Handle moving above the first line
|
||||||
if move_lines < 0 && current_line == 0 && new_sub_line < 0 {
|
if move_lines < 0 && current_line == 0 && new_sub_line < 0 {
|
||||||
// Move cursor to the beginning of the text
|
// Move cursor to the beginning of the text
|
||||||
self.move_to(0, window, cx);
|
self.move_to(Cursor::new(0), window, cx);
|
||||||
self.preferred_x_offset = preferred_x_offset;
|
self.preferred_x_offset = preferred_x_offset;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -645,7 +646,8 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_offset = (prev_lines_offset + new_local_index).min(self.text.len());
|
let new_offset = (prev_lines_offset + new_local_index).min(self.text.len());
|
||||||
self.selected_range = new_offset..new_offset;
|
let new_cursor = Cursor::new(new_offset);
|
||||||
|
self.selected_range = (new_cursor..new_cursor).into();
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
// Set back the preferred_x_offset
|
// Set back the preferred_x_offset
|
||||||
self.preferred_x_offset = preferred_x_offset;
|
self.preferred_x_offset = preferred_x_offset;
|
||||||
|
|
@ -684,9 +686,10 @@ impl InputState {
|
||||||
self.history.ignore = false;
|
self.history.ignore = false;
|
||||||
// Ensure cursor to start when set text
|
// Ensure cursor to start when set text
|
||||||
if self.is_single_line() {
|
if self.is_single_line() {
|
||||||
self.selected_range = self.text.len()..self.text.len();
|
self.selected_range =
|
||||||
|
(Cursor::new(self.text.len())..Cursor::new(self.text.len())).into();
|
||||||
} else {
|
} else {
|
||||||
self.selected_range = 0..0;
|
self.selected_range = (Cursor::new(0)..Cursor::new(0)).into();
|
||||||
}
|
}
|
||||||
// Move scroll to top
|
// Move scroll to top
|
||||||
self.scroll_handle.set_offset(point(px(0.), px(0.)));
|
self.scroll_handle.set_offset(point(px(0.), px(0.)));
|
||||||
|
|
@ -704,9 +707,9 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let text: SharedString = text.into();
|
let text: SharedString = text.into();
|
||||||
let range_utf16 = self.range_to_utf16(&(self.cursor_offset()..self.cursor_offset()));
|
let range_utf16 = self.range_to_utf16(&(self.cursor().offset..self.cursor().offset));
|
||||||
self.replace_text_in_range(Some(range_utf16), &text, window, cx);
|
self.replace_text_in_range(Some(range_utf16), &text, window, cx);
|
||||||
self.selected_range = self.selected_range.end..self.selected_range.end;
|
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replace text at the current cursor position.
|
/// Replace text at the current cursor position.
|
||||||
|
|
@ -720,7 +723,7 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let text: SharedString = text.into();
|
let text: SharedString = text.into();
|
||||||
self.replace_text_in_range(None, &text, window, cx);
|
self.replace_text_in_range(None, &text, window, cx);
|
||||||
self.selected_range = self.selected_range.end..self.selected_range.end;
|
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_text(
|
fn replace_text(
|
||||||
|
|
@ -819,6 +822,11 @@ impl InputState {
|
||||||
self.mask_pattern.unmask(&self.text).into()
|
self.mask_pattern.unmask(&self.text).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the line and column (1-based) of the cursor.
|
||||||
|
pub fn line_column(&self) -> LineColumn {
|
||||||
|
self.text_wrapper.line_column(self.cursor().offset)
|
||||||
|
}
|
||||||
|
|
||||||
/// Focus the input field.
|
/// Focus the input field.
|
||||||
pub fn focus(&self, window: &mut Window, _: &mut Context<Self>) {
|
pub fn focus(&self, window: &mut Window, _: &mut Context<Self>) {
|
||||||
self.focus_handle.focus(window);
|
self.focus_handle.focus(window);
|
||||||
|
|
@ -827,7 +835,11 @@ impl InputState {
|
||||||
pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
self.move_to(
|
||||||
|
Cursor::new(self.previous_boundary(self.cursor().offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.move_to(self.selected_range.start, window, cx)
|
self.move_to(self.selected_range.start, window, cx)
|
||||||
}
|
}
|
||||||
|
|
@ -836,7 +848,11 @@ impl InputState {
|
||||||
pub(super) fn right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.next_boundary(self.selected_range.end), window, cx);
|
self.move_to(
|
||||||
|
Cursor::new(self.next_boundary(self.selected_range.end.offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
self.move_to(self.selected_range.end, window, cx)
|
self.move_to(self.selected_range.end, window, cx)
|
||||||
}
|
}
|
||||||
|
|
@ -849,7 +865,7 @@ impl InputState {
|
||||||
|
|
||||||
if !self.selected_range.is_empty() {
|
if !self.selected_range.is_empty() {
|
||||||
self.move_to(
|
self.move_to(
|
||||||
self.previous_boundary(self.selected_range.start.saturating_sub(1)),
|
Cursor::new(self.previous_boundary(self.selected_range.start.saturating_sub(1))),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
@ -865,7 +881,7 @@ impl InputState {
|
||||||
|
|
||||||
if !self.selected_range.is_empty() {
|
if !self.selected_range.is_empty() {
|
||||||
self.move_to(
|
self.move_to(
|
||||||
self.next_boundary(self.selected_range.end.saturating_sub(1)),
|
Cursor::new(self.next_boundary(self.selected_range.end.offset.saturating_sub(1))),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
@ -912,7 +928,11 @@ impl InputState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
self.select_to(
|
||||||
|
Cursor::new(self.previous_boundary(self.cursor().offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_right(
|
pub(super) fn select_right(
|
||||||
|
|
@ -921,7 +941,11 @@ impl InputState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx);
|
self.select_to(
|
||||||
|
Cursor::new(self.next_boundary(self.cursor().offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
|
@ -929,7 +953,7 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let offset = self.start_of_line(window, cx).saturating_sub(1);
|
let offset = self.start_of_line(window, cx).saturating_sub(1);
|
||||||
self.select_to(self.previous_boundary(offset), window, cx);
|
self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_down(
|
pub(super) fn select_down(
|
||||||
|
|
@ -942,7 +966,7 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let offset = (self.end_of_line(window, cx) + 1).min(self.text.len());
|
let offset = (self.end_of_line(window, cx) + 1).min(self.text.len());
|
||||||
self.select_to(self.next_boundary(offset), window, cx);
|
self.select_to(Cursor::new(self.next_boundary(offset)), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_all(
|
pub(super) fn select_all(
|
||||||
|
|
@ -951,20 +975,20 @@ impl InputState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.move_to(0, window, cx);
|
self.move_to(Cursor::new(0), window, cx);
|
||||||
self.select_to(self.text.len(), window, cx)
|
self.select_to(Cursor::new(self.text.len()), window, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn home(&mut self, _: &MoveHome, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
let offset = self.start_of_line(window, cx);
|
let offset = self.start_of_line(window, cx);
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn end(&mut self, _: &MoveEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
let offset = self.end_of_line(window, cx);
|
let offset = self.end_of_line(window, cx);
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn move_to_start(
|
pub(super) fn move_to_start(
|
||||||
|
|
@ -973,7 +997,7 @@ impl InputState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.move_to(0, window, cx);
|
self.move_to(Cursor::new(0), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn move_to_end(
|
pub(super) fn move_to_end(
|
||||||
|
|
@ -983,7 +1007,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let end = self.text.len();
|
let end = self.text.len();
|
||||||
self.move_to(end, window, cx);
|
self.move_to(Cursor::new(end), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn move_to_previous_word(
|
pub(super) fn move_to_previous_word(
|
||||||
|
|
@ -993,7 +1017,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.previous_start_of_word();
|
let offset = self.previous_start_of_word();
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn move_to_next_word(
|
pub(super) fn move_to_next_word(
|
||||||
|
|
@ -1003,7 +1027,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.next_end_of_word();
|
let offset = self.next_end_of_word();
|
||||||
self.move_to(offset, window, cx);
|
self.move_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_start(
|
pub(super) fn select_to_start(
|
||||||
|
|
@ -1012,7 +1036,7 @@ impl InputState {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
self.select_to(0, window, cx);
|
self.select_to(Cursor::new(0), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_end(
|
pub(super) fn select_to_end(
|
||||||
|
|
@ -1022,7 +1046,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let end = self.text.len();
|
let end = self.text.len();
|
||||||
self.select_to(end, window, cx);
|
self.select_to(Cursor::new(end), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_start_of_line(
|
pub(super) fn select_to_start_of_line(
|
||||||
|
|
@ -1032,7 +1056,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.start_of_line(window, cx);
|
let offset = self.start_of_line(window, cx);
|
||||||
self.select_to(self.previous_boundary(offset), window, cx);
|
self.select_to(Cursor::new(self.previous_boundary(offset)), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_end_of_line(
|
pub(super) fn select_to_end_of_line(
|
||||||
|
|
@ -1042,7 +1066,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.end_of_line(window, cx);
|
let offset = self.end_of_line(window, cx);
|
||||||
self.select_to(self.next_boundary(offset), window, cx);
|
self.select_to(Cursor::new(self.next_boundary(offset)), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_previous_word(
|
pub(super) fn select_to_previous_word(
|
||||||
|
|
@ -1052,7 +1076,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.previous_start_of_word();
|
let offset = self.previous_start_of_word();
|
||||||
self.select_to(offset, window, cx);
|
self.select_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_to_next_word(
|
pub(super) fn select_to_next_word(
|
||||||
|
|
@ -1062,12 +1086,12 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let offset = self.next_end_of_word();
|
let offset = self.next_end_of_word();
|
||||||
self.select_to(offset, window, cx);
|
self.select_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the start offset of the previous word.
|
/// Return the start offset of the previous word.
|
||||||
fn previous_start_of_word(&mut self) -> usize {
|
fn previous_start_of_word(&mut self) -> usize {
|
||||||
let offset = self.selected_range.start;
|
let offset = self.selected_range.start.offset;
|
||||||
let prev_str = self.text_for_range_utf8(0..offset);
|
let prev_str = self.text_for_range_utf8(0..offset);
|
||||||
UnicodeSegmentation::split_word_bound_indices(prev_str)
|
UnicodeSegmentation::split_word_bound_indices(prev_str)
|
||||||
.filter(|(_, s)| !s.trim_start().is_empty())
|
.filter(|(_, s)| !s.trim_start().is_empty())
|
||||||
|
|
@ -1078,7 +1102,7 @@ impl InputState {
|
||||||
|
|
||||||
/// Return the next end offset of the next word.
|
/// Return the next end offset of the next word.
|
||||||
fn next_end_of_word(&mut self) -> usize {
|
fn next_end_of_word(&mut self) -> usize {
|
||||||
let offset = self.cursor_offset();
|
let offset = self.cursor().offset;
|
||||||
let next_str = self.text_for_range_utf8(offset..self.text.len());
|
let next_str = self.text_for_range_utf8(offset..self.text.len());
|
||||||
UnicodeSegmentation::split_word_bound_indices(next_str)
|
UnicodeSegmentation::split_word_bound_indices(next_str)
|
||||||
.find(|(_, s)| !s.trim_start().is_empty())
|
.find(|(_, s)| !s.trim_start().is_empty())
|
||||||
|
|
@ -1092,7 +1116,7 @@ impl InputState {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = self.previous_boundary(self.cursor_offset());
|
let offset = self.previous_boundary(self.cursor().offset);
|
||||||
let line = self
|
let line = self
|
||||||
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
|
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|
@ -1110,8 +1134,11 @@ impl InputState {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut offset =
|
let mut offset = self.previous_boundary(
|
||||||
self.previous_boundary(self.selected_range.start.min(self.selected_range.end));
|
self.selected_range
|
||||||
|
.start
|
||||||
|
.min(self.selected_range.end.offset),
|
||||||
|
);
|
||||||
if self.text.chars().nth(offset) == Some('\r') {
|
if self.text.chars().nth(offset) == Some('\r') {
|
||||||
offset += 1;
|
offset += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -1131,7 +1158,7 @@ impl InputState {
|
||||||
return self.text.len();
|
return self.text.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = self.next_boundary(self.cursor_offset());
|
let offset = self.next_boundary(self.cursor().offset);
|
||||||
// ignore if offset is "\n"
|
// ignore if offset is "\n"
|
||||||
if self
|
if self
|
||||||
.text_for_range(
|
.text_for_range(
|
||||||
|
|
@ -1205,7 +1232,11 @@ impl InputState {
|
||||||
|
|
||||||
pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx)
|
self.select_to(
|
||||||
|
Cursor::new(self.previous_boundary(self.cursor().offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(None, "", window, cx);
|
self.replace_text_in_range(None, "", window, cx);
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
|
|
@ -1213,7 +1244,11 @@ impl InputState {
|
||||||
|
|
||||||
pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx)
|
self.select_to(
|
||||||
|
Cursor::new(self.next_boundary(self.cursor().offset)),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(None, "", window, cx);
|
self.replace_text_in_range(None, "", window, cx);
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
|
|
@ -1226,11 +1261,11 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let mut offset = self.start_of_line(window, cx);
|
let mut offset = self.start_of_line(window, cx);
|
||||||
if offset == self.cursor_offset() {
|
if offset == self.cursor().offset {
|
||||||
offset = offset.saturating_sub(1);
|
offset = offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range(
|
||||||
Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
|
Some(self.range_to_utf16(&(offset..self.cursor().offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1246,11 +1281,11 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let mut offset = self.end_of_line(window, cx);
|
let mut offset = self.end_of_line(window, cx);
|
||||||
if offset == self.cursor_offset() {
|
if offset == self.cursor().offset {
|
||||||
offset = (offset + 1).clamp(0, self.text.len());
|
offset = (offset + 1).clamp(0, self.text.len());
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range(
|
||||||
Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
|
Some(self.range_to_utf16(&(self.cursor().offset..offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1266,7 +1301,7 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let offset = self.previous_start_of_word();
|
let offset = self.previous_start_of_word();
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range(
|
||||||
Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
|
Some(self.range_to_utf16(&(offset..self.cursor().offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1282,7 +1317,7 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let offset = self.next_end_of_word();
|
let offset = self.next_end_of_word();
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range(
|
||||||
Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
|
Some(self.range_to_utf16(&(self.cursor().offset..offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1346,7 +1381,7 @@ impl InputState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tab_indent = tab_size.to_string();
|
let tab_indent = tab_size.to_string();
|
||||||
let selected_range = self.selected_range.clone();
|
let selected_range = self.selected_range;
|
||||||
let mut added_len = 0;
|
let mut added_len = 0;
|
||||||
let is_selected = !self.selected_range.is_empty();
|
let is_selected = !self.selected_range.is_empty();
|
||||||
|
|
||||||
|
|
@ -1356,7 +1391,7 @@ impl InputState {
|
||||||
|
|
||||||
let selected_text = self
|
let selected_text = self
|
||||||
.text_for_range(
|
.text_for_range(
|
||||||
self.range_to_utf16(&(offset..selected_range.end)),
|
self.range_to_utf16(&(offset..selected_range.end.offset)),
|
||||||
&mut None,
|
&mut None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1376,14 +1411,15 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_selected {
|
if is_selected {
|
||||||
self.selected_range = start_offset..selected_range.end + added_len;
|
self.selected_range =
|
||||||
|
(Cursor::new(start_offset)..selected_range.end + added_len).into();
|
||||||
} else {
|
} else {
|
||||||
self.selected_range =
|
self.selected_range =
|
||||||
selected_range.start + added_len..selected_range.end + added_len;
|
(selected_range.start + added_len..selected_range.end + added_len).into();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Selected none
|
// Selected none
|
||||||
let offset = self.selected_range.start;
|
let offset = self.selected_range.start.offset;
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range(
|
||||||
Some(self.range_to_utf16(&(offset..offset))),
|
Some(self.range_to_utf16(&(offset..offset))),
|
||||||
&tab_indent,
|
&tab_indent,
|
||||||
|
|
@ -1392,7 +1428,8 @@ impl InputState {
|
||||||
);
|
);
|
||||||
added_len = tab_indent.len();
|
added_len = tab_indent.len();
|
||||||
|
|
||||||
self.selected_range = selected_range.start + added_len..selected_range.end + added_len;
|
self.selected_range =
|
||||||
|
(selected_range.start + added_len..selected_range.end + added_len).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1402,7 +1439,7 @@ impl InputState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let tab_indent = tab_size.to_string();
|
let tab_indent = tab_size.to_string();
|
||||||
let selected_range = self.selected_range.clone();
|
let selected_range = self.selected_range;
|
||||||
let mut removed_len = 0;
|
let mut removed_len = 0;
|
||||||
let is_selected = !self.selected_range.is_empty();
|
let is_selected = !self.selected_range.is_empty();
|
||||||
|
|
||||||
|
|
@ -1412,7 +1449,7 @@ impl InputState {
|
||||||
|
|
||||||
let selected_text = self
|
let selected_text = self
|
||||||
.text_for_range(
|
.text_for_range(
|
||||||
self.range_to_utf16(&(offset..selected_range.end)),
|
self.range_to_utf16(&(offset..selected_range.end.offset)),
|
||||||
&mut None,
|
&mut None,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
@ -1437,10 +1474,14 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_selected {
|
if is_selected {
|
||||||
self.selected_range = start_offset..selected_range.end.saturating_sub(removed_len);
|
self.selected_range = (Cursor::new(start_offset)
|
||||||
|
..Cursor::new(selected_range.end.saturating_sub(removed_len)))
|
||||||
|
.into();
|
||||||
} else {
|
} else {
|
||||||
self.selected_range = selected_range.start.saturating_sub(removed_len)
|
self.selected_range =
|
||||||
..selected_range.end.saturating_sub(removed_len);
|
(Cursor::new(selected_range.start.saturating_sub(removed_len))
|
||||||
|
..Cursor::new(selected_range.end.saturating_sub(removed_len)))
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Selected none
|
// Selected none
|
||||||
|
|
@ -1458,7 +1499,7 @@ impl InputState {
|
||||||
);
|
);
|
||||||
removed_len = tab_indent.len();
|
removed_len = tab_indent.len();
|
||||||
let new_offset = start_offset.saturating_sub(removed_len);
|
let new_offset = start_offset.saturating_sub(removed_len);
|
||||||
self.selected_range = new_offset..new_offset;
|
self.selected_range = (Cursor::new(new_offset)..Cursor::new(new_offset)).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1505,9 +1546,9 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.modifiers.shift {
|
if event.modifiers.shift {
|
||||||
self.select_to(offset, window, cx);
|
self.select_to(Cursor::new(offset), window, cx);
|
||||||
} else {
|
} else {
|
||||||
self.move_to(offset, window, cx)
|
self.move_to(Cursor::new(offset), window, cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1594,9 +1635,7 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected_text = self
|
let selected_text = self.text_for_range_utf8(self.selected_range).to_string();
|
||||||
.text_for_range_utf8(self.selected_range.clone())
|
|
||||||
.to_string();
|
|
||||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1605,9 +1644,7 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected_text = self
|
let selected_text = self.text_for_range_utf8(self.selected_range).to_string();
|
||||||
.text_for_range_utf8(self.selected_range.clone())
|
|
||||||
.to_string();
|
|
||||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||||
self.replace_text_in_range(None, "", window, cx);
|
self.replace_text_in_range(None, "", window, cx);
|
||||||
}
|
}
|
||||||
|
|
@ -1640,19 +1677,15 @@ impl InputState {
|
||||||
|
|
||||||
let new_range = range.start..range.start + new_text.len();
|
let new_range = range.start..range.start + new_text.len();
|
||||||
|
|
||||||
self.history.push(Change::new(
|
self.history
|
||||||
range.clone(),
|
.push(Change::new(range.clone(), &old_text, new_range, new_text));
|
||||||
&old_text,
|
|
||||||
new_range.clone(),
|
|
||||||
new_text,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.history.ignore = true;
|
self.history.ignore = true;
|
||||||
if let Some(changes) = self.history.undo() {
|
if let Some(changes) = self.history.undo() {
|
||||||
for change in changes {
|
for change in changes {
|
||||||
let range_utf16 = self.range_to_utf16(&change.new_range);
|
let range_utf16 = self.range_to_utf16(&change.new_range.into());
|
||||||
self.replace_text_in_range(Some(range_utf16), &change.old_text, window, cx);
|
self.replace_text_in_range(Some(range_utf16), &change.old_text, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1663,7 +1696,7 @@ impl InputState {
|
||||||
self.history.ignore = true;
|
self.history.ignore = true;
|
||||||
if let Some(changes) = self.history.redo() {
|
if let Some(changes) = self.history.redo() {
|
||||||
for change in changes {
|
for change in changes {
|
||||||
let range_utf16 = self.range_to_utf16(&change.old_range);
|
let range_utf16 = self.range_to_utf16(&change.old_range.into());
|
||||||
self.replace_text_in_range(Some(range_utf16), &change.new_text, window, cx);
|
self.replace_text_in_range(Some(range_utf16), &change.new_text, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1675,15 +1708,18 @@ impl InputState {
|
||||||
/// The offset is the UTF-8 offset.
|
/// The offset is the UTF-8 offset.
|
||||||
///
|
///
|
||||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||||
fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
fn move_to(&mut self, cursor: Cursor, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
let offset = offset.clamp(0, self.text.len());
|
let cursor = Cursor::new(cursor.offset.clamp(0, self.text.len()));
|
||||||
self.selected_range = offset..offset;
|
self.selected_range = (cursor..cursor).into();
|
||||||
self.pause_blink_cursor(cx);
|
self.pause_blink_cursor(cx);
|
||||||
self.update_preferred_x_offset(cx);
|
self.update_preferred_x_offset(cx);
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn cursor_offset(&self) -> usize {
|
/// Get the cursor position.
|
||||||
|
///
|
||||||
|
/// The offset is the UTF-8 offset.
|
||||||
|
pub fn cursor(&self) -> Cursor {
|
||||||
if let Some(marked_range) = &self.marked_range {
|
if let Some(marked_range) = &self.marked_range {
|
||||||
return marked_range.end;
|
return marked_range.end;
|
||||||
}
|
}
|
||||||
|
|
@ -1799,17 +1835,17 @@ impl InputState {
|
||||||
/// The offset is the UTF-8 offset.
|
/// The offset is the UTF-8 offset.
|
||||||
///
|
///
|
||||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||||
fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
fn select_to(&mut self, offset: Cursor, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
let offset = offset.clamp(0, self.text.len());
|
let offset = offset.clamp(0, self.text.len());
|
||||||
if self.selection_reversed {
|
if self.selection_reversed {
|
||||||
self.selected_range.start = offset
|
self.selected_range.start = Cursor::new(offset)
|
||||||
} else {
|
} else {
|
||||||
self.selected_range.end = offset
|
self.selected_range.end = Cursor::new(offset)
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.selected_range.end < self.selected_range.start {
|
if self.selected_range.end < self.selected_range.start {
|
||||||
self.selection_reversed = !self.selection_reversed;
|
self.selection_reversed = !self.selection_reversed;
|
||||||
self.selected_range = self.selected_range.end..self.selected_range.start;
|
self.selected_range = (self.selected_range.end..self.selected_range.start).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure keep word selected range
|
// Ensure keep word selected range
|
||||||
|
|
@ -1878,14 +1914,14 @@ impl InputState {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.selected_range = start..end;
|
self.selected_range = (start..end).into();
|
||||||
self.selected_word_range = Some(self.selected_range.clone());
|
self.selected_word_range = Some(self.selected_range);
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
fn unselect(&mut self, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
let offset = self.next_boundary(self.cursor_offset());
|
let offset = self.next_boundary(self.cursor().offset);
|
||||||
self.selected_range = offset..offset;
|
self.selected_range = (offset..offset).into();
|
||||||
cx.notify()
|
cx.notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1998,7 +2034,7 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||||
self.select_to(offset, window, cx);
|
self.select_to(Cursor::new(offset), window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_input(&self, new_text: &str) -> bool {
|
fn is_valid_input(&self, new_text: &str) -> bool {
|
||||||
|
|
@ -2090,7 +2126,7 @@ impl EntityInputHandler for InputState {
|
||||||
_cx: &mut Context<Self>,
|
_cx: &mut Context<Self>,
|
||||||
) -> Option<UTF16Selection> {
|
) -> Option<UTF16Selection> {
|
||||||
Some(UTF16Selection {
|
Some(UTF16Selection {
|
||||||
range: self.range_to_utf16(&self.selected_range),
|
range: self.range_to_utf16(&self.selected_range.into()),
|
||||||
reversed: false,
|
reversed: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -2101,8 +2137,7 @@ impl EntityInputHandler for InputState {
|
||||||
_cx: &mut Context<Self>,
|
_cx: &mut Context<Self>,
|
||||||
) -> Option<Range<usize>> {
|
) -> Option<Range<usize>> {
|
||||||
self.marked_range
|
self.marked_range
|
||||||
.as_ref()
|
.map(|range| self.range_to_utf16(&range.into()))
|
||||||
.map(|range| self.range_to_utf16(range))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||||
|
|
@ -2127,8 +2162,8 @@ impl EntityInputHandler for InputState {
|
||||||
let range = range_utf16
|
let range = range_utf16
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||||
.or(self.marked_range.clone())
|
.or(self.marked_range.map(|range| range.into()))
|
||||||
.unwrap_or(self.selected_range.clone());
|
.unwrap_or(self.selected_range.into());
|
||||||
|
|
||||||
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
|
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
|
||||||
+ new_text
|
+ new_text
|
||||||
|
|
@ -2149,7 +2184,7 @@ impl EntityInputHandler for InputState {
|
||||||
.update_highlighter(&range, self.text.clone(), &new_text, cx);
|
.update_highlighter(&range, self.text.clone(), &new_text, cx);
|
||||||
self.mode.clear_markers();
|
self.mode.clear_markers();
|
||||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||||
self.selected_range = new_offset..new_offset;
|
self.selected_range = (new_offset..new_offset).into();
|
||||||
self.marked_range.take();
|
self.marked_range.take();
|
||||||
self.update_preferred_x_offset(cx);
|
self.update_preferred_x_offset(cx);
|
||||||
self.update_scroll_offset(None, cx);
|
self.update_scroll_offset(None, cx);
|
||||||
|
|
@ -2174,8 +2209,8 @@ impl EntityInputHandler for InputState {
|
||||||
let range = range_utf16
|
let range = range_utf16
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||||
.or(self.marked_range.clone())
|
.or(self.marked_range.map(|range| range.into()))
|
||||||
.unwrap_or(self.selected_range.clone());
|
.unwrap_or(self.selected_range.into());
|
||||||
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
|
let pending_text: SharedString = (self.text_for_range_utf8(0..range.start).to_owned()
|
||||||
+ new_text
|
+ new_text
|
||||||
+ self.text_for_range_utf8(range.end..self.text.len()))
|
+ self.text_for_range_utf8(range.end..self.text.len()))
|
||||||
|
|
@ -2192,15 +2227,16 @@ impl EntityInputHandler for InputState {
|
||||||
self.text_wrapper.update(self.text.clone(), false, cx);
|
self.text_wrapper.update(self.text.clone(), false, cx);
|
||||||
if new_text.is_empty() {
|
if new_text.is_empty() {
|
||||||
// Cancel selection, when cancel IME input.
|
// Cancel selection, when cancel IME input.
|
||||||
self.selected_range = range.start..range.start;
|
self.selected_range = (range.start..range.start).into();
|
||||||
self.marked_range = None;
|
self.marked_range = None;
|
||||||
} else {
|
} else {
|
||||||
self.marked_range = Some(range.start..range.start + new_text.len());
|
self.marked_range = Some((range.start..range.start + new_text.len()).into());
|
||||||
self.selected_range = new_selected_range_utf16
|
self.selected_range = new_selected_range_utf16
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
.map(|range_utf16| self.range_from_utf16(range_utf16))
|
||||||
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
|
.map(|new_range| new_range.start + range.start..new_range.end + range.end)
|
||||||
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
|
.unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len())
|
||||||
|
.into();
|
||||||
}
|
}
|
||||||
self.mode.update_auto_grow(&self.text_wrapper);
|
self.mode.update_auto_grow(&self.text_wrapper);
|
||||||
cx.emit(InputEvent::Change(self.unmask_value()));
|
cx.emit(InputEvent::Change(self.unmask_value()));
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crate::input::LineColumn;
|
||||||
use gpui::{App, Font, LineFragment, Pixels, SharedString};
|
use gpui::{App, Font, LineFragment, Pixels, SharedString};
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
@ -99,4 +100,19 @@ impl TextWrapper {
|
||||||
self.wrapped_lines = wrapped_lines;
|
self.wrapped_lines = wrapped_lines;
|
||||||
self.lines = lines;
|
self.lines = lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the line and column (1-based) of the given offset (Entire text).
|
||||||
|
pub(super) fn line_column(&self, offset: usize) -> LineColumn {
|
||||||
|
if self.lines.is_empty() {
|
||||||
|
return LineColumn::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = self
|
||||||
|
.lines
|
||||||
|
.binary_search_by_key(&offset, |line| line.range.end)
|
||||||
|
.unwrap_or_else(|i| i);
|
||||||
|
let column = offset.saturating_sub(self.lines[line].range.start);
|
||||||
|
|
||||||
|
(line + 1, column + 1).into()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue