editor: Avoid scroll on select all. (#1277)
- Add `replace_text_in_range_silent` for some case to avoid show completion, for example on cut, paster ... - Improve to only call validation, mask on single line mode. - Improve move cursor to scroll to visible to keep edge 3 lines. https://github.com/user-attachments/assets/ca8bcca8-a55b-4c98-af7c-7639d1c34923 - Fix `RopeExt::offset_to_position` may crash. - Add deffered_scroll_offset to improve paste to scroll to cursor. https://github.com/user-attachments/assets/1af09a50-37f0-47c8-8185-4b78820d064a
This commit is contained in:
parent
4b3731bf14
commit
993ce34bd8
9 changed files with 156 additions and 86 deletions
34
Cargo.lock
generated
34
Cargo.lock
generated
|
|
@ -757,7 +757,7 @@ dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.12.1",
|
"itertools 0.11.0",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -780,7 +780,7 @@ dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools 0.13.0",
|
"itertools 0.11.0",
|
||||||
"log",
|
"log",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -1449,7 +1449,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "collections"
|
name = "collections"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.10.0",
|
"indexmap 2.10.0",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
|
|
@ -1918,7 +1918,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_refineable"
|
name = "derive_refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -3112,7 +3112,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui"
|
name = "gpui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"as-raw-xcb-connection",
|
"as-raw-xcb-connection",
|
||||||
|
|
@ -3297,7 +3297,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpui_macros"
|
name = "gpui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -3566,7 +3566,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client"
|
name = "http_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -3586,7 +3586,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http_client_tls"
|
name = "http_client_tls"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
|
|
@ -4544,7 +4544,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "media"
|
name = "media"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bindgen 0.71.1",
|
"bindgen 0.71.1",
|
||||||
|
|
@ -5602,7 +5602,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "perf"
|
name = "perf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -6519,7 +6519,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "refineable"
|
name = "refineable"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_refineable",
|
"derive_refineable",
|
||||||
"workspace-hack",
|
"workspace-hack",
|
||||||
|
|
@ -6672,7 +6672,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest_client"
|
name = "reqwest_client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -6756,7 +6756,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rope"
|
name = "rope"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -7263,7 +7263,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semantic_version"
|
name = "semantic_version"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -7900,7 +7900,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sum_tree"
|
name = "sum_tree"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -9216,7 +9216,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util"
|
name = "util"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-fs 2.1.3",
|
"async-fs 2.1.3",
|
||||||
|
|
@ -9251,7 +9251,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "util_macros"
|
name = "util_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/zed-industries/zed.git#fbe06238e45c2fec6955c70b86eb26fd6a864266"
|
source = "git+https://github.com/zed-industries/zed.git#4532765ae845b8c98c73c88cc916f7d771b429d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"perf",
|
"perf",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,11 @@ impl Example {
|
||||||
let editor = cx.new(|cx| {
|
let editor = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
.multi_line()
|
.multi_line()
|
||||||
.line_number(true)
|
|
||||||
.tab_size(TabSize {
|
.tab_size(TabSize {
|
||||||
tab_size: 4,
|
tab_size: 4,
|
||||||
hard_tabs: false,
|
hard_tabs: false,
|
||||||
})
|
})
|
||||||
.soft_wrap(false)
|
.soft_wrap(true)
|
||||||
.placeholder("Enter your code here...")
|
.placeholder("Enter your code here...")
|
||||||
.default_value(text)
|
.default_value(text)
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -164,10 +164,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 = state.last_cursor != Some(cursor);
|
|
||||||
let selection_changed = state.last_selected_range != Some(selected_range);
|
let selection_changed = state.last_selected_range != Some(selected_range);
|
||||||
|
if selection_changed {
|
||||||
if cursor_moved || selection_changed {
|
|
||||||
scroll_offset.x = if scroll_offset.x + cursor_pos.x
|
scroll_offset.x = if scroll_offset.x + cursor_pos.x
|
||||||
> (bounds.size.width - line_number_width - RIGHT_MARGIN)
|
> (bounds.size.width - line_number_width - RIGHT_MARGIN)
|
||||||
{
|
{
|
||||||
|
|
@ -225,6 +223,10 @@ impl TextElement {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {
|
||||||
|
scroll_offset = deferred_scroll_offset;
|
||||||
|
}
|
||||||
|
|
||||||
bounds.origin = bounds.origin + scroll_offset;
|
bounds.origin = bounds.origin + scroll_offset;
|
||||||
|
|
||||||
(cursor_bounds, scroll_offset, current_row)
|
(cursor_bounds, scroll_offset, current_row)
|
||||||
|
|
@ -455,7 +457,11 @@ impl TextElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
let total_lines = state.text_wrapper.len();
|
let total_lines = state.text_wrapper.len();
|
||||||
let scroll_top = state.scroll_handle.offset().y;
|
let scroll_top = if let Some(deferred_scroll_offset) = state.deferred_scroll_offset {
|
||||||
|
deferred_scroll_offset.y
|
||||||
|
} else {
|
||||||
|
state.scroll_handle.offset().y
|
||||||
|
};
|
||||||
|
|
||||||
let mut visible_range = 0..total_lines;
|
let mut visible_range = 0..total_lines;
|
||||||
let mut line_bottom = px(0.);
|
let mut line_bottom = px(0.);
|
||||||
|
|
@ -1084,11 +1090,7 @@ impl Element for TextElement {
|
||||||
input_bounds.size.height,
|
input_bounds.size.height,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
cx.theme()
|
cx.theme().background,
|
||||||
.highlight_theme
|
|
||||||
.style
|
|
||||||
.background
|
|
||||||
.unwrap_or(cx.theme().background),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// Each item is the normal lines.
|
// Each item is the normal lines.
|
||||||
|
|
@ -1126,6 +1128,8 @@ impl Element for TextElement {
|
||||||
state
|
state
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.set_offset(prepaint.cursor_scroll_offset);
|
||||||
|
state.deferred_scroll_offset = None;
|
||||||
|
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{App, Context, EntityInputHandler, MouseMoveEvent, Task, Window};
|
use gpui::{App, Context, MouseMoveEvent, Task, Window};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use crate::input::{popovers::ContextMenu, InputState, RopeExt};
|
use crate::input::{popovers::ContextMenu, InputState, RopeExt};
|
||||||
|
|
@ -99,7 +99,7 @@ impl InputState {
|
||||||
let end = self.text.position_to_offset(&edit.range.end);
|
let end = self.text.position_to_offset(&edit.range.end);
|
||||||
|
|
||||||
let range_utf16 = self.range_to_utf16(&(start..end));
|
let range_utf16 = self.range_to_utf16(&(start..end));
|
||||||
self.replace_text_in_range(Some(range_utf16), &edit.new_text, window, cx);
|
self.replace_text_in_range_silent(Some(range_utf16), &edit.new_text, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,6 +105,10 @@ impl InputMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) {
|
pub(super) fn update_auto_grow(&mut self, text_wrapper: &TextWrapper) {
|
||||||
|
if self.is_single_line() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let wrapped_lines = text_wrapper.len();
|
let wrapped_lines = text_wrapper.len();
|
||||||
self.set_rows(wrapped_lines);
|
self.set_rows(wrapped_lines);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
canvas, deferred, div, prelude::FluentBuilder, px, relative, Action, AnyElement, App,
|
canvas, deferred, div, prelude::FluentBuilder, px, relative, Action, AnyElement, App,
|
||||||
AppContext, Bounds, Context, DismissEvent, Empty, Entity, EntityInputHandler, EventEmitter,
|
AppContext, Bounds, Context, DismissEvent, Empty, Entity, EventEmitter, HighlightStyle,
|
||||||
HighlightStyle, InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render,
|
InteractiveElement as _, IntoElement, ParentElement, Pixels, Point, Render, RenderOnce,
|
||||||
RenderOnce, SharedString, Styled, StyledText, Subscription, Window,
|
SharedString, Styled, StyledText, Subscription, Window,
|
||||||
};
|
};
|
||||||
use lsp_types::{CompletionItem, CompletionTextEdit};
|
use lsp_types::{CompletionItem, CompletionTextEdit};
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ impl CompletionMenu {
|
||||||
range = offset..offset;
|
range = offset..offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.replace_text_in_range(
|
editor.replace_text_in_range_silent(
|
||||||
Some(editor.range_to_utf16(&range)),
|
Some(editor.range_to_utf16(&range)),
|
||||||
&new_text,
|
&new_text,
|
||||||
window,
|
window,
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,8 @@ impl RopeExt for Rope {
|
||||||
fn offset_to_position(&self, offset: usize) -> Position {
|
fn offset_to_position(&self, offset: usize) -> Position {
|
||||||
let point = self.offset_to_point(offset);
|
let point = self.offset_to_point(offset);
|
||||||
let line = self.line(point.row as usize);
|
let line = self.line(point.row as usize);
|
||||||
let character = line.slice(0..point.column as usize).chars().count();
|
let column = line.clip_offset(point.column as usize, sum_tree::Bias::Left);
|
||||||
|
let character = line.slice(0..column).chars().count();
|
||||||
Position::new(point.row, character as u32)
|
Position::new(point.row, character as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ use std::{ops::Range, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, canvas, div, prelude::FluentBuilder as _, App, AppContext as _, Context, Empty,
|
actions, canvas, div, prelude::FluentBuilder as _, App, AppContext as _, Context, Empty,
|
||||||
Entity, EntityInputHandler, FocusHandle, Focusable, Half, InteractiveElement as _, IntoElement,
|
Entity, FocusHandle, Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding,
|
||||||
KeyBinding, ParentElement as _, Pixels, Render, Styled, Subscription, Window,
|
ParentElement as _, Pixels, Render, Styled, Subscription, Window,
|
||||||
};
|
};
|
||||||
use rope::Rope;
|
use rope::Rope;
|
||||||
|
|
||||||
|
|
@ -345,7 +345,7 @@ impl SearchPanel {
|
||||||
text_state.update(cx, |state, cx| {
|
text_state.update(cx, |state, cx| {
|
||||||
let range_utf16 = state.range_to_utf16(&range);
|
let range_utf16 = state.range_to_utf16(&range);
|
||||||
state.scroll_to(next_range.end, cx);
|
state.scroll_to(next_range.end, cx);
|
||||||
state.replace_text_in_range(
|
state.replace_text_in_range_silent(
|
||||||
Some(range_utf16),
|
Some(range_utf16),
|
||||||
new_text.as_str(),
|
new_text.as_str(),
|
||||||
window,
|
window,
|
||||||
|
|
@ -375,7 +375,7 @@ impl SearchPanel {
|
||||||
for range in ranges.iter().rev() {
|
for range in ranges.iter().rev() {
|
||||||
rope.replace(range.clone(), new_text.as_str());
|
rope.replace(range.clone(), new_text.as_str());
|
||||||
}
|
}
|
||||||
state.replace_text_in_range(
|
state.replace_text_in_range_silent(
|
||||||
Some(0..state.text.len()),
|
Some(0..state.text.len()),
|
||||||
&rope.to_string(),
|
&rope.to_string(),
|
||||||
window,
|
window,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, point, prelude::FluentBuilder as _, px, Action, App, AppContext, Bounds,
|
actions, div, point, prelude::FluentBuilder as _, px, Action, App, AppContext, Bounds,
|
||||||
ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, Half,
|
ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
|
||||||
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
|
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
|
||||||
ScrollWheelEvent, SharedString, Styled as _, Subscription, Task, UTF16Selection, Window,
|
ScrollWheelEvent, SharedString, Styled as _, Subscription, Task, UTF16Selection, Window,
|
||||||
|
|
@ -284,6 +284,8 @@ pub struct InputState {
|
||||||
pub(super) pattern: Option<regex::Regex>,
|
pub(super) pattern: Option<regex::Regex>,
|
||||||
pub(super) validate: Option<Box<dyn Fn(&str, &mut Context<Self>) -> bool + 'static>>,
|
pub(super) validate: Option<Box<dyn Fn(&str, &mut Context<Self>) -> bool + 'static>>,
|
||||||
pub(crate) scroll_handle: ScrollHandle,
|
pub(crate) scroll_handle: ScrollHandle,
|
||||||
|
/// The deferred scroll offset to apply on next layout.
|
||||||
|
pub(crate) deferred_scroll_offset: Option<Point<Pixels>>,
|
||||||
pub(super) scroll_state: ScrollbarState,
|
pub(super) scroll_state: ScrollbarState,
|
||||||
/// The size of the scrollable content.
|
/// The size of the scrollable content.
|
||||||
pub(crate) scroll_size: gpui::Size<Pixels>,
|
pub(crate) scroll_size: gpui::Size<Pixels>,
|
||||||
|
|
@ -304,6 +306,9 @@ pub struct InputState {
|
||||||
|
|
||||||
pub lsp: Lsp,
|
pub lsp: Lsp,
|
||||||
|
|
||||||
|
/// A flag to indicate if we should ignore the next completion event.
|
||||||
|
pub(super) silent_replace_text: bool,
|
||||||
|
|
||||||
/// To remember the horizontal column (x-coordinate) of the cursor position for keep column for move up/down.
|
/// To remember the horizontal column (x-coordinate) of the cursor position for keep column for move up/down.
|
||||||
///
|
///
|
||||||
/// The first element is the x-coordinate (Pixels), preferred to use this.
|
/// The first element is the x-coordinate (Pixels), preferred to use this.
|
||||||
|
|
@ -378,6 +383,7 @@ impl InputState {
|
||||||
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.)),
|
||||||
|
deferred_scroll_offset: None,
|
||||||
preferred_column: None,
|
preferred_column: None,
|
||||||
placeholder: SharedString::default(),
|
placeholder: SharedString::default(),
|
||||||
mask_pattern: MaskPattern::default(),
|
mask_pattern: MaskPattern::default(),
|
||||||
|
|
@ -387,6 +393,7 @@ impl InputState {
|
||||||
completion_inserting: false,
|
completion_inserting: false,
|
||||||
hover_popover: None,
|
hover_popover: None,
|
||||||
hover_definition: None,
|
hover_definition: None,
|
||||||
|
silent_replace_text: false,
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
_context_menu_task: Task::ready(Ok(())),
|
_context_menu_task: Task::ready(Ok(())),
|
||||||
}
|
}
|
||||||
|
|
@ -448,6 +455,7 @@ impl InputState {
|
||||||
|
|
||||||
/// Set this input is searchable, default is false (Default true for Code Editor).
|
/// Set this input is searchable, default is false (Default true for Code Editor).
|
||||||
pub fn searchable(mut self, searchable: bool) -> Self {
|
pub fn searchable(mut self, searchable: bool) -> Self {
|
||||||
|
debug_assert!(self.mode.is_multi_line());
|
||||||
self.searchable = searchable;
|
self.searchable = searchable;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -460,6 +468,7 @@ impl InputState {
|
||||||
|
|
||||||
/// Set enable/disable line number, only for [`InputMode::CodeEditor`] mode.
|
/// Set enable/disable line number, only for [`InputMode::CodeEditor`] mode.
|
||||||
pub fn line_number(mut self, line_number: bool) -> Self {
|
pub fn line_number(mut self, line_number: bool) -> Self {
|
||||||
|
debug_assert!(self.mode.is_code_editor());
|
||||||
if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {
|
if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {
|
||||||
*l = line_number;
|
*l = line_number;
|
||||||
}
|
}
|
||||||
|
|
@ -468,6 +477,7 @@ impl InputState {
|
||||||
|
|
||||||
/// Set line number, only for [`InputMode::CodeEditor`] mode.
|
/// Set line number, only for [`InputMode::CodeEditor`] mode.
|
||||||
pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn set_line_number(&mut self, line_number: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
debug_assert!(self.mode.is_code_editor());
|
||||||
if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {
|
if let InputMode::CodeEditor { line_number: l, .. } = &mut self.mode {
|
||||||
*l = line_number;
|
*l = line_number;
|
||||||
}
|
}
|
||||||
|
|
@ -478,6 +488,7 @@ impl InputState {
|
||||||
///
|
///
|
||||||
/// Only for [`InputMode::MultiLine`] and [`InputMode::CodeEditor`] mode.
|
/// Only for [`InputMode::MultiLine`] and [`InputMode::CodeEditor`] mode.
|
||||||
pub fn tab_size(mut self, tab: TabSize) -> Self {
|
pub fn tab_size(mut self, tab: TabSize) -> Self {
|
||||||
|
debug_assert!(self.mode.is_multi_line() || self.mode.is_code_editor());
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
InputMode::MultiLine { tab: t, .. } => *t = tab,
|
InputMode::MultiLine { tab: t, .. } => *t = tab,
|
||||||
InputMode::CodeEditor { tab: t, .. } => *t = tab,
|
InputMode::CodeEditor { tab: t, .. } => *t = tab,
|
||||||
|
|
@ -699,7 +710,7 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let text: SharedString = text.into();
|
let text: SharedString = text.into();
|
||||||
let range_utf16 = self.range_to_utf16(&(self.cursor()..self.cursor()));
|
let range_utf16 = self.range_to_utf16(&(self.cursor()..self.cursor()));
|
||||||
self.replace_text_in_range(Some(range_utf16), &text, window, cx);
|
self.replace_text_in_range_silent(Some(range_utf16), &text, window, cx);
|
||||||
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -713,7 +724,7 @@ impl InputState {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let text: SharedString = text.into();
|
let text: SharedString = text.into();
|
||||||
self.replace_text_in_range(None, &text, window, cx);
|
self.replace_text_in_range_silent(None, &text, window, cx);
|
||||||
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
self.selected_range = (self.selected_range.end..self.selected_range.end).into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -725,7 +736,7 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let text: SharedString = text.into();
|
let text: SharedString = text.into();
|
||||||
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
|
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
|
||||||
self.replace_text_in_range(Some(range), &text, window, cx);
|
self.replace_text_in_range_silent(Some(range), &text, window, cx);
|
||||||
self.reset_highlighter(cx);
|
self.reset_highlighter(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -739,13 +750,19 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set with password masked state.
|
/// Set with password masked state.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn masked(mut self, masked: bool) -> Self {
|
pub fn masked(mut self, masked: bool) -> Self {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.masked = masked;
|
self.masked = masked;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the password masked state of the input field.
|
/// Set the password masked state of the input field.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn set_masked(&mut self, masked: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.masked = masked;
|
self.masked = masked;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
@ -758,12 +775,14 @@ impl InputState {
|
||||||
|
|
||||||
/// Set the soft wrap mode for multi-line input, default is true.
|
/// Set the soft wrap mode for multi-line input, default is true.
|
||||||
pub fn soft_wrap(mut self, wrap: bool) -> Self {
|
pub fn soft_wrap(mut self, wrap: bool) -> Self {
|
||||||
|
debug_assert!(self.mode.is_multi_line());
|
||||||
self.soft_wrap = wrap;
|
self.soft_wrap = wrap;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the soft wrap mode for multi-line input, default is true.
|
/// Update the soft wrap mode for multi-line input, default is true.
|
||||||
pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
debug_assert!(self.mode.is_multi_line());
|
||||||
self.soft_wrap = wrap;
|
self.soft_wrap = wrap;
|
||||||
if wrap {
|
if wrap {
|
||||||
let wrap_width = self
|
let wrap_width = self
|
||||||
|
|
@ -785,29 +804,41 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the regular expression pattern of the input field.
|
/// Set the regular expression pattern of the input field.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn pattern(mut self, pattern: regex::Regex) -> Self {
|
pub fn pattern(mut self, pattern: regex::Regex) -> Self {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.pattern = Some(pattern);
|
self.pattern = Some(pattern);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the regular expression pattern of the input field with reference.
|
/// Set the regular expression pattern of the input field with reference.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn set_pattern(
|
pub fn set_pattern(
|
||||||
&mut self,
|
&mut self,
|
||||||
pattern: regex::Regex,
|
pattern: regex::Regex,
|
||||||
_window: &mut Window,
|
_window: &mut Window,
|
||||||
_cx: &mut Context<Self>,
|
_cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.pattern = Some(pattern);
|
self.pattern = Some(pattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the validation function of the input field.
|
/// Set the validation function of the input field.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn validate(mut self, f: impl Fn(&str, &mut Context<Self>) -> bool + 'static) -> Self {
|
pub fn validate(mut self, f: impl Fn(&str, &mut Context<Self>) -> bool + 'static) -> Self {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.validate = Some(Box::new(f));
|
self.validate = Some(Box::new(f));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set true to show indicator at the input right.
|
/// Set true to show indicator at the input right.
|
||||||
|
///
|
||||||
|
/// Only for [`InputMode::SingleLine`] mode.
|
||||||
pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
|
pub fn set_loading(&mut self, loading: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
debug_assert!(self.mode.is_single_line());
|
||||||
self.loading = loading;
|
self.loading = loading;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
@ -862,8 +893,6 @@ impl InputState {
|
||||||
.text
|
.text
|
||||||
.point_to_offset(rope::Point::new(row as u32, col as u32));
|
.point_to_offset(rope::Point::new(row as u32, col as u32));
|
||||||
|
|
||||||
// TODO: Scroll to make the row in center of viewport.
|
|
||||||
|
|
||||||
self.move_to(offset, cx);
|
self.move_to(offset, cx);
|
||||||
self.update_preferred_column();
|
self.update_preferred_column();
|
||||||
self.focus(window, cx);
|
self.focus(window, cx);
|
||||||
|
|
@ -990,8 +1019,8 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn select_all(&mut self, _: &SelectAll, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.move_to(0, cx);
|
self.selected_range = (0..self.text.len()).into();
|
||||||
self.select_to(self.text.len(), cx);
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn home(&mut self, _: &MoveHome, _: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn home(&mut self, _: &MoveHome, _: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
|
@ -1101,6 +1130,7 @@ impl InputState {
|
||||||
/// 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;
|
||||||
|
let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
|
||||||
// FIXME: Avoid to_string
|
// FIXME: Avoid to_string
|
||||||
let left_part = self.text.slice(0..offset).to_string();
|
let left_part = self.text.slice(0..offset).to_string();
|
||||||
|
|
||||||
|
|
@ -1114,6 +1144,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();
|
let offset = self.cursor();
|
||||||
|
let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
|
||||||
let right_part = self.text.slice(offset..self.text.len()).to_string();
|
let right_part = self.text.slice(offset..self.text.len()).to_string();
|
||||||
|
|
||||||
UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
|
UnicodeSegmentation::split_word_bound_indices(right_part.as_str())
|
||||||
|
|
@ -1230,7 +1261,7 @@ impl InputState {
|
||||||
if offset == self.cursor() {
|
if offset == self.cursor() {
|
||||||
offset = offset.saturating_sub(1);
|
offset = offset.saturating_sub(1);
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..self.cursor()))),
|
Some(self.range_to_utf16(&(offset..self.cursor()))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1250,7 +1281,7 @@ impl InputState {
|
||||||
if offset == self.cursor() {
|
if offset == self.cursor() {
|
||||||
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_silent(
|
||||||
Some(self.range_to_utf16(&(self.cursor()..offset))),
|
Some(self.range_to_utf16(&(self.cursor()..offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1266,7 +1297,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.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..self.cursor()))),
|
Some(self.range_to_utf16(&(offset..self.cursor()))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1282,7 +1313,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.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(self.cursor()..offset))),
|
Some(self.range_to_utf16(&(self.cursor()..offset))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1306,7 +1337,8 @@ impl InputState {
|
||||||
|
|
||||||
// Add newline and indent
|
// Add newline and indent
|
||||||
let new_line_text = format!("\n{}", indent);
|
let new_line_text = format!("\n{}", indent);
|
||||||
self.replace_text_in_range(None, &new_line_text, window, cx);
|
self.replace_text_in_range_silent(None, &new_line_text, window, cx);
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
} else {
|
} else {
|
||||||
// Single line input, just emit the event (e.g.: In a modal dialog to confirm).
|
// Single line input, just emit the event (e.g.: In a modal dialog to confirm).
|
||||||
cx.propagate();
|
cx.propagate();
|
||||||
|
|
@ -1372,7 +1404,7 @@ impl InputState {
|
||||||
.unwrap_or("".into());
|
.unwrap_or("".into());
|
||||||
|
|
||||||
for line in selected_text.split('\n') {
|
for line in selected_text.split('\n') {
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..offset))),
|
Some(self.range_to_utf16(&(offset..offset))),
|
||||||
&tab_indent,
|
&tab_indent,
|
||||||
window,
|
window,
|
||||||
|
|
@ -1392,7 +1424,7 @@ impl InputState {
|
||||||
} else {
|
} else {
|
||||||
// Selected none
|
// Selected none
|
||||||
let offset = self.selected_range.start;
|
let offset = self.selected_range.start;
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..offset))),
|
Some(self.range_to_utf16(&(offset..offset))),
|
||||||
&tab_indent,
|
&tab_indent,
|
||||||
window,
|
window,
|
||||||
|
|
@ -1430,7 +1462,7 @@ impl InputState {
|
||||||
|
|
||||||
for line in selected_text.split('\n') {
|
for line in selected_text.split('\n') {
|
||||||
if line.starts_with(tab_indent.as_ref()) {
|
if line.starts_with(tab_indent.as_ref()) {
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),
|
Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1457,6 +1489,7 @@ impl InputState {
|
||||||
// Selected none
|
// Selected none
|
||||||
let start_offset = self.selected_range.start;
|
let start_offset = self.selected_range.start;
|
||||||
let offset = self.start_of_line_of_selection(window, cx);
|
let offset = self.start_of_line_of_selection(window, cx);
|
||||||
|
let offset = self.offset_from_utf16(self.offset_to_utf16(offset));
|
||||||
// FIXME: To improve performance
|
// FIXME: To improve performance
|
||||||
if self
|
if self
|
||||||
.text
|
.text
|
||||||
|
|
@ -1464,7 +1497,7 @@ impl InputState {
|
||||||
.to_string()
|
.to_string()
|
||||||
.starts_with(tab_indent.as_ref())
|
.starts_with(tab_indent.as_ref())
|
||||||
{
|
{
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),
|
Some(self.range_to_utf16(&(offset..offset + tab_indent.len()))),
|
||||||
"",
|
"",
|
||||||
window,
|
window,
|
||||||
|
|
@ -1647,15 +1680,19 @@ impl InputState {
|
||||||
|
|
||||||
// Check if row_offset_y is out of the viewport
|
// Check if row_offset_y is out of the viewport
|
||||||
// If row offset is not in the viewport, scroll to make it visible
|
// If row offset is not in the viewport, scroll to make it visible
|
||||||
if row_offset_y < -scroll_offset.y {
|
let edge_height = 3 * line_height;
|
||||||
|
if row_offset_y - edge_height < -scroll_offset.y {
|
||||||
// Scroll up
|
// Scroll up
|
||||||
scroll_offset.y = -row_offset_y - line_height + bounds.size.height.half();
|
scroll_offset.y = -row_offset_y + edge_height;
|
||||||
} else if row_offset_y + line_height > -scroll_offset.y + bounds.size.height {
|
} else if row_offset_y + edge_height > -scroll_offset.y + bounds.size.height {
|
||||||
// Scroll down
|
// Scroll down
|
||||||
scroll_offset.y = -(row_offset_y - bounds.size.height.half());
|
scroll_offset.y = -(row_offset_y - bounds.size.height + edge_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_scroll_offset(Some(scroll_offset), cx);
|
scroll_offset.x = scroll_offset.x.min(px(0.));
|
||||||
|
scroll_offset.y = scroll_offset.y.min(px(0.));
|
||||||
|
self.deferred_scroll_offset = Some(scroll_offset);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn show_character_palette(
|
pub(super) fn show_character_palette(
|
||||||
|
|
@ -1683,7 +1720,8 @@ impl InputState {
|
||||||
|
|
||||||
let selected_text = self.text.slice(self.selected_range.into()).to_string();
|
let selected_text = self.text.slice(self.selected_range.into()).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_silent(None, "", window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
|
@ -1693,7 +1731,8 @@ impl InputState {
|
||||||
new_text = new_text.replace('\n', "");
|
new_text = new_text.replace('\n', "");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.replace_text_in_range(None, &new_text, window, cx);
|
self.replace_text_in_range_silent(None, &new_text, window, cx);
|
||||||
|
self.scroll_to(self.cursor(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1715,7 +1754,7 @@ impl InputState {
|
||||||
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.into());
|
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_silent(Some(range_utf16), &change.old_text, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.history.ignore = false;
|
self.history.ignore = false;
|
||||||
|
|
@ -1726,7 +1765,7 @@ impl InputState {
|
||||||
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.into());
|
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_silent(Some(range_utf16), &change.new_text, window, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.history.ignore = false;
|
self.history.ignore = false;
|
||||||
|
|
@ -2152,7 +2191,9 @@ impl InputState {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn selected_text(&self) -> Rope {
|
pub(super) fn selected_text(&self) -> Rope {
|
||||||
self.text.slice(self.selected_range.into())
|
let range_utf16 = self.range_to_utf16(&self.selected_range.into());
|
||||||
|
let range = self.range_from_utf16(&range_utf16);
|
||||||
|
self.text.slice(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
|
pub(crate) fn range_to_bounds(&self, range: &Range<usize>) -> Option<Bounds<Pixels>> {
|
||||||
|
|
@ -2193,13 +2234,28 @@ impl InputState {
|
||||||
) {
|
) {
|
||||||
let start = self.text.position_to_offset(&lsp_range.start);
|
let start = self.text.position_to_offset(&lsp_range.start);
|
||||||
let end = self.text.position_to_offset(&lsp_range.end);
|
let end = self.text.position_to_offset(&lsp_range.end);
|
||||||
self.replace_text_in_range(
|
self.replace_text_in_range_silent(
|
||||||
Some(self.range_to_utf16(&(start..end))),
|
Some(self.range_to_utf16(&(start..end))),
|
||||||
new_text,
|
new_text,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace text in range in silent.
|
||||||
|
///
|
||||||
|
/// This will not trigger any UI interaction, such as auto-completion.
|
||||||
|
pub(crate) fn replace_text_in_range_silent(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
self.silent_replace_text = true;
|
||||||
|
self.replace_text_in_range(range_utf16, new_text, window, cx);
|
||||||
|
self.silent_replace_text = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityInputHandler for InputState {
|
impl EntityInputHandler for InputState {
|
||||||
|
|
@ -2269,20 +2325,23 @@ impl EntityInputHandler for InputState {
|
||||||
let old_text = self.text.clone();
|
let old_text = self.text.clone();
|
||||||
self.text.replace(range.clone(), new_text);
|
self.text.replace(range.clone(), new_text);
|
||||||
|
|
||||||
let pending_text = self.text.to_string();
|
|
||||||
// Check if the new text is valid
|
|
||||||
if !self.is_valid_input(&pending_text, cx) {
|
|
||||||
self.text = old_text;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_offset = (range.start + new_text.len()).min(self.text.len());
|
let mut new_offset = (range.start + new_text.len()).min(self.text.len());
|
||||||
if !self.mask_pattern.is_none() {
|
|
||||||
let mask_text = self.mask_pattern.mask(&pending_text);
|
if self.mode.is_single_line() {
|
||||||
self.text = Rope::from(mask_text.as_str());
|
let pending_text = self.text.to_string();
|
||||||
let new_text_len =
|
// Check if the new text is valid
|
||||||
(new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
|
if !self.is_valid_input(&pending_text, cx) {
|
||||||
new_offset = (range.start + new_text_len).min(mask_text.len());
|
self.text = old_text;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.mask_pattern.is_none() {
|
||||||
|
let mask_text = self.mask_pattern.mask(&pending_text);
|
||||||
|
self.text = Rope::from(mask_text.as_str());
|
||||||
|
let new_text_len =
|
||||||
|
(new_text.len() + mask_text.len()).saturating_sub(pending_text.len());
|
||||||
|
new_offset = (range.start + new_text_len).min(mask_text.len());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_history(&old_text, &range, &new_text);
|
self.push_history(&old_text, &range, &new_text);
|
||||||
|
|
@ -2296,10 +2355,11 @@ impl EntityInputHandler for InputState {
|
||||||
self.selected_range = (new_offset..new_offset).into();
|
self.selected_range = (new_offset..new_offset).into();
|
||||||
self.ime_marked_range.take();
|
self.ime_marked_range.take();
|
||||||
self.update_preferred_column();
|
self.update_preferred_column();
|
||||||
self.update_scroll_offset(None, cx);
|
|
||||||
self.update_search(cx);
|
self.update_search(cx);
|
||||||
self.mode.update_auto_grow(&self.text_wrapper);
|
self.mode.update_auto_grow(&self.text_wrapper);
|
||||||
self.handle_completion_trigger(&range, &new_text, window, cx);
|
if !self.silent_replace_text {
|
||||||
|
self.handle_completion_trigger(&range, &new_text, window, cx);
|
||||||
|
}
|
||||||
cx.emit(InputEvent::Change);
|
cx.emit(InputEvent::Change);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
@ -2328,11 +2388,13 @@ impl EntityInputHandler for InputState {
|
||||||
|
|
||||||
let old_text = self.text.clone();
|
let old_text = self.text.clone();
|
||||||
self.text.replace(range.clone(), new_text);
|
self.text.replace(range.clone(), new_text);
|
||||||
let pending_text = self.text.to_string();
|
|
||||||
|
|
||||||
if !self.is_valid_input(&pending_text, cx) {
|
if self.mode.is_single_line() {
|
||||||
self.text = old_text;
|
let pending_text = self.text.to_string();
|
||||||
return;
|
if !self.is_valid_input(&pending_text, cx) {
|
||||||
|
self.text = old_text;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push_history(&old_text, &range, new_text);
|
self.push_history(&old_text, &range, new_text);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue