Merge pull request #1405 from zed-industries/ime-support-2
Improve support for non-US keyboards and input
This commit is contained in:
commit
d9de0261ba
61 changed files with 2389 additions and 675 deletions
|
@ -11,22 +11,22 @@
|
||||||
"enter": "menu::Confirm",
|
"enter": "menu::Confirm",
|
||||||
"escape": "menu::Cancel",
|
"escape": "menu::Cancel",
|
||||||
"ctrl-c": "menu::Cancel",
|
"ctrl-c": "menu::Cancel",
|
||||||
"shift-cmd-{": "pane::ActivatePrevItem",
|
"cmd-{": "pane::ActivatePrevItem",
|
||||||
"shift-cmd-}": "pane::ActivateNextItem",
|
"cmd-}": "pane::ActivateNextItem",
|
||||||
"alt-cmd-left": "pane::ActivatePrevItem",
|
"alt-cmd-left": "pane::ActivatePrevItem",
|
||||||
"alt-cmd-right": "pane::ActivateNextItem",
|
"alt-cmd-right": "pane::ActivateNextItem",
|
||||||
"cmd-w": "pane::CloseActiveItem",
|
"cmd-w": "pane::CloseActiveItem",
|
||||||
"cmd-shift-W": "workspace::CloseWindow",
|
"cmd-shift-w": "workspace::CloseWindow",
|
||||||
"alt-cmd-t": "pane::CloseInactiveItems",
|
"alt-cmd-t": "pane::CloseInactiveItems",
|
||||||
"cmd-s": "workspace::Save",
|
"cmd-s": "workspace::Save",
|
||||||
"cmd-shift-S": "workspace::SaveAs",
|
"cmd-shift-s": "workspace::SaveAs",
|
||||||
"cmd-=": "zed::IncreaseBufferFontSize",
|
"cmd-=": "zed::IncreaseBufferFontSize",
|
||||||
"cmd--": "zed::DecreaseBufferFontSize",
|
"cmd--": "zed::DecreaseBufferFontSize",
|
||||||
"cmd-0": "zed::ResetBufferFontSize",
|
"cmd-0": "zed::ResetBufferFontSize",
|
||||||
"cmd-,": "zed::OpenSettings",
|
"cmd-,": "zed::OpenSettings",
|
||||||
"cmd-q": "zed::Quit",
|
"cmd-q": "zed::Quit",
|
||||||
"cmd-n": "workspace::NewFile",
|
"cmd-n": "workspace::NewFile",
|
||||||
"cmd-shift-N": "workspace::NewWindow",
|
"cmd-shift-n": "workspace::NewWindow",
|
||||||
"cmd-o": "workspace::Open"
|
"cmd-o": "workspace::Open"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -53,7 +53,7 @@
|
||||||
"cmd-c": "editor::Copy",
|
"cmd-c": "editor::Copy",
|
||||||
"cmd-v": "editor::Paste",
|
"cmd-v": "editor::Paste",
|
||||||
"cmd-z": "editor::Undo",
|
"cmd-z": "editor::Undo",
|
||||||
"cmd-shift-Z": "editor::Redo",
|
"cmd-shift-z": "editor::Redo",
|
||||||
"up": "editor::MoveUp",
|
"up": "editor::MoveUp",
|
||||||
"down": "editor::MoveDown",
|
"down": "editor::MoveDown",
|
||||||
"left": "editor::MoveLeft",
|
"left": "editor::MoveLeft",
|
||||||
|
@ -73,17 +73,17 @@
|
||||||
"cmd-up": "editor::MoveToBeginning",
|
"cmd-up": "editor::MoveToBeginning",
|
||||||
"cmd-down": "editor::MoveToEnd",
|
"cmd-down": "editor::MoveToEnd",
|
||||||
"shift-up": "editor::SelectUp",
|
"shift-up": "editor::SelectUp",
|
||||||
"ctrl-shift-P": "editor::SelectUp",
|
"ctrl-shift-p": "editor::SelectUp",
|
||||||
"shift-down": "editor::SelectDown",
|
"shift-down": "editor::SelectDown",
|
||||||
"ctrl-shift-N": "editor::SelectDown",
|
"ctrl-shift-n": "editor::SelectDown",
|
||||||
"shift-left": "editor::SelectLeft",
|
"shift-left": "editor::SelectLeft",
|
||||||
"ctrl-shift-B": "editor::SelectLeft",
|
"ctrl-shift-b": "editor::SelectLeft",
|
||||||
"shift-right": "editor::SelectRight",
|
"shift-right": "editor::SelectRight",
|
||||||
"ctrl-shift-F": "editor::SelectRight",
|
"ctrl-shift-f": "editor::SelectRight",
|
||||||
"alt-shift-left": "editor::SelectToPreviousWordStart",
|
"alt-shift-left": "editor::SelectToPreviousWordStart",
|
||||||
"alt-shift-B": "editor::SelectToPreviousWordStart",
|
"alt-shift-b": "editor::SelectToPreviousWordStart",
|
||||||
"alt-shift-right": "editor::SelectToNextWordEnd",
|
"alt-shift-right": "editor::SelectToNextWordEnd",
|
||||||
"alt-shift-F": "editor::SelectToNextWordEnd",
|
"alt-shift-f": "editor::SelectToNextWordEnd",
|
||||||
"cmd-shift-up": "editor::SelectToBeginning",
|
"cmd-shift-up": "editor::SelectToBeginning",
|
||||||
"cmd-shift-down": "editor::SelectToEnd",
|
"cmd-shift-down": "editor::SelectToEnd",
|
||||||
"cmd-a": "editor::SelectAll",
|
"cmd-a": "editor::SelectAll",
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
"stop_at_soft_wraps": true
|
"stop_at_soft_wraps": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ctrl-shift-A": [
|
"ctrl-shift-a": [
|
||||||
"editor::SelectToBeginningOfLine",
|
"editor::SelectToBeginningOfLine",
|
||||||
{
|
{
|
||||||
"stop_at_soft_wraps": true
|
"stop_at_soft_wraps": true
|
||||||
|
@ -106,14 +106,15 @@
|
||||||
"stop_at_soft_wraps": true
|
"stop_at_soft_wraps": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ctrl-shift-E": [
|
"ctrl-shift-e": [
|
||||||
"editor::SelectToEndOfLine",
|
"editor::SelectToEndOfLine",
|
||||||
{
|
{
|
||||||
"stop_at_soft_wraps": true
|
"stop_at_soft_wraps": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"pageup": "editor::PageUp",
|
"pageup": "editor::PageUp",
|
||||||
"pagedown": "editor::PageDown"
|
"pagedown": "editor::PageDown",
|
||||||
|
"ctrl-cmd-space": "editor::ShowCharacterPalette"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -137,10 +138,7 @@
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == auto_height",
|
"context": "Editor && mode == auto_height",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"alt-enter": [
|
"alt-enter": "editor::Newline"
|
||||||
"editor::Input",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -157,7 +155,7 @@
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-f": "project_search::ToggleFocus",
|
"cmd-f": "project_search::ToggleFocus",
|
||||||
"cmd-g": "search::SelectNextMatch",
|
"cmd-g": "search::SelectNextMatch",
|
||||||
"cmd-shift-G": "search::SelectPrevMatch",
|
"cmd-shift-g": "search::SelectPrevMatch",
|
||||||
"alt-cmd-c": "search::ToggleCaseSensitive",
|
"alt-cmd-c": "search::ToggleCaseSensitive",
|
||||||
"alt-cmd-w": "search::ToggleWholeWord",
|
"alt-cmd-w": "search::ToggleWholeWord",
|
||||||
"alt-cmd-r": "search::ToggleRegex"
|
"alt-cmd-r": "search::ToggleRegex"
|
||||||
|
@ -189,7 +187,7 @@
|
||||||
"alt-up": "editor::SelectLargerSyntaxNode",
|
"alt-up": "editor::SelectLargerSyntaxNode",
|
||||||
"alt-down": "editor::SelectSmallerSyntaxNode",
|
"alt-down": "editor::SelectSmallerSyntaxNode",
|
||||||
"cmd-u": "editor::UndoSelection",
|
"cmd-u": "editor::UndoSelection",
|
||||||
"cmd-shift-U": "editor::RedoSelection",
|
"cmd-shift-u": "editor::RedoSelection",
|
||||||
"f8": "editor::GoToDiagnostic",
|
"f8": "editor::GoToDiagnostic",
|
||||||
"shift-f8": "editor::GoToPrevDiagnostic",
|
"shift-f8": "editor::GoToPrevDiagnostic",
|
||||||
"f2": "editor::Rename",
|
"f2": "editor::Rename",
|
||||||
|
@ -205,7 +203,7 @@
|
||||||
{
|
{
|
||||||
"context": "Editor && mode == full",
|
"context": "Editor && mode == full",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-O": "outline::Toggle",
|
"cmd-shift-o": "outline::Toggle",
|
||||||
"ctrl-g": "go_to_line::Toggle"
|
"ctrl-g": "go_to_line::Toggle"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -250,9 +248,9 @@
|
||||||
],
|
],
|
||||||
"ctrl-0": "pane::ActivateLastItem",
|
"ctrl-0": "pane::ActivateLastItem",
|
||||||
"ctrl--": "pane::GoBack",
|
"ctrl--": "pane::GoBack",
|
||||||
"shift-ctrl-_": "pane::GoForward",
|
"ctrl-_": "pane::GoForward",
|
||||||
"cmd-shift-T": "pane::ReopenClosedItem",
|
"cmd-shift-t": "pane::ReopenClosedItem",
|
||||||
"cmd-shift-F": "project_search::ToggleFocus"
|
"cmd-shift-f": "project_search::ToggleFocus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -295,14 +293,14 @@
|
||||||
8
|
8
|
||||||
],
|
],
|
||||||
"cmd-b": "workspace::ToggleLeftSidebar",
|
"cmd-b": "workspace::ToggleLeftSidebar",
|
||||||
"cmd-shift-F": "project_search::Deploy",
|
"cmd-shift-f": "project_search::Deploy",
|
||||||
"cmd-k cmd-t": "theme_selector::Toggle",
|
"cmd-k cmd-t": "theme_selector::Toggle",
|
||||||
"cmd-k cmd-s": "zed::OpenKeymap",
|
"cmd-k cmd-s": "zed::OpenKeymap",
|
||||||
"cmd-t": "project_symbols::Toggle",
|
"cmd-t": "project_symbols::Toggle",
|
||||||
"cmd-p": "file_finder::Toggle",
|
"cmd-p": "file_finder::Toggle",
|
||||||
"cmd-shift-P": "command_palette::Toggle",
|
"cmd-shift-p": "command_palette::Toggle",
|
||||||
"cmd-shift-M": "diagnostics::Deploy",
|
"cmd-shift-m": "diagnostics::Deploy",
|
||||||
"cmd-shift-E": "project_panel::Toggle",
|
"cmd-shift-e": "project_panel::Toggle",
|
||||||
"cmd-alt-s": "workspace::SaveAll"
|
"cmd-alt-s": "workspace::SaveAll"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -310,9 +308,9 @@
|
||||||
{
|
{
|
||||||
"context": "Editor",
|
"context": "Editor",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"ctrl-shift-K": "editor::DeleteLine",
|
"ctrl-shift-k": "editor::DeleteLine",
|
||||||
"cmd-shift-D": "editor::DuplicateLine",
|
"cmd-shift-d": "editor::DuplicateLine",
|
||||||
"cmd-shift-L": "editor::SplitSelectionIntoLines",
|
"cmd-shift-l": "editor::SplitSelectionIntoLines",
|
||||||
"ctrl-cmd-up": "editor::MoveLineUp",
|
"ctrl-cmd-up": "editor::MoveLineUp",
|
||||||
"ctrl-cmd-down": "editor::MoveLineDown",
|
"ctrl-cmd-down": "editor::MoveLineDown",
|
||||||
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
"ctrl-alt-backspace": "editor::DeleteToPreviousSubwordStart",
|
||||||
|
@ -324,9 +322,9 @@
|
||||||
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
"ctrl-alt-right": "editor::MoveToNextSubwordEnd",
|
||||||
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
|
"ctrl-alt-f": "editor::MoveToNextSubwordEnd",
|
||||||
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
"ctrl-alt-shift-left": "editor::SelectToPreviousSubwordStart",
|
||||||
"ctrl-alt-shift-B": "editor::SelectToPreviousSubwordStart",
|
"ctrl-alt-shift-b": "editor::SelectToPreviousSubwordStart",
|
||||||
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
"ctrl-alt-shift-right": "editor::SelectToNextSubwordEnd",
|
||||||
"ctrl-alt-shift-F": "editor::SelectToNextSubwordEnd"
|
"ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -387,8 +385,8 @@
|
||||||
{
|
{
|
||||||
"context": "Workspace",
|
"context": "Workspace",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"cmd-shift-C": "contacts_panel::Toggle",
|
"cmd-shift-c": "contacts_panel::Toggle",
|
||||||
"cmd-shift-B": "workspace::ToggleRightSidebar"
|
"cmd-shift-b": "workspace::ToggleRightSidebar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,8 +11,8 @@ use client::{
|
||||||
};
|
};
|
||||||
use collections::{BTreeMap, HashMap, HashSet};
|
use collections::{BTreeMap, HashMap, HashSet};
|
||||||
use editor::{
|
use editor::{
|
||||||
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Input, Redo, Rename,
|
self, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename, ToOffset,
|
||||||
ToOffset, ToggleCodeActions, Undo,
|
ToggleCodeActions, Undo,
|
||||||
};
|
};
|
||||||
use futures::{channel::mpsc, Future, StreamExt as _};
|
use futures::{channel::mpsc, Future, StreamExt as _};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -154,9 +154,7 @@ async fn test_share_project(
|
||||||
// .await;
|
// .await;
|
||||||
|
|
||||||
// Edit the buffer as client B and see that edit as client A.
|
// Edit the buffer as client B and see that edit as client A.
|
||||||
editor_b.update(cx_b, |editor, cx| {
|
editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
|
||||||
editor.handle_input(&Input("ok, ".into()), cx)
|
|
||||||
});
|
|
||||||
buffer_a
|
buffer_a
|
||||||
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
.condition(&cx_a, |buffer, _| buffer.text() == "ok, b-contents")
|
||||||
.await;
|
.await;
|
||||||
|
@ -1751,7 +1749,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu
|
||||||
// Type a completion trigger character as the guest.
|
// Type a completion trigger character as the guest.
|
||||||
editor_b.update(cx_b, |editor, cx| {
|
editor_b.update(cx_b, |editor, cx| {
|
||||||
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
|
||||||
editor.handle_input(&Input(".".into()), cx);
|
editor.handle_input(".", cx);
|
||||||
cx.focus(&editor_b);
|
cx.focus(&editor_b);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use gpui::{
|
||||||
fonts::{FontId, HighlightStyle},
|
fonts::{FontId, HighlightStyle},
|
||||||
Entity, ModelContext, ModelHandle,
|
Entity, ModelContext, ModelHandle,
|
||||||
};
|
};
|
||||||
use language::{Point, Subscription as BufferSubscription};
|
use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
|
||||||
use sum_tree::{Bias, TreeMap};
|
use sum_tree::{Bias, TreeMap};
|
||||||
|
@ -195,6 +195,11 @@ impl DisplayMap {
|
||||||
.insert(Some(type_id), Arc::new((style, ranges)));
|
.insert(Some(type_id), Arc::new((style, ranges)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_highlights(&self, type_id: TypeId) -> Option<(HighlightStyle, &[Range<Anchor>])> {
|
||||||
|
let highlights = self.text_highlights.get(&Some(type_id))?;
|
||||||
|
Some((highlights.0, &highlights.1))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_text_highlights(
|
pub fn clear_text_highlights(
|
||||||
&mut self,
|
&mut self,
|
||||||
type_id: TypeId,
|
type_id: TypeId,
|
||||||
|
@ -544,6 +549,12 @@ impl ToDisplayPoint for usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToDisplayPoint for OffsetUtf16 {
|
||||||
|
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
|
||||||
|
self.to_offset(&map.buffer_snapshot).to_display_point(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToDisplayPoint for Point {
|
impl ToDisplayPoint for Point {
|
||||||
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
|
fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
|
||||||
map.point_to_display_point(*self, Bias::Left)
|
map.point_to_display_point(*self, Bias::Left)
|
||||||
|
|
|
@ -63,14 +63,14 @@ impl FoldPoint {
|
||||||
.cursor::<(FoldPoint, TransformSummary)>();
|
.cursor::<(FoldPoint, TransformSummary)>();
|
||||||
cursor.seek(self, Bias::Right, &());
|
cursor.seek(self, Bias::Right, &());
|
||||||
let overshoot = self.0 - cursor.start().1.output.lines;
|
let overshoot = self.0 - cursor.start().1.output.lines;
|
||||||
let mut offset = cursor.start().1.output.bytes;
|
let mut offset = cursor.start().1.output.len;
|
||||||
if !overshoot.is_zero() {
|
if !overshoot.is_zero() {
|
||||||
let transform = cursor.item().expect("display point out of range");
|
let transform = cursor.item().expect("display point out of range");
|
||||||
assert!(transform.output_text.is_none());
|
assert!(transform.output_text.is_none());
|
||||||
let end_buffer_offset = snapshot
|
let end_buffer_offset = snapshot
|
||||||
.buffer_snapshot
|
.buffer_snapshot
|
||||||
.point_to_offset(cursor.start().1.input.lines + overshoot);
|
.point_to_offset(cursor.start().1.input.lines + overshoot);
|
||||||
offset += end_buffer_offset - cursor.start().1.input.bytes;
|
offset += end_buffer_offset - cursor.start().1.input.len;
|
||||||
}
|
}
|
||||||
FoldOffset(offset)
|
FoldOffset(offset)
|
||||||
}
|
}
|
||||||
|
@ -249,7 +249,7 @@ impl FoldMap {
|
||||||
fn check_invariants(&self) {
|
fn check_invariants(&self) {
|
||||||
if cfg!(test) {
|
if cfg!(test) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
self.transforms.lock().summary().input.bytes,
|
self.transforms.lock().summary().input.len,
|
||||||
self.buffer.lock().len(),
|
self.buffer.lock().len(),
|
||||||
"transform tree does not match buffer's length"
|
"transform tree does not match buffer's length"
|
||||||
);
|
);
|
||||||
|
@ -341,7 +341,7 @@ impl FoldMap {
|
||||||
let mut fold = folds.next().unwrap();
|
let mut fold = folds.next().unwrap();
|
||||||
let sum = new_transforms.summary();
|
let sum = new_transforms.summary();
|
||||||
|
|
||||||
assert!(fold.start >= sum.input.bytes);
|
assert!(fold.start >= sum.input.len);
|
||||||
|
|
||||||
while folds
|
while folds
|
||||||
.peek()
|
.peek()
|
||||||
|
@ -353,9 +353,9 @@ impl FoldMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fold.start > sum.input.bytes {
|
if fold.start > sum.input.len {
|
||||||
let text_summary = new_buffer
|
let text_summary = new_buffer
|
||||||
.text_summary_for_range::<TextSummary, _>(sum.input.bytes..fold.start);
|
.text_summary_for_range::<TextSummary, _>(sum.input.len..fold.start);
|
||||||
new_transforms.push(
|
new_transforms.push(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
|
@ -384,9 +384,9 @@ impl FoldMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sum = new_transforms.summary();
|
let sum = new_transforms.summary();
|
||||||
if sum.input.bytes < edit.new.end {
|
if sum.input.len < edit.new.end {
|
||||||
let text_summary = new_buffer
|
let text_summary = new_buffer
|
||||||
.text_summary_for_range::<TextSummary, _>(sum.input.bytes..edit.new.end);
|
.text_summary_for_range::<TextSummary, _>(sum.input.len..edit.new.end);
|
||||||
new_transforms.push(
|
new_transforms.push(
|
||||||
Transform {
|
Transform {
|
||||||
summary: TransformSummary {
|
summary: TransformSummary {
|
||||||
|
@ -558,7 +558,7 @@ impl FoldSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> FoldOffset {
|
pub fn len(&self) -> FoldOffset {
|
||||||
FoldOffset(self.transforms.summary().output.bytes)
|
FoldOffset(self.transforms.summary().output.len)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn line_len(&self, row: u32) -> u32 {
|
pub fn line_len(&self, row: u32) -> u32 {
|
||||||
|
@ -766,7 +766,7 @@ impl FoldSnapshot {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
FoldOffset(self.transforms.summary().output.bytes)
|
FoldOffset(self.transforms.summary().output.len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1050,7 +1050,7 @@ impl<'a> Iterator for FoldChunks<'a> {
|
||||||
// advance the transform and buffer cursors to the end of the fold.
|
// advance the transform and buffer cursors to the end of the fold.
|
||||||
if let Some(output_text) = transform.output_text {
|
if let Some(output_text) = transform.output_text {
|
||||||
self.buffer_chunk.take();
|
self.buffer_chunk.take();
|
||||||
self.buffer_offset += transform.summary.input.bytes;
|
self.buffer_offset += transform.summary.input.len;
|
||||||
self.buffer_chunks.seek(self.buffer_offset);
|
self.buffer_chunks.seek(self.buffer_offset);
|
||||||
|
|
||||||
while self.buffer_offset >= self.transform_cursor.end(&()).1
|
while self.buffer_offset >= self.transform_cursor.end(&()).1
|
||||||
|
@ -1158,7 +1158,7 @@ impl FoldOffset {
|
||||||
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
|
let overshoot = if cursor.item().map_or(true, |t| t.is_fold()) {
|
||||||
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
|
Point::new(0, (self.0 - cursor.start().0 .0) as u32)
|
||||||
} else {
|
} else {
|
||||||
let buffer_offset = cursor.start().1.input.bytes + self.0 - cursor.start().0 .0;
|
let buffer_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0;
|
||||||
let buffer_point = snapshot.buffer_snapshot.offset_to_point(buffer_offset);
|
let buffer_point = snapshot.buffer_snapshot.offset_to_point(buffer_offset);
|
||||||
buffer_point - cursor.start().1.input.lines
|
buffer_point - cursor.start().1.input.lines
|
||||||
};
|
};
|
||||||
|
@ -1176,7 +1176,7 @@ impl Sub for FoldOffset {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset {
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
self.0 += &summary.output.bytes;
|
self.0 += &summary.output.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1188,7 +1188,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize {
|
||||||
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
|
||||||
*self += &summary.input.bytes;
|
*self += &summary.input.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,15 +39,15 @@ pub use items::MAX_TAB_TITLE_LEN;
|
||||||
pub use language::{char_kind, CharKind};
|
pub use language::{char_kind, CharKind};
|
||||||
use language::{
|
use language::{
|
||||||
BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity,
|
BracketPair, Buffer, CodeAction, CodeLabel, Completion, Diagnostic, DiagnosticSeverity,
|
||||||
IndentKind, IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal,
|
IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Point, Selection, SelectionGoal,
|
||||||
TransactionId,
|
TransactionId,
|
||||||
};
|
};
|
||||||
use link_go_to_definition::LinkGoToDefinitionState;
|
use link_go_to_definition::LinkGoToDefinitionState;
|
||||||
use multi_buffer::MultiBufferChunks;
|
|
||||||
pub use multi_buffer::{
|
pub use multi_buffer::{
|
||||||
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, ToOffset,
|
||||||
ToPoint,
|
ToPoint,
|
||||||
};
|
};
|
||||||
|
use multi_buffer::{MultiBufferChunks, ToOffsetUtf16};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
use project::{LocationLink, Project, ProjectPath, ProjectTransaction};
|
use project::{LocationLink, Project, ProjectPath, ProjectTransaction};
|
||||||
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
|
||||||
|
@ -95,9 +95,6 @@ pub struct Jump {
|
||||||
anchor: language::Anchor,
|
anchor: language::Anchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
|
||||||
pub struct Input(pub String);
|
|
||||||
|
|
||||||
#[derive(Clone, Deserialize, PartialEq)]
|
#[derive(Clone, Deserialize, PartialEq)]
|
||||||
pub struct SelectToBeginningOfLine {
|
pub struct SelectToBeginningOfLine {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -186,6 +183,7 @@ actions!(
|
||||||
Tab,
|
Tab,
|
||||||
TabPrev,
|
TabPrev,
|
||||||
ToggleComments,
|
ToggleComments,
|
||||||
|
ShowCharacterPalette,
|
||||||
SelectLargerSyntaxNode,
|
SelectLargerSyntaxNode,
|
||||||
SelectSmallerSyntaxNode,
|
SelectSmallerSyntaxNode,
|
||||||
GoToDefinition,
|
GoToDefinition,
|
||||||
|
@ -210,7 +208,6 @@ actions!(
|
||||||
impl_actions!(
|
impl_actions!(
|
||||||
editor,
|
editor,
|
||||||
[
|
[
|
||||||
Input,
|
|
||||||
SelectNext,
|
SelectNext,
|
||||||
SelectToBeginningOfLine,
|
SelectToBeginningOfLine,
|
||||||
SelectToEndOfLine,
|
SelectToEndOfLine,
|
||||||
|
@ -224,6 +221,7 @@ impl_internal_actions!(editor, [Scroll, Select, Jump]);
|
||||||
|
|
||||||
enum DocumentHighlightRead {}
|
enum DocumentHighlightRead {}
|
||||||
enum DocumentHighlightWrite {}
|
enum DocumentHighlightWrite {}
|
||||||
|
enum InputComposition {}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
@ -236,7 +234,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
|
cx.add_action(|this: &mut Editor, action: &Scroll, cx| this.set_scroll_position(action.0, cx));
|
||||||
cx.add_action(Editor::select);
|
cx.add_action(Editor::select);
|
||||||
cx.add_action(Editor::cancel);
|
cx.add_action(Editor::cancel);
|
||||||
cx.add_action(Editor::handle_input);
|
|
||||||
cx.add_action(Editor::newline);
|
cx.add_action(Editor::newline);
|
||||||
cx.add_action(Editor::backspace);
|
cx.add_action(Editor::backspace);
|
||||||
cx.add_action(Editor::delete);
|
cx.add_action(Editor::delete);
|
||||||
|
@ -310,6 +307,7 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(Editor::open_excerpts);
|
cx.add_action(Editor::open_excerpts);
|
||||||
cx.add_action(Editor::jump);
|
cx.add_action(Editor::jump);
|
||||||
cx.add_action(Editor::restart_language_server);
|
cx.add_action(Editor::restart_language_server);
|
||||||
|
cx.add_action(Editor::show_character_palette);
|
||||||
cx.add_async_action(Editor::confirm_completion);
|
cx.add_async_action(Editor::confirm_completion);
|
||||||
cx.add_async_action(Editor::confirm_code_action);
|
cx.add_async_action(Editor::confirm_code_action);
|
||||||
cx.add_async_action(Editor::rename);
|
cx.add_async_action(Editor::rename);
|
||||||
|
@ -405,6 +403,7 @@ pub struct Editor {
|
||||||
autoclose_stack: InvalidationStack<BracketPairState>,
|
autoclose_stack: InvalidationStack<BracketPairState>,
|
||||||
snippet_stack: InvalidationStack<SnippetState>,
|
snippet_stack: InvalidationStack<SnippetState>,
|
||||||
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
|
||||||
|
ime_transaction: Option<TransactionId>,
|
||||||
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
active_diagnostics: Option<ActiveDiagnosticGroup>,
|
||||||
scroll_position: Vector2F,
|
scroll_position: Vector2F,
|
||||||
scroll_top_anchor: Anchor,
|
scroll_top_anchor: Anchor,
|
||||||
|
@ -992,6 +991,7 @@ impl Editor {
|
||||||
autoclose_stack: Default::default(),
|
autoclose_stack: Default::default(),
|
||||||
snippet_stack: Default::default(),
|
snippet_stack: Default::default(),
|
||||||
select_larger_syntax_node_stack: Vec::new(),
|
select_larger_syntax_node_stack: Vec::new(),
|
||||||
|
ime_transaction: Default::default(),
|
||||||
active_diagnostics: None,
|
active_diagnostics: None,
|
||||||
soft_wrap_mode_override: None,
|
soft_wrap_mode_override: None,
|
||||||
get_field_editor_theme,
|
get_field_editor_theme,
|
||||||
|
@ -1808,13 +1808,11 @@ impl Editor {
|
||||||
cx.propagate_action();
|
cx.propagate_action();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_input(&mut self, action: &Input, cx: &mut ViewContext<Self>) {
|
pub fn handle_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
if !self.input_enabled {
|
if !self.input_enabled {
|
||||||
cx.propagate_action();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = action.0.as_ref();
|
|
||||||
if !self.skip_autoclose_end(text, cx) {
|
if !self.skip_autoclose_end(text, cx) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
if !this.surround_with_bracket_pair(text, cx) {
|
if !this.surround_with_bracket_pair(text, cx) {
|
||||||
|
@ -2481,14 +2479,17 @@ impl Editor {
|
||||||
});
|
});
|
||||||
if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
|
if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
|
||||||
if excerpted_buffer == *buffer {
|
if excerpted_buffer == *buffer {
|
||||||
let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot());
|
let all_edits_within_excerpt = buffer.read_with(&cx, |buffer, _| {
|
||||||
let excerpt_range = excerpt_range.to_offset(&snapshot);
|
let excerpt_range = excerpt_range.to_offset(buffer);
|
||||||
if snapshot
|
buffer
|
||||||
.edited_ranges_for_transaction(transaction)
|
.edited_ranges_for_transaction(transaction)
|
||||||
.all(|range| {
|
.all(|range| {
|
||||||
excerpt_range.start <= range.start && excerpt_range.end >= range.end
|
excerpt_range.start <= range.start
|
||||||
|
&& excerpt_range.end >= range.end
|
||||||
})
|
})
|
||||||
{
|
});
|
||||||
|
|
||||||
|
if all_edits_within_excerpt {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2501,12 +2502,12 @@ impl Editor {
|
||||||
let mut ranges_to_highlight = Vec::new();
|
let mut ranges_to_highlight = Vec::new();
|
||||||
let excerpt_buffer = cx.add_model(|cx| {
|
let excerpt_buffer = cx.add_model(|cx| {
|
||||||
let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
|
let mut multibuffer = MultiBuffer::new(replica_id).with_title(title);
|
||||||
for (buffer, transaction) in &entries {
|
for (buffer_handle, transaction) in &entries {
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let buffer = buffer_handle.read(cx);
|
||||||
ranges_to_highlight.extend(
|
ranges_to_highlight.extend(
|
||||||
multibuffer.push_excerpts_with_context_lines(
|
multibuffer.push_excerpts_with_context_lines(
|
||||||
buffer.clone(),
|
buffer_handle.clone(),
|
||||||
snapshot
|
buffer
|
||||||
.edited_ranges_for_transaction::<usize>(transaction)
|
.edited_ranges_for_transaction::<usize>(transaction)
|
||||||
.collect(),
|
.collect(),
|
||||||
1,
|
1,
|
||||||
|
@ -3614,6 +3615,7 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||||
|
self.unmark_text(cx);
|
||||||
cx.emit(Event::Edited);
|
cx.emit(Event::Edited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3627,6 +3629,7 @@ impl Editor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
self.request_autoscroll(Autoscroll::Fit, cx);
|
self.request_autoscroll(Autoscroll::Fit, cx);
|
||||||
|
self.unmark_text(cx);
|
||||||
cx.emit(Event::Edited);
|
cx.emit(Event::Edited);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5026,6 +5029,10 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||||
|
cx.show_character_palette();
|
||||||
|
}
|
||||||
|
|
||||||
fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
|
fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext<Editor>) {
|
||||||
if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
|
if let Some(active_diagnostics) = self.active_diagnostics.as_mut() {
|
||||||
let buffer = self.buffer.read(cx).snapshot(cx);
|
let buffer = self.buffer.read(cx).snapshot(cx);
|
||||||
|
@ -5151,10 +5158,10 @@ impl Editor {
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
|
update: impl FnOnce(&mut Self, &mut ViewContext<Self>),
|
||||||
) {
|
) -> Option<TransactionId> {
|
||||||
self.start_transaction_at(Instant::now(), cx);
|
self.start_transaction_at(Instant::now(), cx);
|
||||||
update(self, cx);
|
update(self, cx);
|
||||||
self.end_transaction_at(Instant::now(), cx);
|
self.end_transaction_at(Instant::now(), cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
|
fn start_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
|
||||||
|
@ -5168,7 +5175,11 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end_transaction_at(&mut self, now: Instant, cx: &mut ViewContext<Self>) {
|
fn end_transaction_at(
|
||||||
|
&mut self,
|
||||||
|
now: Instant,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Option<TransactionId> {
|
||||||
if let Some(tx_id) = self
|
if let Some(tx_id) = self
|
||||||
.buffer
|
.buffer
|
||||||
.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
|
.update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
|
||||||
|
@ -5180,6 +5191,9 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.emit(Event::Edited);
|
cx.emit(Event::Edited);
|
||||||
|
Some(tx_id)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5528,6 +5542,13 @@ impl Editor {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn text_highlights<'a, T: 'static>(
|
||||||
|
&'a self,
|
||||||
|
cx: &'a AppContext,
|
||||||
|
) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
|
||||||
|
self.display_map.read(cx).text_highlights(TypeId::of::<T>())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_text_highlights<T: 'static>(
|
pub fn clear_text_highlights<T: 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
cx: &mut ViewContext<Self>,
|
cx: &mut ViewContext<Self>,
|
||||||
|
@ -5718,6 +5739,44 @@ impl Editor {
|
||||||
})
|
})
|
||||||
.detach()
|
.detach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn marked_text_ranges(&self, cx: &AppContext) -> Option<Vec<Range<OffsetUtf16>>> {
|
||||||
|
let snapshot = self.buffer.read(cx).read(cx);
|
||||||
|
let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
|
||||||
|
Some(
|
||||||
|
ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(move |range| {
|
||||||
|
range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selection_replacement_ranges(
|
||||||
|
&self,
|
||||||
|
range: Range<OffsetUtf16>,
|
||||||
|
cx: &AppContext,
|
||||||
|
) -> Vec<Range<OffsetUtf16>> {
|
||||||
|
let selections = self.selections.all::<OffsetUtf16>(cx);
|
||||||
|
let newest_selection = selections
|
||||||
|
.iter()
|
||||||
|
.max_by_key(|selection| selection.id)
|
||||||
|
.unwrap();
|
||||||
|
let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
|
||||||
|
let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
|
||||||
|
let snapshot = self.buffer.read(cx).read(cx);
|
||||||
|
selections
|
||||||
|
.into_iter()
|
||||||
|
.map(|mut selection| {
|
||||||
|
selection.start.0 =
|
||||||
|
(selection.start.0 as isize).saturating_add(start_delta) as usize;
|
||||||
|
selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
|
||||||
|
snapshot.clip_offset_utf16(selection.start, Bias::Left)
|
||||||
|
..snapshot.clip_offset_utf16(selection.end, Bias::Right)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EditorSnapshot {
|
impl EditorSnapshot {
|
||||||
|
@ -5773,6 +5832,7 @@ pub enum Event {
|
||||||
SelectionsChanged { local: bool },
|
SelectionsChanged { local: bool },
|
||||||
ScrollPositionChanged { local: bool },
|
ScrollPositionChanged { local: bool },
|
||||||
Closed,
|
Closed,
|
||||||
|
IgnoredInput,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditorFocused(pub ViewHandle<Editor>);
|
pub struct EditorFocused(pub ViewHandle<Editor>);
|
||||||
|
@ -5877,6 +5937,168 @@ impl View for Editor {
|
||||||
|
|
||||||
context
|
context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_for_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<String> {
|
||||||
|
Some(
|
||||||
|
self.buffer
|
||||||
|
.read(cx)
|
||||||
|
.read(cx)
|
||||||
|
.text_for_range(OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
|
||||||
|
// Prevent the IME menu from appearing when holding down an alphabetic key
|
||||||
|
// while input is disabled.
|
||||||
|
if !self.input_enabled {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = self.selections.newest::<OffsetUtf16>(cx).range();
|
||||||
|
Some(range.start.0..range.end.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
|
||||||
|
let snapshot = self.buffer.read(cx).read(cx);
|
||||||
|
let range = self.text_highlights::<InputComposition>(cx)?.1.get(0)?;
|
||||||
|
Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmark_text(&mut self, cx: &mut ViewContext<Self>) {
|
||||||
|
self.clear_text_highlights::<InputComposition>(cx);
|
||||||
|
self.ime_transaction.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
text: &str,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if !self.input_enabled {
|
||||||
|
cx.emit(Event::IgnoredInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.transact(cx, |this, cx| {
|
||||||
|
let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
|
||||||
|
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
|
||||||
|
Some(this.selection_replacement_ranges(range_utf16, cx))
|
||||||
|
} else if let Some(marked_ranges) = this.marked_text_ranges(cx) {
|
||||||
|
Some(marked_ranges)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(new_selected_ranges) = new_selected_ranges {
|
||||||
|
this.change_selections(None, cx, |selections| {
|
||||||
|
selections.select_ranges(new_selected_ranges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.handle_input(text, cx);
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(transaction) = self.ime_transaction {
|
||||||
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.group_until_transaction(transaction, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.unmark_text(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
text: &str,
|
||||||
|
new_selected_range_utf16: Option<Range<usize>>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
if !self.input_enabled {
|
||||||
|
cx.emit(Event::IgnoredInput);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transaction = self.transact(cx, |this, cx| {
|
||||||
|
let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
|
||||||
|
let snapshot = this.buffer.read(cx).read(cx);
|
||||||
|
if let Some(relative_range_utf16) = range_utf16.as_ref() {
|
||||||
|
for marked_range in &mut marked_ranges {
|
||||||
|
marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
|
||||||
|
marked_range.start.0 += relative_range_utf16.start;
|
||||||
|
marked_range.start =
|
||||||
|
snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
|
||||||
|
marked_range.end =
|
||||||
|
snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(marked_ranges)
|
||||||
|
} else if let Some(range_utf16) = range_utf16 {
|
||||||
|
let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
|
||||||
|
Some(this.selection_replacement_ranges(range_utf16, cx))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ranges) = ranges_to_replace {
|
||||||
|
this.change_selections(None, cx, |s| s.select_ranges(ranges));
|
||||||
|
}
|
||||||
|
|
||||||
|
let marked_ranges = {
|
||||||
|
let snapshot = this.buffer.read(cx).read(cx);
|
||||||
|
this.selections
|
||||||
|
.disjoint_anchors()
|
||||||
|
.into_iter()
|
||||||
|
.map(|selection| {
|
||||||
|
selection.start.bias_left(&*snapshot)..selection.end.bias_right(&*snapshot)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
};
|
||||||
|
|
||||||
|
if text.is_empty() {
|
||||||
|
this.unmark_text(cx);
|
||||||
|
} else {
|
||||||
|
this.highlight_text::<InputComposition>(
|
||||||
|
marked_ranges.clone(),
|
||||||
|
this.style(cx).composition_mark,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handle_input(text, cx);
|
||||||
|
|
||||||
|
if let Some(new_selected_range) = new_selected_range_utf16 {
|
||||||
|
let snapshot = this.buffer.read(cx).read(cx);
|
||||||
|
let new_selected_ranges = marked_ranges
|
||||||
|
.into_iter()
|
||||||
|
.map(|marked_range| {
|
||||||
|
let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
|
||||||
|
let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
|
||||||
|
let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
|
||||||
|
snapshot.clip_offset_utf16(new_start, Bias::Left)
|
||||||
|
..snapshot.clip_offset_utf16(new_end, Bias::Right)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
drop(snapshot);
|
||||||
|
this.change_selections(None, cx, |selections| {
|
||||||
|
selections.select_ranges(new_selected_ranges)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
self.ime_transaction = self.ime_transaction.or(transaction);
|
||||||
|
if let Some(transaction) = self.ime_transaction {
|
||||||
|
self.buffer.update(cx, |buffer, cx| {
|
||||||
|
buffer.group_until_transaction(transaction, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.text_highlights::<InputComposition>(cx).is_none() {
|
||||||
|
self.ime_transaction.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_style(
|
fn build_style(
|
||||||
|
@ -6473,6 +6695,108 @@ mod tests {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn test_ime_composition(cx: &mut MutableAppContext) {
|
||||||
|
cx.set_global(Settings::test(cx));
|
||||||
|
let buffer = cx.add_model(|cx| {
|
||||||
|
let mut buffer = language::Buffer::new(0, "abcde", cx);
|
||||||
|
// Ensure automatic grouping doesn't occur.
|
||||||
|
buffer.set_group_interval(Duration::ZERO);
|
||||||
|
buffer
|
||||||
|
});
|
||||||
|
|
||||||
|
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
|
||||||
|
cx.add_window(Default::default(), |cx| {
|
||||||
|
let mut editor = build_editor(buffer.clone(), cx);
|
||||||
|
|
||||||
|
// Start a new IME composition.
|
||||||
|
editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
|
||||||
|
editor.replace_and_mark_text_in_range(Some(0..1), "á", None, cx);
|
||||||
|
editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, cx);
|
||||||
|
assert_eq!(editor.text(cx), "äbcde");
|
||||||
|
assert_eq!(
|
||||||
|
editor.marked_text_ranges(cx),
|
||||||
|
Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Finalize IME composition.
|
||||||
|
editor.replace_text_in_range(None, "ā", cx);
|
||||||
|
assert_eq!(editor.text(cx), "ābcde");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
|
||||||
|
// IME composition edits are grouped and are undone/redone at once.
|
||||||
|
editor.undo(&Default::default(), cx);
|
||||||
|
assert_eq!(editor.text(cx), "abcde");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
editor.redo(&Default::default(), cx);
|
||||||
|
assert_eq!(editor.text(cx), "ābcde");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
|
||||||
|
// Start a new IME composition.
|
||||||
|
editor.replace_and_mark_text_in_range(Some(0..1), "à", None, cx);
|
||||||
|
assert_eq!(
|
||||||
|
editor.marked_text_ranges(cx),
|
||||||
|
Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Undoing during an IME composition cancels it.
|
||||||
|
editor.undo(&Default::default(), cx);
|
||||||
|
assert_eq!(editor.text(cx), "ābcde");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
|
||||||
|
// Start a new IME composition with an invalid marked range, ensuring it gets clipped.
|
||||||
|
editor.replace_and_mark_text_in_range(Some(4..999), "è", None, cx);
|
||||||
|
assert_eq!(editor.text(cx), "ābcdè");
|
||||||
|
assert_eq!(
|
||||||
|
editor.marked_text_ranges(cx),
|
||||||
|
Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
|
||||||
|
editor.replace_text_in_range(Some(4..999), "ę", cx);
|
||||||
|
assert_eq!(editor.text(cx), "ābcdę");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
|
||||||
|
// Start a new IME composition with multiple cursors.
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.select_ranges([
|
||||||
|
OffsetUtf16(1)..OffsetUtf16(1),
|
||||||
|
OffsetUtf16(3)..OffsetUtf16(3),
|
||||||
|
OffsetUtf16(5)..OffsetUtf16(5),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, cx);
|
||||||
|
assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
|
||||||
|
assert_eq!(
|
||||||
|
editor.marked_text_ranges(cx),
|
||||||
|
Some(vec![
|
||||||
|
OffsetUtf16(0)..OffsetUtf16(3),
|
||||||
|
OffsetUtf16(4)..OffsetUtf16(7),
|
||||||
|
OffsetUtf16(8)..OffsetUtf16(11)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
|
||||||
|
editor.replace_and_mark_text_in_range(Some(1..2), "1", None, cx);
|
||||||
|
assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
|
||||||
|
assert_eq!(
|
||||||
|
editor.marked_text_ranges(cx),
|
||||||
|
Some(vec![
|
||||||
|
OffsetUtf16(1)..OffsetUtf16(2),
|
||||||
|
OffsetUtf16(5)..OffsetUtf16(6),
|
||||||
|
OffsetUtf16(9)..OffsetUtf16(10)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
// Finalize IME composition with multiple cursors.
|
||||||
|
editor.replace_text_in_range(Some(9..10), "2", cx);
|
||||||
|
assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
|
||||||
|
assert_eq!(editor.marked_text_ranges(cx), None);
|
||||||
|
|
||||||
|
editor
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
fn test_selection_with_mouse(cx: &mut gpui::MutableAppContext) {
|
||||||
cx.set_global(Settings::test(cx));
|
cx.set_global(Settings::test(cx));
|
||||||
|
@ -8247,9 +8571,9 @@ mod tests {
|
||||||
// is pasted at each cursor.
|
// is pasted at each cursor.
|
||||||
cx.set_state("|two one✅ four three six five |");
|
cx.set_state("|two one✅ four three six five |");
|
||||||
cx.update_editor(|e, cx| {
|
cx.update_editor(|e, cx| {
|
||||||
e.handle_input(&Input("( ".into()), cx);
|
e.handle_input("( ", cx);
|
||||||
e.paste(&Paste, cx);
|
e.paste(&Paste, cx);
|
||||||
e.handle_input(&Input(") ".into()), cx);
|
e.handle_input(") ", cx);
|
||||||
});
|
});
|
||||||
cx.assert_editor_state(indoc! {"
|
cx.assert_editor_state(indoc! {"
|
||||||
( one✅
|
( one✅
|
||||||
|
@ -8924,9 +9248,9 @@ mod tests {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -8939,9 +9263,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
view.move_right(&MoveRight, cx);
|
view.move_right(&MoveRight, cx);
|
||||||
view.handle_input(&Input("}".to_string()), cx);
|
view.handle_input("}", cx);
|
||||||
view.handle_input(&Input("}".to_string()), cx);
|
view.handle_input("}", cx);
|
||||||
view.handle_input(&Input("}".to_string()), cx);
|
view.handle_input("}", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -8954,8 +9278,8 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
view.undo(&Undo, cx);
|
view.undo(&Undo, cx);
|
||||||
view.handle_input(&Input("/".to_string()), cx);
|
view.handle_input("/", cx);
|
||||||
view.handle_input(&Input("*".to_string()), cx);
|
view.handle_input("*", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -8974,7 +9298,7 @@ mod tests {
|
||||||
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0),
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
view.handle_input(&Input("*".to_string()), cx);
|
view.handle_input("*", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -8992,7 +9316,7 @@ mod tests {
|
||||||
view.change_selections(None, cx, |s| {
|
view.change_selections(None, cx, |s| {
|
||||||
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
|
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 0)])
|
||||||
});
|
});
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9008,7 +9332,7 @@ mod tests {
|
||||||
view.change_selections(None, cx, |s| {
|
view.change_selections(None, cx, |s| {
|
||||||
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)])
|
s.select_display_ranges([DisplayPoint::new(0, 0)..DisplayPoint::new(0, 1)])
|
||||||
});
|
});
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9025,7 +9349,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
view.undo(&Undo, cx);
|
view.undo(&Undo, cx);
|
||||||
view.handle_input(&Input("[".to_string()), cx);
|
view.handle_input("[", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9045,7 +9369,7 @@ mod tests {
|
||||||
view.change_selections(None, cx, |s| {
|
view.change_selections(None, cx, |s| {
|
||||||
s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
|
s.select_display_ranges([DisplayPoint::new(0, 1)..DisplayPoint::new(0, 1)])
|
||||||
});
|
});
|
||||||
view.handle_input(&Input("[".to_string()), cx);
|
view.handle_input("[", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9101,9 +9425,9 @@ mod tests {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
view.handle_input(&Input("{".to_string()), cx);
|
view.handle_input("{", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.text(cx),
|
view.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9183,9 +9507,9 @@ mod tests {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.handle_input(&Input("{".to_string()), cx);
|
editor.handle_input("{", cx);
|
||||||
editor.handle_input(&Input("{".to_string()), cx);
|
editor.handle_input("{", cx);
|
||||||
editor.handle_input(&Input("_".to_string()), cx);
|
editor.handle_input("_", cx);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
editor.text(cx),
|
editor.text(cx),
|
||||||
"
|
"
|
||||||
|
@ -9699,7 +10023,9 @@ mod tests {
|
||||||
cx.set_state("editor|");
|
cx.set_state("editor|");
|
||||||
cx.simulate_keystroke(".");
|
cx.simulate_keystroke(".");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
||||||
cx.simulate_keystrokes(["c", "l", "o"]);
|
cx.simulate_keystroke("c");
|
||||||
|
cx.simulate_keystroke("l");
|
||||||
|
cx.simulate_keystroke("o");
|
||||||
cx.assert_editor_state("editor.clo|");
|
cx.assert_editor_state("editor.clo|");
|
||||||
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
assert!(cx.editor(|e, _| e.context_menu.is_none()));
|
||||||
cx.update_editor(|editor, cx| {
|
cx.update_editor(|editor, cx| {
|
||||||
|
@ -9911,7 +10237,7 @@ mod tests {
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|
||||||
view.handle_input(&Input("X".to_string()), cx);
|
view.handle_input("X", cx);
|
||||||
assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
|
assert_eq!(view.text(cx), "Xaaaa\nXbbbb");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
view.selections.ranges(cx),
|
view.selections.ranges(cx),
|
||||||
|
@ -9951,7 +10277,7 @@ mod tests {
|
||||||
assert_eq!(view.text(cx), expected_text);
|
assert_eq!(view.text(cx), expected_text);
|
||||||
view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
|
view.change_selections(None, cx, |s| s.select_ranges(selection_ranges));
|
||||||
|
|
||||||
view.handle_input(&Input("X".to_string()), cx);
|
view.handle_input("X", cx);
|
||||||
|
|
||||||
let (expected_text, expected_selections) = marked_text_ranges(indoc! {"
|
let (expected_text, expected_selections) = marked_text_ranges(indoc! {"
|
||||||
aaaa
|
aaaa
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use super::{
|
use super::{
|
||||||
display_map::{BlockContext, ToDisplayPoint},
|
display_map::{BlockContext, ToDisplayPoint},
|
||||||
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
|
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Scroll, Select, SelectPhase,
|
||||||
SoftWrap, ToPoint, MAX_LINE_LEN,
|
SoftWrap, ToPoint, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -24,13 +24,13 @@ use gpui::{
|
||||||
json::{self, ToJson},
|
json::{self, ToJson},
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
text_layout::{self, Line, RunStyle, TextLayoutCache},
|
||||||
AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, KeyDownEvent,
|
AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext,
|
||||||
LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent,
|
LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent,
|
||||||
MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext,
|
MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext,
|
||||||
WeakViewHandle,
|
WeakViewHandle,
|
||||||
};
|
};
|
||||||
use json::json;
|
use json::json;
|
||||||
use language::{Bias, DiagnosticSeverity, Selection};
|
use language::{Bias, DiagnosticSeverity, OffsetUtf16, Selection};
|
||||||
use project::ProjectPath;
|
use project::ProjectPath;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -283,21 +283,6 @@ impl EditorElement {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_down(&self, input: Option<&str>, cx: &mut EventContext) -> bool {
|
|
||||||
let view = self.view.upgrade(cx.app).unwrap();
|
|
||||||
|
|
||||||
if view.is_focused(cx.app) {
|
|
||||||
if let Some(input) = input {
|
|
||||||
cx.dispatch_action(Input(input.to_string()));
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
|
fn modifiers_changed(&self, cmd: bool, cx: &mut EventContext) -> bool {
|
||||||
cx.dispatch_action(CmdChanged { cmd_down: cmd });
|
cx.dispatch_action(CmdChanged { cmd_down: cmd });
|
||||||
false
|
false
|
||||||
|
@ -1569,7 +1554,6 @@ impl Element for EditorElement {
|
||||||
delta,
|
delta,
|
||||||
precise,
|
precise,
|
||||||
}) => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
}) => self.scroll(*position, *delta, *precise, layout, paint, cx),
|
||||||
Event::KeyDown(KeyDownEvent { input, .. }) => self.key_down(input.as_deref(), cx),
|
|
||||||
Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) => {
|
Event::ModifiersChanged(ModifiersChangedEvent { cmd, .. }) => {
|
||||||
self.modifiers_changed(*cmd, cx)
|
self.modifiers_changed(*cmd, cx)
|
||||||
}
|
}
|
||||||
|
@ -1581,6 +1565,43 @@ impl Element for EditorElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
bounds: RectF,
|
||||||
|
_: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &gpui::MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
let text_bounds = RectF::new(
|
||||||
|
bounds.origin() + vec2f(layout.gutter_size.x(), 0.0),
|
||||||
|
layout.text_size,
|
||||||
|
);
|
||||||
|
let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.);
|
||||||
|
let scroll_position = layout.snapshot.scroll_position();
|
||||||
|
let start_row = scroll_position.y() as u32;
|
||||||
|
let scroll_top = scroll_position.y() * layout.line_height;
|
||||||
|
let scroll_left = scroll_position.x() * layout.em_width;
|
||||||
|
|
||||||
|
let range_start =
|
||||||
|
OffsetUtf16(range_utf16.start).to_display_point(&layout.snapshot.display_snapshot);
|
||||||
|
if range_start.row() < start_row {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = layout
|
||||||
|
.line_layouts
|
||||||
|
.get((range_start.row() - start_row) as usize)?;
|
||||||
|
let range_start_x = line.x_for_index(range_start.column() as usize);
|
||||||
|
let range_start_y = range_start.row() as f32 * layout.line_height;
|
||||||
|
Some(RectF::new(
|
||||||
|
content_origin + vec2f(range_start_x, range_start_y + layout.line_height)
|
||||||
|
- vec2f(scroll_left, scroll_top),
|
||||||
|
vec2f(layout.em_width, layout.line_height),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
@ -1740,6 +1761,13 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bounding_rect(&self, origin: Vector2F) -> RectF {
|
||||||
|
RectF::new(
|
||||||
|
self.origin + origin,
|
||||||
|
vec2f(self.block_width, self.line_height),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn paint(&self, origin: Vector2F, cx: &mut PaintContext) {
|
pub fn paint(&self, origin: Vector2F, cx: &mut PaintContext) {
|
||||||
let bounds = match self.shape {
|
let bounds = match self.shape {
|
||||||
CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
|
CursorShape::Bar => RectF::new(self.origin + origin, vec2f(2.0, self.line_height)),
|
||||||
|
|
|
@ -419,7 +419,6 @@ mod tests {
|
||||||
requests.next().await;
|
requests.next().await;
|
||||||
cx.foreground().run_until_parked();
|
cx.foreground().run_until_parked();
|
||||||
|
|
||||||
println!("tag");
|
|
||||||
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
cx.assert_editor_text_highlights::<LinkGoToDefinitionState>(indoc! {"
|
||||||
fn test()
|
fn test()
|
||||||
[do_work]();
|
[do_work]();
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub use language::Completion;
|
||||||
use language::{
|
use language::{
|
||||||
char_kind, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, DiagnosticEntry, Event, File,
|
char_kind, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, DiagnosticEntry, Event, File,
|
||||||
IndentSize, Language, OffsetRangeExt, Outline, OutlineItem, Selection, ToOffset as _,
|
IndentSize, Language, OffsetRangeExt, Outline, OutlineItem, Selection, ToOffset as _,
|
||||||
ToPoint as _, ToPointUtf16 as _, TransactionId,
|
ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -29,7 +29,7 @@ use text::{
|
||||||
locator::Locator,
|
locator::Locator,
|
||||||
rope::TextDimension,
|
rope::TextDimension,
|
||||||
subscription::{Subscription, Topic},
|
subscription::{Subscription, Topic},
|
||||||
Edit, Point, PointUtf16, TextSummary,
|
Edit, OffsetUtf16, Point, PointUtf16, TextSummary,
|
||||||
};
|
};
|
||||||
use theme::SyntaxTheme;
|
use theme::SyntaxTheme;
|
||||||
use util::post_inc;
|
use util::post_inc;
|
||||||
|
@ -72,6 +72,10 @@ pub trait ToOffset: 'static + fmt::Debug {
|
||||||
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
|
fn to_offset(&self, snapshot: &MultiBufferSnapshot) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToOffsetUtf16: 'static + fmt::Debug {
|
||||||
|
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ToPoint: 'static + fmt::Debug {
|
pub trait ToPoint: 'static + fmt::Debug {
|
||||||
fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
|
fn to_point(&self, snapshot: &MultiBufferSnapshot) -> Point;
|
||||||
}
|
}
|
||||||
|
@ -554,6 +558,20 @@ impl MultiBuffer {
|
||||||
self.history.finalize_last_transaction();
|
self.history.finalize_last_transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn group_until_transaction(
|
||||||
|
&mut self,
|
||||||
|
transaction_id: TransactionId,
|
||||||
|
cx: &mut ModelContext<Self>,
|
||||||
|
) {
|
||||||
|
if let Some(buffer) = self.as_singleton() {
|
||||||
|
buffer.update(cx, |buffer, _| {
|
||||||
|
buffer.group_until_transaction(transaction_id)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.history.group_until(transaction_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_active_selections(
|
pub fn set_active_selections(
|
||||||
&mut self,
|
&mut self,
|
||||||
selections: &[Selection<Anchor>],
|
selections: &[Selection<Anchor>],
|
||||||
|
@ -809,7 +827,7 @@ impl MultiBuffer {
|
||||||
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
|
let mut cursor = snapshot.excerpts.cursor::<Option<&ExcerptId>>();
|
||||||
let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &());
|
let mut new_excerpts = cursor.slice(&Some(prev_excerpt_id), Bias::Right, &());
|
||||||
|
|
||||||
let edit_start = new_excerpts.summary().text.bytes;
|
let edit_start = new_excerpts.summary().text.len;
|
||||||
new_excerpts.update_last(
|
new_excerpts.update_last(
|
||||||
|excerpt| {
|
|excerpt| {
|
||||||
excerpt.has_trailing_newline = true;
|
excerpt.has_trailing_newline = true;
|
||||||
|
@ -862,7 +880,7 @@ impl MultiBuffer {
|
||||||
&(),
|
&(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let edit_end = new_excerpts.summary().text.bytes;
|
let edit_end = new_excerpts.summary().text.len;
|
||||||
|
|
||||||
let suffix = cursor.suffix(&());
|
let suffix = cursor.suffix(&());
|
||||||
let changed_trailing_excerpt = suffix.is_empty();
|
let changed_trailing_excerpt = suffix.is_empty();
|
||||||
|
@ -1068,7 +1086,7 @@ impl MultiBuffer {
|
||||||
|
|
||||||
// Push an edit for the removal of this run of excerpts.
|
// Push an edit for the removal of this run of excerpts.
|
||||||
let old_end = cursor.start().1;
|
let old_end = cursor.start().1;
|
||||||
let new_start = new_excerpts.summary().text.bytes;
|
let new_start = new_excerpts.summary().text.len;
|
||||||
edits.push(Edit {
|
edits.push(Edit {
|
||||||
old: old_start..old_end,
|
old: old_start..old_end,
|
||||||
new: new_start..new_start,
|
new: new_start..new_start,
|
||||||
|
@ -1297,7 +1315,7 @@ impl MultiBuffer {
|
||||||
)
|
)
|
||||||
.map(|mut edit| {
|
.map(|mut edit| {
|
||||||
let excerpt_old_start = cursor.start().1;
|
let excerpt_old_start = cursor.start().1;
|
||||||
let excerpt_new_start = new_excerpts.summary().text.bytes;
|
let excerpt_new_start = new_excerpts.summary().text.len;
|
||||||
edit.old.start += excerpt_old_start;
|
edit.old.start += excerpt_old_start;
|
||||||
edit.old.end += excerpt_old_start;
|
edit.old.end += excerpt_old_start;
|
||||||
edit.new.start += excerpt_new_start;
|
edit.new.start += excerpt_new_start;
|
||||||
|
@ -1527,7 +1545,7 @@ impl MultiBufferSnapshot {
|
||||||
let mut cursor = self.excerpts.cursor::<usize>();
|
let mut cursor = self.excerpts.cursor::<usize>();
|
||||||
cursor.seek(&offset, Bias::Left, &());
|
cursor.seek(&offset, Bias::Left, &());
|
||||||
let mut excerpt_chunks = cursor.item().map(|excerpt| {
|
let mut excerpt_chunks = cursor.item().map(|excerpt| {
|
||||||
let end_before_footer = cursor.start() + excerpt.text_summary.bytes;
|
let end_before_footer = cursor.start() + excerpt.text_summary.len;
|
||||||
let start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
let start = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||||
let end = start + (cmp::min(offset, end_before_footer) - cursor.start());
|
let end = start + (cmp::min(offset, end_before_footer) - cursor.start());
|
||||||
excerpt.buffer.reversed_chunks_in_range(start..end)
|
excerpt.buffer.reversed_chunks_in_range(start..end)
|
||||||
|
@ -1629,7 +1647,7 @@ impl MultiBufferSnapshot {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.excerpts.summary().text.bytes
|
self.excerpts.summary().text.len
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_buffer_row(&self) -> u32 {
|
pub fn max_buffer_row(&self) -> u32 {
|
||||||
|
@ -1674,6 +1692,25 @@ impl MultiBufferSnapshot {
|
||||||
*cursor.start() + overshoot
|
*cursor.start() + overshoot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
|
||||||
|
if let Some((_, _, buffer)) = self.as_singleton() {
|
||||||
|
return buffer.clip_offset_utf16(offset, bias);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = self.excerpts.cursor::<OffsetUtf16>();
|
||||||
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
|
let overshoot = if let Some(excerpt) = cursor.item() {
|
||||||
|
let excerpt_start = excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
|
||||||
|
let buffer_offset = excerpt
|
||||||
|
.buffer
|
||||||
|
.clip_offset_utf16(excerpt_start + (offset - cursor.start()), bias);
|
||||||
|
OffsetUtf16(buffer_offset.0.saturating_sub(excerpt_start.0))
|
||||||
|
} else {
|
||||||
|
OffsetUtf16(0)
|
||||||
|
};
|
||||||
|
*cursor.start() + overshoot
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||||
if let Some((_, _, buffer)) = self.as_singleton() {
|
if let Some((_, _, buffer)) = self.as_singleton() {
|
||||||
return buffer.clip_point_utf16(point, bias);
|
return buffer.clip_point_utf16(point, bias);
|
||||||
|
@ -1781,7 +1818,7 @@ impl MultiBufferSnapshot {
|
||||||
.offset_to_point_utf16(excerpt_start_offset + overshoot);
|
.offset_to_point_utf16(excerpt_start_offset + overshoot);
|
||||||
*start_point + (buffer_point - excerpt_start_point)
|
*start_point + (buffer_point - excerpt_start_point)
|
||||||
} else {
|
} else {
|
||||||
self.excerpts.summary().text.lines_utf16
|
self.excerpts.summary().text.lines_utf16()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1803,7 +1840,7 @@ impl MultiBufferSnapshot {
|
||||||
.point_to_point_utf16(excerpt_start_point + overshoot);
|
.point_to_point_utf16(excerpt_start_point + overshoot);
|
||||||
*start_point + (buffer_point - excerpt_start_point_utf16)
|
*start_point + (buffer_point - excerpt_start_point_utf16)
|
||||||
} else {
|
} else {
|
||||||
self.excerpts.summary().text.lines_utf16
|
self.excerpts.summary().text.lines_utf16()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1824,7 +1861,53 @@ impl MultiBufferSnapshot {
|
||||||
.point_to_offset(excerpt_start_point + overshoot);
|
.point_to_offset(excerpt_start_point + overshoot);
|
||||||
*start_offset + buffer_offset - excerpt_start_offset
|
*start_offset + buffer_offset - excerpt_start_offset
|
||||||
} else {
|
} else {
|
||||||
self.excerpts.summary().text.bytes
|
self.excerpts.summary().text.len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_utf16_to_offset(&self, offset_utf16: OffsetUtf16) -> usize {
|
||||||
|
if let Some((_, _, buffer)) = self.as_singleton() {
|
||||||
|
return buffer.offset_utf16_to_offset(offset_utf16);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = self.excerpts.cursor::<(OffsetUtf16, usize)>();
|
||||||
|
cursor.seek(&offset_utf16, Bias::Right, &());
|
||||||
|
if let Some(excerpt) = cursor.item() {
|
||||||
|
let (start_offset_utf16, start_offset) = cursor.start();
|
||||||
|
let overshoot = offset_utf16 - start_offset_utf16;
|
||||||
|
let excerpt_start_offset = excerpt.range.context.start.to_offset(&excerpt.buffer);
|
||||||
|
let excerpt_start_offset_utf16 =
|
||||||
|
excerpt.buffer.offset_to_offset_utf16(excerpt_start_offset);
|
||||||
|
let buffer_offset = excerpt
|
||||||
|
.buffer
|
||||||
|
.offset_utf16_to_offset(excerpt_start_offset_utf16 + overshoot);
|
||||||
|
*start_offset + (buffer_offset - excerpt_start_offset)
|
||||||
|
} else {
|
||||||
|
self.excerpts.summary().text.len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
|
||||||
|
if let Some((_, _, buffer)) = self.as_singleton() {
|
||||||
|
return buffer.offset_to_offset_utf16(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut cursor = self.excerpts.cursor::<(usize, OffsetUtf16)>();
|
||||||
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
|
if let Some(excerpt) = cursor.item() {
|
||||||
|
let (start_offset, start_offset_utf16) = cursor.start();
|
||||||
|
let overshoot = offset - start_offset;
|
||||||
|
let excerpt_start_offset_utf16 =
|
||||||
|
excerpt.range.context.start.to_offset_utf16(&excerpt.buffer);
|
||||||
|
let excerpt_start_offset = excerpt
|
||||||
|
.buffer
|
||||||
|
.offset_utf16_to_offset(excerpt_start_offset_utf16);
|
||||||
|
let buffer_offset_utf16 = excerpt
|
||||||
|
.buffer
|
||||||
|
.offset_to_offset_utf16(excerpt_start_offset + overshoot);
|
||||||
|
*start_offset_utf16 + (buffer_offset_utf16 - excerpt_start_offset_utf16)
|
||||||
|
} else {
|
||||||
|
self.excerpts.summary().text.len_utf16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1847,7 +1930,7 @@ impl MultiBufferSnapshot {
|
||||||
.point_utf16_to_offset(excerpt_start_point + overshoot);
|
.point_utf16_to_offset(excerpt_start_point + overshoot);
|
||||||
*start_offset + (buffer_offset - excerpt_start_offset)
|
*start_offset + (buffer_offset - excerpt_start_offset)
|
||||||
} else {
|
} else {
|
||||||
self.excerpts.summary().text.bytes
|
self.excerpts.summary().text.len
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2311,7 +2394,7 @@ impl MultiBufferSnapshot {
|
||||||
.context
|
.context
|
||||||
.start
|
.start
|
||||||
.to_offset(&start_excerpt.buffer);
|
.to_offset(&start_excerpt.buffer);
|
||||||
let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.bytes;
|
let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.len;
|
||||||
|
|
||||||
let start_in_buffer =
|
let start_in_buffer =
|
||||||
excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
|
excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
|
||||||
|
@ -2415,7 +2498,7 @@ impl MultiBufferSnapshot {
|
||||||
.context
|
.context
|
||||||
.start
|
.start
|
||||||
.to_offset(&start_excerpt.buffer);
|
.to_offset(&start_excerpt.buffer);
|
||||||
let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.bytes;
|
let excerpt_buffer_end = excerpt_buffer_start + start_excerpt.text_summary.len;
|
||||||
|
|
||||||
let start_in_buffer =
|
let start_in_buffer =
|
||||||
excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
|
excerpt_buffer_start + range.start.saturating_sub(*cursor.start());
|
||||||
|
@ -2651,9 +2734,8 @@ impl History {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group(&mut self) -> Option<TransactionId> {
|
fn group(&mut self) -> Option<TransactionId> {
|
||||||
let mut new_len = self.undo_stack.len();
|
let mut count = 0;
|
||||||
let mut transactions = self.undo_stack.iter_mut();
|
let mut transactions = self.undo_stack.iter();
|
||||||
|
|
||||||
if let Some(mut transaction) = transactions.next_back() {
|
if let Some(mut transaction) = transactions.next_back() {
|
||||||
while let Some(prev_transaction) = transactions.next_back() {
|
while let Some(prev_transaction) = transactions.next_back() {
|
||||||
if !prev_transaction.suppress_grouping
|
if !prev_transaction.suppress_grouping
|
||||||
|
@ -2661,13 +2743,31 @@ impl History {
|
||||||
<= self.group_interval
|
<= self.group_interval
|
||||||
{
|
{
|
||||||
transaction = prev_transaction;
|
transaction = prev_transaction;
|
||||||
new_len -= 1;
|
count += 1;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.group_trailing(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_until(&mut self, transaction_id: TransactionId) {
|
||||||
|
let mut count = 0;
|
||||||
|
for transaction in self.undo_stack.iter().rev() {
|
||||||
|
if transaction.id == transaction_id {
|
||||||
|
self.group_trailing(count);
|
||||||
|
break;
|
||||||
|
} else if transaction.suppress_grouping {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
|
||||||
|
let new_len = self.undo_stack.len() - n;
|
||||||
let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
|
let (transactions_to_keep, transactions_to_merge) = self.undo_stack.split_at_mut(new_len);
|
||||||
if let Some(last_transaction) = transactions_to_keep.last_mut() {
|
if let Some(last_transaction) = transactions_to_keep.last_mut() {
|
||||||
if let Some(transaction) = transactions_to_merge.last() {
|
if let Some(transaction) = transactions_to_merge.last() {
|
||||||
|
@ -2717,11 +2817,11 @@ impl Excerpt {
|
||||||
) -> ExcerptChunks<'a> {
|
) -> ExcerptChunks<'a> {
|
||||||
let content_start = self.range.context.start.to_offset(&self.buffer);
|
let content_start = self.range.context.start.to_offset(&self.buffer);
|
||||||
let chunks_start = content_start + range.start;
|
let chunks_start = content_start + range.start;
|
||||||
let chunks_end = content_start + cmp::min(range.end, self.text_summary.bytes);
|
let chunks_end = content_start + cmp::min(range.end, self.text_summary.len);
|
||||||
|
|
||||||
let footer_height = if self.has_trailing_newline
|
let footer_height = if self.has_trailing_newline
|
||||||
&& range.start <= self.text_summary.bytes
|
&& range.start <= self.text_summary.len
|
||||||
&& range.end > self.text_summary.bytes
|
&& range.end > self.text_summary.len
|
||||||
{
|
{
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
|
@ -2739,10 +2839,10 @@ impl Excerpt {
|
||||||
fn bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
|
fn bytes_in_range(&self, range: Range<usize>) -> ExcerptBytes {
|
||||||
let content_start = self.range.context.start.to_offset(&self.buffer);
|
let content_start = self.range.context.start.to_offset(&self.buffer);
|
||||||
let bytes_start = content_start + range.start;
|
let bytes_start = content_start + range.start;
|
||||||
let bytes_end = content_start + cmp::min(range.end, self.text_summary.bytes);
|
let bytes_end = content_start + cmp::min(range.end, self.text_summary.len);
|
||||||
let footer_height = if self.has_trailing_newline
|
let footer_height = if self.has_trailing_newline
|
||||||
&& range.start <= self.text_summary.bytes
|
&& range.start <= self.text_summary.len
|
||||||
&& range.end > self.text_summary.bytes
|
&& range.end > self.text_summary.len
|
||||||
{
|
{
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
|
@ -2836,13 +2936,13 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for TextSummary {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
|
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for usize {
|
||||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||||
*self += summary.text.bytes;
|
*self += summary.text.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
|
impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for usize {
|
||||||
fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
|
fn cmp(&self, cursor_location: &ExcerptSummary, _: &()) -> cmp::Ordering {
|
||||||
Ord::cmp(self, &cursor_location.text.bytes)
|
Ord::cmp(self, &cursor_location.text.len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2852,6 +2952,12 @@ impl<'a> sum_tree::SeekTarget<'a, ExcerptSummary, ExcerptSummary> for Option<&'a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for OffsetUtf16 {
|
||||||
|
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||||
|
*self += summary.text.len_utf16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
|
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
|
||||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||||
*self += summary.text.lines;
|
*self += summary.text.lines;
|
||||||
|
@ -2860,7 +2966,7 @@ impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for Point {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
|
impl<'a> sum_tree::Dimension<'a, ExcerptSummary> for PointUtf16 {
|
||||||
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ExcerptSummary, _: &()) {
|
||||||
*self += summary.text.lines_utf16
|
*self += summary.text.lines_utf16()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3060,6 +3166,24 @@ impl ToOffset for usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToOffset for OffsetUtf16 {
|
||||||
|
fn to_offset<'a>(&self, snapshot: &MultiBufferSnapshot) -> usize {
|
||||||
|
snapshot.offset_utf16_to_offset(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for OffsetUtf16 {
|
||||||
|
fn to_offset_utf16(&self, _snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for usize {
|
||||||
|
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||||
|
snapshot.offset_to_offset_utf16(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToPoint for usize {
|
impl ToPoint for usize {
|
||||||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||||
snapshot.offset_to_point(*self)
|
snapshot.offset_to_point(*self)
|
||||||
|
@ -3823,11 +3947,11 @@ mod tests {
|
||||||
buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
|
buffer.text_summary_for_range::<PointUtf16, _>(0..buffer_range.start);
|
||||||
|
|
||||||
let excerpt_start = excerpt_starts.next().unwrap();
|
let excerpt_start = excerpt_starts.next().unwrap();
|
||||||
let mut offset = excerpt_start.bytes;
|
let mut offset = excerpt_start.len;
|
||||||
let mut buffer_offset = buffer_range.start;
|
let mut buffer_offset = buffer_range.start;
|
||||||
let mut point = excerpt_start.lines;
|
let mut point = excerpt_start.lines;
|
||||||
let mut buffer_point = buffer_start_point;
|
let mut buffer_point = buffer_start_point;
|
||||||
let mut point_utf16 = excerpt_start.lines_utf16;
|
let mut point_utf16 = excerpt_start.lines_utf16();
|
||||||
let mut buffer_point_utf16 = buffer_start_point_utf16;
|
let mut buffer_point_utf16 = buffer_start_point_utf16;
|
||||||
for ch in buffer
|
for ch in buffer
|
||||||
.snapshot()
|
.snapshot()
|
||||||
|
@ -3841,7 +3965,7 @@ mod tests {
|
||||||
let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
|
let buffer_right_offset = buffer.clip_offset(buffer_offset, Bias::Right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
left_offset,
|
left_offset,
|
||||||
excerpt_start.bytes + (buffer_left_offset - buffer_range.start),
|
excerpt_start.len + (buffer_left_offset - buffer_range.start),
|
||||||
"clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
|
"clip_offset({:?}, Left). buffer: {:?}, buffer offset: {:?}",
|
||||||
offset,
|
offset,
|
||||||
buffer_id,
|
buffer_id,
|
||||||
|
@ -3849,7 +3973,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
right_offset,
|
right_offset,
|
||||||
excerpt_start.bytes + (buffer_right_offset - buffer_range.start),
|
excerpt_start.len + (buffer_right_offset - buffer_range.start),
|
||||||
"clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
|
"clip_offset({:?}, Right). buffer: {:?}, buffer offset: {:?}",
|
||||||
offset,
|
offset,
|
||||||
buffer_id,
|
buffer_id,
|
||||||
|
@ -3910,7 +4034,7 @@ mod tests {
|
||||||
buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
|
buffer.clip_point_utf16(buffer_point_utf16, Bias::Right);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
left_point_utf16,
|
left_point_utf16,
|
||||||
excerpt_start.lines_utf16
|
excerpt_start.lines_utf16()
|
||||||
+ (buffer_left_point_utf16 - buffer_start_point_utf16),
|
+ (buffer_left_point_utf16 - buffer_start_point_utf16),
|
||||||
"clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
|
"clip_point_utf16({:?}, Left). buffer: {:?}, buffer point_utf16: {:?}",
|
||||||
point_utf16,
|
point_utf16,
|
||||||
|
@ -3919,7 +4043,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
right_point_utf16,
|
right_point_utf16,
|
||||||
excerpt_start.lines_utf16
|
excerpt_start.lines_utf16()
|
||||||
+ (buffer_right_point_utf16 - buffer_start_point_utf16),
|
+ (buffer_right_point_utf16 - buffer_start_point_utf16),
|
||||||
"clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
|
"clip_point_utf16({:?}, Right). buffer: {:?}, buffer point_utf16: {:?}",
|
||||||
point_utf16,
|
point_utf16,
|
||||||
|
@ -4069,7 +4193,7 @@ mod tests {
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
|
|
||||||
multibuffer.update(cx, |multibuffer, cx| {
|
multibuffer.update(cx, |multibuffer, cx| {
|
||||||
multibuffer.start_transaction_at(now, cx);
|
let transaction_1 = multibuffer.start_transaction_at(now, cx).unwrap();
|
||||||
multibuffer.edit(
|
multibuffer.edit(
|
||||||
[
|
[
|
||||||
(Point::new(0, 0)..Point::new(0, 0), "A"),
|
(Point::new(0, 0)..Point::new(0, 0), "A"),
|
||||||
|
@ -4152,6 +4276,16 @@ mod tests {
|
||||||
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
assert_eq!(multibuffer.read(cx).text(), "ABCD1234\nAB5678");
|
||||||
multibuffer.undo(cx);
|
multibuffer.undo(cx);
|
||||||
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
||||||
|
|
||||||
|
// Transactions can be grouped manually.
|
||||||
|
multibuffer.redo(cx);
|
||||||
|
multibuffer.redo(cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
||||||
|
multibuffer.group_until_transaction(transaction_1, cx);
|
||||||
|
multibuffer.undo(cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "1234\n5678");
|
||||||
|
multibuffer.redo(cx);
|
||||||
|
assert_eq!(multibuffer.read(cx).text(), "XABCD1234\nAB5678");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToPoint};
|
use super::{ExcerptId, MultiBufferSnapshot, ToOffset, ToOffsetUtf16, ToPoint};
|
||||||
use std::{
|
use std::{
|
||||||
cmp::Ordering,
|
cmp::Ordering,
|
||||||
ops::{Range, Sub},
|
ops::{Range, Sub},
|
||||||
};
|
};
|
||||||
use sum_tree::Bias;
|
use sum_tree::Bias;
|
||||||
use text::{rope::TextDimension, Point};
|
use text::{rope::TextDimension, OffsetUtf16, Point};
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
|
||||||
pub struct Anchor {
|
pub struct Anchor {
|
||||||
|
@ -89,6 +89,12 @@ impl ToOffset for Anchor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for Anchor {
|
||||||
|
fn to_offset_utf16(&self, snapshot: &MultiBufferSnapshot) -> OffsetUtf16 {
|
||||||
|
self.summary(snapshot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToPoint for Anchor {
|
impl ToPoint for Anchor {
|
||||||
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
fn to_point<'a>(&self, snapshot: &MultiBufferSnapshot) -> Point {
|
||||||
self.summary(snapshot)
|
self.summary(snapshot)
|
||||||
|
|
|
@ -181,13 +181,7 @@ impl<'a> EditorTestContext<'a> {
|
||||||
|
|
||||||
pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
|
pub fn simulate_keystroke(&mut self, keystroke_text: &str) {
|
||||||
let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
let keystroke = Keystroke::parse(keystroke_text).unwrap();
|
||||||
let input = if keystroke.modified() {
|
self.cx.dispatch_keystroke(self.window_id, keystroke, false);
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(keystroke.key.clone())
|
|
||||||
};
|
|
||||||
self.cx
|
|
||||||
.dispatch_keystroke(self.window_id, keystroke, input, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
|
pub fn simulate_keystrokes<const COUNT: usize>(&mut self, keystroke_texts: [&str; COUNT]) {
|
||||||
|
|
|
@ -279,7 +279,7 @@ impl PickerDelegate for FileFinder {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use editor::{Editor, Input};
|
use editor::Editor;
|
||||||
use menu::{Confirm, SelectNext};
|
use menu::{Confirm, SelectNext};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use workspace::{AppState, Workspace};
|
use workspace::{AppState, Workspace};
|
||||||
|
@ -318,12 +318,14 @@ mod tests {
|
||||||
cx.dispatch_action(window_id, Toggle);
|
cx.dispatch_action(window_id, Toggle);
|
||||||
|
|
||||||
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
|
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
|
||||||
cx.dispatch_action(window_id, Input("b".into()));
|
|
||||||
cx.dispatch_action(window_id, Input("n".into()));
|
|
||||||
cx.dispatch_action(window_id, Input("a".into()));
|
|
||||||
finder
|
finder
|
||||||
.condition(&cx, |finder, _| finder.matches.len() == 2)
|
.update(cx, |finder, cx| {
|
||||||
|
finder.update_matches("bna".to_string(), cx)
|
||||||
|
})
|
||||||
.await;
|
.await;
|
||||||
|
finder.read_with(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.matches.len(), 2);
|
||||||
|
});
|
||||||
|
|
||||||
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
let active_pane = cx.read(|cx| workspace.read(cx).active_pane().clone());
|
||||||
cx.dispatch_action(window_id, SelectNext);
|
cx.dispatch_action(window_id, SelectNext);
|
||||||
|
|
|
@ -2,11 +2,12 @@ use gpui::{
|
||||||
color::Color,
|
color::Color,
|
||||||
fonts::{Properties, Weight},
|
fonts::{Properties, Weight},
|
||||||
text_layout::RunStyle,
|
text_layout::RunStyle,
|
||||||
DebugContext, Element as _, Quad,
|
DebugContext, Element as _, MeasurementContext, Quad,
|
||||||
};
|
};
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use pathfinder_geometry::rect::RectF;
|
use pathfinder_geometry::rect::RectF;
|
||||||
use simplelog::SimpleLogger;
|
use simplelog::SimpleLogger;
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||||
|
@ -112,6 +113,18 @@ impl gpui::Element for TextElement {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -3,12 +3,13 @@ pub mod action;
|
||||||
use crate::{
|
use crate::{
|
||||||
elements::ElementBox,
|
elements::ElementBox,
|
||||||
executor::{self, Task},
|
executor::{self, Task},
|
||||||
|
geometry::rect::RectF,
|
||||||
keymap::{self, Binding, Keystroke},
|
keymap::{self, Binding, Keystroke},
|
||||||
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
|
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
|
||||||
presenter::Presenter,
|
presenter::Presenter,
|
||||||
util::post_inc,
|
util::post_inc,
|
||||||
AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, PathPromptOptions,
|
AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseRegionId,
|
||||||
TextLayoutCache,
|
PathPromptOptions, TextLayoutCache,
|
||||||
};
|
};
|
||||||
pub use action::*;
|
pub use action::*;
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
@ -28,7 +29,7 @@ use std::{
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
mem,
|
mem,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
rc::{self, Rc},
|
rc::{self, Rc},
|
||||||
|
@ -64,6 +65,32 @@ pub trait View: Entity + Sized {
|
||||||
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
|
fn debug_json(&self, _: &AppContext) -> serde_json::Value {
|
||||||
serde_json::Value::Null
|
serde_json::Value::Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_for_range(&self, _: Range<usize>, _: &AppContext) -> Option<String> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn selected_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn marked_text_range(&self, _: &AppContext) -> Option<Range<usize>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn unmark_text(&mut self, _: &mut ViewContext<Self>) {}
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
_: Option<Range<usize>>,
|
||||||
|
_: &str,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
_: Option<Range<usize>>,
|
||||||
|
_: &str,
|
||||||
|
_: Option<Range<usize>>,
|
||||||
|
_: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ReadModel {
|
pub trait ReadModel {
|
||||||
|
@ -154,6 +181,11 @@ pub struct TestAppContext {
|
||||||
condition_duration: Option<Duration>,
|
condition_duration: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct WindowInputHandler {
|
||||||
|
app: Rc<RefCell<MutableAppContext>>,
|
||||||
|
window_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(asset_source: impl AssetSource) -> Result<Self> {
|
pub fn new(asset_source: impl AssetSource) -> Result<Self> {
|
||||||
let platform = platform::current::platform();
|
let platform = platform::current::platform();
|
||||||
|
@ -310,6 +342,87 @@ impl App {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl WindowInputHandler {
|
||||||
|
fn read_focused_view<T, F>(&self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&dyn AnyView, &AppContext) -> T,
|
||||||
|
{
|
||||||
|
let app = self.app.borrow();
|
||||||
|
let view_id = app.focused_view_id(self.window_id)?;
|
||||||
|
let view = app.cx.views.get(&(self.window_id, view_id))?;
|
||||||
|
let result = f(view.as_ref(), &app);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_focused_view<T, F>(&mut self, f: F) -> Option<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(usize, usize, &mut dyn AnyView, &mut MutableAppContext) -> T,
|
||||||
|
{
|
||||||
|
let mut app = self.app.borrow_mut();
|
||||||
|
app.update(|app| {
|
||||||
|
let view_id = app.focused_view_id(self.window_id)?;
|
||||||
|
let mut view = app.cx.views.remove(&(self.window_id, view_id))?;
|
||||||
|
let result = f(self.window_id, view_id, view.as_mut(), &mut *app);
|
||||||
|
app.cx.views.insert((self.window_id, view_id), view);
|
||||||
|
Some(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputHandler for WindowInputHandler {
|
||||||
|
fn text_for_range(&self, range: Range<usize>) -> Option<String> {
|
||||||
|
self.read_focused_view(|view, cx| view.text_for_range(range.clone(), cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(&self) -> Option<Range<usize>> {
|
||||||
|
self.read_focused_view(|view, cx| view.selected_text_range(cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(&mut self, range: Option<Range<usize>>, text: &str) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.replace_text_in_range(range, text, cx, window_id, view_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marked_text_range(&self) -> Option<Range<usize>> {
|
||||||
|
self.read_focused_view(|view, cx| view.marked_text_range(cx))
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmark_text(&mut self) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.unmark_text(cx, window_id, view_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
) {
|
||||||
|
self.update_focused_view(|window_id, view_id, view, cx| {
|
||||||
|
view.replace_and_mark_text_in_range(
|
||||||
|
range,
|
||||||
|
new_text,
|
||||||
|
new_selected_range,
|
||||||
|
cx,
|
||||||
|
window_id,
|
||||||
|
view_id,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
|
||||||
|
let app = self.app.borrow();
|
||||||
|
let (presenter, _) = app.presenters_and_platform_windows.get(&self.window_id)?;
|
||||||
|
let presenter = presenter.borrow();
|
||||||
|
presenter.rect_for_text_range(range_utf16, &app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
impl TestAppContext {
|
impl TestAppContext {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
@ -361,14 +474,8 @@ impl TestAppContext {
|
||||||
self.cx.borrow_mut().dispatch_global_action(action);
|
self.cx.borrow_mut().dispatch_global_action(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch_keystroke(
|
pub fn dispatch_keystroke(&mut self, window_id: usize, keystroke: Keystroke, is_held: bool) {
|
||||||
&mut self,
|
let handled = self.cx.borrow_mut().update(|cx| {
|
||||||
window_id: usize,
|
|
||||||
keystroke: Keystroke,
|
|
||||||
input: Option<String>,
|
|
||||||
is_held: bool,
|
|
||||||
) {
|
|
||||||
self.cx.borrow_mut().update(|cx| {
|
|
||||||
let presenter = cx
|
let presenter = cx
|
||||||
.presenters_and_platform_windows
|
.presenters_and_platform_windows
|
||||||
.get(&window_id)
|
.get(&window_id)
|
||||||
|
@ -377,17 +484,29 @@ impl TestAppContext {
|
||||||
.clone();
|
.clone();
|
||||||
let dispatch_path = presenter.borrow().dispatch_path(cx.as_ref());
|
let dispatch_path = presenter.borrow().dispatch_path(cx.as_ref());
|
||||||
|
|
||||||
if !cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) {
|
if cx.dispatch_keystroke(window_id, dispatch_path, &keystroke) {
|
||||||
presenter.borrow_mut().dispatch_event(
|
return true;
|
||||||
|
}
|
||||||
|
if presenter.borrow_mut().dispatch_event(
|
||||||
Event::KeyDown(KeyDownEvent {
|
Event::KeyDown(KeyDownEvent {
|
||||||
keystroke,
|
keystroke: keystroke.clone(),
|
||||||
input,
|
|
||||||
is_held,
|
is_held,
|
||||||
}),
|
}),
|
||||||
cx,
|
cx,
|
||||||
);
|
) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if !handled && !keystroke.cmd && !keystroke.ctrl {
|
||||||
|
WindowInputHandler {
|
||||||
|
app: self.cx.clone(),
|
||||||
|
window_id,
|
||||||
|
}
|
||||||
|
.replace_text_in_range(None, &keystroke.key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
|
pub fn add_model<T, F>(&mut self, build_model: F) -> ModelHandle<T>
|
||||||
|
@ -1195,6 +1314,11 @@ impl MutableAppContext {
|
||||||
.set_menus(menus, &self.keystroke_matcher);
|
.set_menus(menus, &self.keystroke_matcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_character_palette(&self, window_id: usize) {
|
||||||
|
let (_, window) = &self.presenters_and_platform_windows[&window_id];
|
||||||
|
window.show_character_palette();
|
||||||
|
}
|
||||||
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&self,
|
&self,
|
||||||
window_id: usize,
|
window_id: usize,
|
||||||
|
@ -1883,6 +2007,11 @@ impl MutableAppContext {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.set_input_handler(Box::new(WindowInputHandler {
|
||||||
|
app: self.upgrade().0,
|
||||||
|
window_id,
|
||||||
|
}));
|
||||||
|
|
||||||
let scene =
|
let scene =
|
||||||
presenter
|
presenter
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
@ -3174,6 +3303,28 @@ pub trait AnyView {
|
||||||
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
fn on_blur(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
||||||
fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
|
fn keymap_context(&self, cx: &AppContext) -> keymap::Context;
|
||||||
fn debug_json(&self, cx: &AppContext) -> serde_json::Value;
|
fn debug_json(&self, cx: &AppContext) -> serde_json::Value;
|
||||||
|
|
||||||
|
fn text_for_range(&self, range: Range<usize>, cx: &AppContext) -> Option<String>;
|
||||||
|
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>>;
|
||||||
|
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>>;
|
||||||
|
fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize);
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
text: &str,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
window_id: usize,
|
||||||
|
view_id: usize,
|
||||||
|
);
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
window_id: usize,
|
||||||
|
view_id: usize,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AnyView for T
|
impl<T> AnyView for T
|
||||||
|
@ -3224,6 +3375,48 @@ where
|
||||||
fn debug_json(&self, cx: &AppContext) -> serde_json::Value {
|
fn debug_json(&self, cx: &AppContext) -> serde_json::Value {
|
||||||
View::debug_json(self, cx)
|
View::debug_json(self, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn text_for_range(&self, range: Range<usize>, cx: &AppContext) -> Option<String> {
|
||||||
|
View::text_for_range(self, range, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
|
||||||
|
View::selected_text_range(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marked_text_range(&self, cx: &AppContext) -> Option<Range<usize>> {
|
||||||
|
View::marked_text_range(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unmark_text(&mut self, cx: &mut MutableAppContext, window_id: usize, view_id: usize) {
|
||||||
|
let mut cx = ViewContext::new(cx, window_id, view_id);
|
||||||
|
View::unmark_text(self, &mut cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
text: &str,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
window_id: usize,
|
||||||
|
view_id: usize,
|
||||||
|
) {
|
||||||
|
let mut cx = ViewContext::new(cx, window_id, view_id);
|
||||||
|
View::replace_text_in_range(self, range, text, &mut cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
cx: &mut MutableAppContext,
|
||||||
|
window_id: usize,
|
||||||
|
view_id: usize,
|
||||||
|
) {
|
||||||
|
let mut cx = ViewContext::new(cx, window_id, view_id);
|
||||||
|
View::replace_and_mark_text_in_range(self, range, new_text, new_selected_range, &mut cx)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ModelContext<'a, T: ?Sized> {
|
pub struct ModelContext<'a, T: ?Sized> {
|
||||||
|
@ -3489,6 +3682,10 @@ impl<'a, T: View> ViewContext<'a, T> {
|
||||||
self.app.platform()
|
self.app.platform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn show_character_palette(&self) {
|
||||||
|
self.app.show_character_palette(self.window_id);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn prompt(
|
pub fn prompt(
|
||||||
&self,
|
&self,
|
||||||
level: PromptLevel,
|
level: PromptLevel,
|
||||||
|
|
|
@ -31,7 +31,9 @@ use crate::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json, Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext,
|
json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
|
Action, DebugContext, Event, EventContext, LayoutContext, PaintContext, RenderContext,
|
||||||
SizeConstraint, View,
|
SizeConstraint, View,
|
||||||
};
|
};
|
||||||
use core::panic;
|
use core::panic;
|
||||||
|
@ -41,7 +43,7 @@ use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
mem,
|
mem,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,6 +51,11 @@ trait AnyElement {
|
||||||
fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F;
|
fn layout(&mut self, constraint: SizeConstraint, cx: &mut LayoutContext) -> Vector2F;
|
||||||
fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext);
|
fn paint(&mut self, origin: Vector2F, visible_bounds: RectF, cx: &mut PaintContext);
|
||||||
fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool;
|
fn dispatch_event(&mut self, event: &Event, cx: &mut EventContext) -> bool;
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF>;
|
||||||
fn debug(&self, cx: &DebugContext) -> serde_json::Value;
|
fn debug(&self, cx: &DebugContext) -> serde_json::Value;
|
||||||
|
|
||||||
fn size(&self) -> Vector2F;
|
fn size(&self) -> Vector2F;
|
||||||
|
@ -83,6 +90,16 @@ pub trait Element {
|
||||||
cx: &mut EventContext,
|
cx: &mut EventContext,
|
||||||
) -> bool;
|
) -> bool;
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
bounds: RectF,
|
||||||
|
visible_bounds: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
paint: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF>;
|
||||||
|
|
||||||
fn metadata(&self) -> Option<&dyn Any> {
|
fn metadata(&self) -> Option<&dyn Any> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -287,6 +304,26 @@ impl<T: Element> AnyElement for Lifecycle<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
if let Lifecycle::PostPaint {
|
||||||
|
element,
|
||||||
|
bounds,
|
||||||
|
visible_bounds,
|
||||||
|
layout,
|
||||||
|
paint,
|
||||||
|
..
|
||||||
|
} = self
|
||||||
|
{
|
||||||
|
element.rect_for_text_range(range_utf16, *bounds, *visible_bounds, layout, paint, cx)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn size(&self) -> Vector2F {
|
fn size(&self) -> Vector2F {
|
||||||
match self {
|
match self {
|
||||||
Lifecycle::Empty | Lifecycle::Init { .. } => panic!("invalid element lifecycle state"),
|
Lifecycle::Empty | Lifecycle::Init { .. } => panic!("invalid element lifecycle state"),
|
||||||
|
@ -385,6 +422,14 @@ impl ElementRc {
|
||||||
self.element.borrow_mut().dispatch_event(event, cx)
|
self.element.borrow_mut().dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.element.borrow().rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> Vector2F {
|
pub fn size(&self) -> Vector2F {
|
||||||
self.element.borrow().size()
|
self.element.borrow().size()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
|
@ -94,6 +96,18 @@ impl Element for Align {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: std::ops::Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: pathfinder_geometry::rect::RectF,
|
bounds: pathfinder_geometry::rect::RectF,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use super::Element;
|
use super::Element;
|
||||||
use crate::{
|
use crate::{
|
||||||
json::{self, json},
|
json::{self, json},
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext, PaintContext,
|
DebugContext, PaintContext,
|
||||||
};
|
};
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
|
@ -67,6 +68,18 @@ where
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: std::ops::Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,6 +169,18 @@ impl Element for ConstrainedBox {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
color::Color,
|
color::Color,
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -7,6 +9,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
json::ToJson,
|
json::ToJson,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
|
presenter::MeasurementContext,
|
||||||
scene::{self, Border, CursorRegion, Quad},
|
scene::{self, Border, CursorRegion, Quad},
|
||||||
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -271,6 +274,18 @@ impl Element for Container {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{
|
geometry::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::{json, ToJson},
|
json::{json, ToJson},
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext,
|
DebugContext,
|
||||||
};
|
};
|
||||||
use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
|
use crate::{Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint};
|
||||||
|
@ -67,6 +70,18 @@ impl Element for Empty {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::vector::Vector2F, CursorRegion, DebugContext, Element, ElementBox, Event,
|
geometry::vector::Vector2F, presenter::MeasurementContext, CursorRegion, DebugContext, Element,
|
||||||
EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion, NavigationDirection,
|
ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion,
|
||||||
PaintContext, SizeConstraint,
|
NavigationDirection, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
use pathfinder_geometry::rect::RectF;
|
use pathfinder_geometry::rect::RectF;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::any::TypeId;
|
use std::{any::TypeId, ops::Range};
|
||||||
|
|
||||||
pub struct EventHandler {
|
pub struct EventHandler {
|
||||||
child: ElementBox,
|
child: ElementBox,
|
||||||
|
@ -150,6 +150,18 @@ impl Element for EventHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json, DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -74,6 +78,18 @@ impl Element for Expanded {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use std::{any::Any, f32::INFINITY};
|
use std::{any::Any, f32::INFINITY, ops::Range};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
json::{self, ToJson, Value},
|
json::{self, ToJson, Value},
|
||||||
|
presenter::MeasurementContext,
|
||||||
Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
|
Axis, DebugContext, Element, ElementBox, ElementStateHandle, Event, EventContext,
|
||||||
LayoutContext, MouseMovedEvent, PaintContext, RenderContext, ScrollWheelEvent, SizeConstraint,
|
LayoutContext, MouseMovedEvent, PaintContext, RenderContext, ScrollWheelEvent, SizeConstraint,
|
||||||
Vector2FExt, View,
|
Vector2FExt, View,
|
||||||
|
@ -334,6 +335,20 @@ impl Element for Flex {
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.children
|
||||||
|
.iter()
|
||||||
|
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
@ -417,6 +432,18 @@ impl Element for FlexItem {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> Option<&dyn Any> {
|
fn metadata(&self) -> Option<&dyn Any> {
|
||||||
Some(&self.metadata)
|
Some(&self.metadata)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::json,
|
json::json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -65,6 +68,18 @@ impl Element for Hook {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -5,11 +5,12 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::{json, ToJson},
|
json::{json, ToJson},
|
||||||
|
presenter::MeasurementContext,
|
||||||
scene, Border, DebugContext, Element, Event, EventContext, ImageData, LayoutContext,
|
scene, Border, DebugContext, Element, Event, EventContext, ImageData, LayoutContext,
|
||||||
PaintContext, SizeConstraint,
|
PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
data: Arc<ImageData>,
|
data: Arc<ImageData>,
|
||||||
|
@ -89,6 +90,18 @@ impl Element for Image {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -76,6 +76,18 @@ impl Element for KeystrokeLabel {
|
||||||
element.dispatch_event(event, cx)
|
element.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
fonts::TextStyle,
|
fonts::TextStyle,
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -5,6 +7,7 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::{ToJson, Value},
|
json::{ToJson, Value},
|
||||||
|
presenter::MeasurementContext,
|
||||||
text_layout::{Line, RunStyle},
|
text_layout::{Line, RunStyle},
|
||||||
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -174,6 +177,18 @@ impl Element for Label {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::json,
|
json::json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
|
DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext,
|
||||||
RenderContext, ScrollWheelEvent, SizeConstraint, View, ViewContext,
|
RenderContext, ScrollWheelEvent, SizeConstraint, View, ViewContext,
|
||||||
};
|
};
|
||||||
|
@ -328,6 +329,39 @@ impl Element for List {
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
bounds: RectF,
|
||||||
|
_: RectF,
|
||||||
|
scroll_top: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
let state = self.state.0.borrow();
|
||||||
|
let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
|
||||||
|
let mut cursor = state.items.cursor::<Count>();
|
||||||
|
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
|
||||||
|
while let Some(item) = cursor.item() {
|
||||||
|
if item_origin.y() > bounds.max_y() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let ListItem::Rendered(element) = item {
|
||||||
|
if let Some(rect) = element.rect_for_text_range(range_utf16.clone(), cx) {
|
||||||
|
return Some(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
item_origin.set_y(item_origin.y() + element.size().y());
|
||||||
|
cursor.next(&());
|
||||||
|
} else {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
@ -939,6 +973,18 @@ mod tests {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
|
fn debug(&self, _: RectF, _: &(), _: &(), _: &DebugContext) -> serde_json::Value {
|
||||||
self.id.into()
|
self.id.into()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use std::any::TypeId;
|
|
||||||
|
|
||||||
use super::Padding;
|
use super::Padding;
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -8,11 +6,12 @@ use crate::{
|
||||||
},
|
},
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
scene::{CursorRegion, HandlerSet},
|
scene::{CursorRegion, HandlerSet},
|
||||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton,
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
|
||||||
MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext, RenderContext,
|
MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext,
|
||||||
SizeConstraint, View,
|
RenderContext, SizeConstraint, View,
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::{any::TypeId, ops::Range};
|
||||||
|
|
||||||
pub struct MouseEventHandler {
|
pub struct MouseEventHandler {
|
||||||
child: ElementBox,
|
child: ElementBox,
|
||||||
|
@ -150,6 +149,18 @@ impl Element for MouseEventHandler {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::ToJson,
|
json::ToJson,
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseRegion,
|
||||||
PaintContext, SizeConstraint,
|
PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -126,6 +129,18 @@ impl Element for Overlay {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range_utf16, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
use std::ops::Range;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::{self, json, ToJson},
|
json::{self, json, ToJson},
|
||||||
|
presenter::MeasurementContext,
|
||||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
|
||||||
SizeConstraint,
|
SizeConstraint,
|
||||||
};
|
};
|
||||||
|
@ -64,6 +67,21 @@ impl Element for Stack {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.children
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), cx))
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, ops::Range};
|
||||||
|
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
rect::RectF,
|
rect::RectF,
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
|
presenter::MeasurementContext,
|
||||||
scene, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
scene, DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,6 +85,18 @@ impl Element for Svg {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::{ToJson, Value},
|
json::{ToJson, Value},
|
||||||
|
presenter::MeasurementContext,
|
||||||
text_layout::{Line, RunStyle, ShapedBoundary},
|
text_layout::{Line, RunStyle, ShapedBoundary},
|
||||||
DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext,
|
DebugContext, Element, Event, EventContext, FontCache, LayoutContext, PaintContext,
|
||||||
SizeConstraint, TextLayoutCache,
|
SizeConstraint, TextLayoutCache,
|
||||||
|
@ -81,7 +82,8 @@ impl Element for Text {
|
||||||
"Highlight out of text range. Text len: {}, Highlight range: {}..{}",
|
"Highlight out of text range. Text len: {}, Highlight range: {}..{}",
|
||||||
self.text.len(),
|
self.text.len(),
|
||||||
range.start,
|
range.start,
|
||||||
range.end);
|
range.end
|
||||||
|
);
|
||||||
result = None;
|
result = None;
|
||||||
}
|
}
|
||||||
} else if offset < self.text.len() {
|
} else if offset < self.text.len() {
|
||||||
|
@ -188,6 +190,18 @@ impl Element for Text {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -6,12 +6,14 @@ use crate::{
|
||||||
fonts::TextStyle,
|
fonts::TextStyle,
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::json,
|
json::json,
|
||||||
|
presenter::MeasurementContext,
|
||||||
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
|
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
|
||||||
SizeConstraint, Task, View,
|
SizeConstraint, Task, View,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
ops::Range,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
@ -196,6 +198,18 @@ impl Element for Tooltip {
|
||||||
self.child.dispatch_event(event, cx)
|
self.child.dispatch_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
self.child.rect_for_text_range(range, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
_: RectF,
|
_: RectF,
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
json::{self, json},
|
json::{self, json},
|
||||||
|
presenter::MeasurementContext,
|
||||||
ElementBox, RenderContext, ScrollWheelEvent, View,
|
ElementBox, RenderContext, ScrollWheelEvent, View,
|
||||||
};
|
};
|
||||||
use json::ToJson;
|
use json::ToJson;
|
||||||
|
@ -327,6 +328,21 @@ impl Element for UniformList {
|
||||||
handled
|
handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
layout
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.find_map(|child| child.rect_for_text_range(range.clone(), cx))
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -30,7 +30,8 @@ pub mod platform;
|
||||||
pub use gpui_macros::test;
|
pub use gpui_macros::test;
|
||||||
pub use platform::*;
|
pub use platform::*;
|
||||||
pub use presenter::{
|
pub use presenter::{
|
||||||
Axis, DebugContext, EventContext, LayoutContext, PaintContext, SizeConstraint, Vector2FExt,
|
Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
|
||||||
|
SizeConstraint, Vector2FExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use anyhow;
|
pub use anyhow;
|
||||||
|
|
|
@ -26,6 +26,7 @@ use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
@ -88,6 +89,21 @@ pub trait Dispatcher: Send + Sync {
|
||||||
fn run_on_main_thread(&self, task: Runnable);
|
fn run_on_main_thread(&self, task: Runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait InputHandler {
|
||||||
|
fn selected_text_range(&self) -> Option<Range<usize>>;
|
||||||
|
fn marked_text_range(&self) -> Option<Range<usize>>;
|
||||||
|
fn text_for_range(&self, range_utf16: Range<usize>) -> Option<String>;
|
||||||
|
fn replace_text_in_range(&mut self, replacement_range: Option<Range<usize>>, text: &str);
|
||||||
|
fn replace_and_mark_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
range_utf16: Option<Range<usize>>,
|
||||||
|
new_text: &str,
|
||||||
|
new_selected_range: Option<Range<usize>>,
|
||||||
|
);
|
||||||
|
fn unmark_text(&mut self);
|
||||||
|
fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF>;
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Window: WindowContext {
|
pub trait Window: WindowContext {
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
|
fn on_event(&mut self, callback: Box<dyn FnMut(Event) -> bool>);
|
||||||
|
@ -95,10 +111,12 @@ pub trait Window: WindowContext {
|
||||||
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
|
fn on_resize(&mut self, callback: Box<dyn FnMut()>);
|
||||||
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
|
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>);
|
||||||
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
fn on_close(&mut self, callback: Box<dyn FnOnce()>);
|
||||||
|
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>);
|
||||||
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
fn prompt(&self, level: PromptLevel, msg: &str, answers: &[&str]) -> oneshot::Receiver<usize>;
|
||||||
fn activate(&self);
|
fn activate(&self);
|
||||||
fn set_title(&mut self, title: &str);
|
fn set_title(&mut self, title: &str);
|
||||||
fn set_edited(&mut self, edited: bool);
|
fn set_edited(&mut self, edited: bool);
|
||||||
|
fn show_character_palette(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WindowContext {
|
pub trait WindowContext {
|
||||||
|
|
|
@ -3,14 +3,12 @@ use crate::{geometry::vector::Vector2F, keymap::Keystroke};
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KeyDownEvent {
|
pub struct KeyDownEvent {
|
||||||
pub keystroke: Keystroke,
|
pub keystroke: Keystroke,
|
||||||
pub input: Option<String>,
|
|
||||||
pub is_held: bool,
|
pub is_held: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KeyUpEvent {
|
pub struct KeyUpEvent {
|
||||||
pub keystroke: Keystroke,
|
pub keystroke: Keystroke,
|
||||||
pub input: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
@ -10,12 +10,26 @@ use cocoa::{
|
||||||
base::{id, YES},
|
base::{id, YES},
|
||||||
foundation::NSString as _,
|
foundation::NSString as _,
|
||||||
};
|
};
|
||||||
|
use core_graphics::{
|
||||||
|
event::{CGEvent, CGEventFlags, CGKeyCode},
|
||||||
|
event_source::{CGEventSource, CGEventSourceStateID},
|
||||||
|
};
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use std::{borrow::Cow, ffi::CStr, os::raw::c_char};
|
use std::{borrow::Cow, ffi::CStr, os::raw::c_char};
|
||||||
|
|
||||||
|
const BACKSPACE_KEY: u16 = 0x7f;
|
||||||
|
const SPACE_KEY: u16 = b' ' as u16;
|
||||||
|
const ENTER_KEY: u16 = 0x0d;
|
||||||
|
const NUMPAD_ENTER_KEY: u16 = 0x03;
|
||||||
|
const ESCAPE_KEY: u16 = 0x1b;
|
||||||
|
const TAB_KEY: u16 = 0x09;
|
||||||
|
const SHIFT_TAB_KEY: u16 = 0x19;
|
||||||
|
|
||||||
pub fn key_to_native(key: &str) -> Cow<str> {
|
pub fn key_to_native(key: &str) -> Cow<str> {
|
||||||
use cocoa::appkit::*;
|
use cocoa::appkit::*;
|
||||||
let code = match key {
|
let code = match key {
|
||||||
"backspace" => 0x7F,
|
"space" => SPACE_KEY,
|
||||||
|
"backspace" => BACKSPACE_KEY,
|
||||||
"up" => NSUpArrowFunctionKey,
|
"up" => NSUpArrowFunctionKey,
|
||||||
"down" => NSDownArrowFunctionKey,
|
"down" => NSDownArrowFunctionKey,
|
||||||
"left" => NSLeftArrowFunctionKey,
|
"left" => NSLeftArrowFunctionKey,
|
||||||
|
@ -68,49 +82,13 @@ impl Event {
|
||||||
cmd,
|
cmd,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
NSEventType::NSKeyDown => {
|
NSEventType::NSKeyDown => Some(Self::KeyDown(KeyDownEvent {
|
||||||
let modifiers = native_event.modifierFlags();
|
keystroke: parse_keystroke(native_event),
|
||||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
|
||||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
|
||||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
|
||||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
|
||||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
|
||||||
|
|
||||||
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
|
||||||
|
|
||||||
Some(Self::KeyDown(KeyDownEvent {
|
|
||||||
keystroke: Keystroke {
|
|
||||||
ctrl,
|
|
||||||
alt,
|
|
||||||
shift,
|
|
||||||
cmd,
|
|
||||||
key: unmodified_chars.into(),
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
is_held: native_event.isARepeat() == YES,
|
is_held: native_event.isARepeat() == YES,
|
||||||
}))
|
})),
|
||||||
}
|
NSEventType::NSKeyUp => Some(Self::KeyUp(KeyUpEvent {
|
||||||
NSEventType::NSKeyUp => {
|
keystroke: parse_keystroke(native_event),
|
||||||
let modifiers = native_event.modifierFlags();
|
})),
|
||||||
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
|
||||||
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
|
||||||
let shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
|
||||||
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
|
||||||
let function = modifiers.contains(NSEventModifierFlags::NSFunctionKeyMask);
|
|
||||||
|
|
||||||
let (unmodified_chars, input) = get_key_text(native_event, cmd, ctrl, function)?;
|
|
||||||
|
|
||||||
Some(Self::KeyUp(KeyUpEvent {
|
|
||||||
keystroke: Keystroke {
|
|
||||||
ctrl,
|
|
||||||
alt,
|
|
||||||
shift,
|
|
||||||
cmd,
|
|
||||||
key: unmodified_chars.into(),
|
|
||||||
},
|
|
||||||
input,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
NSEventType::NSLeftMouseDown
|
NSEventType::NSLeftMouseDown
|
||||||
| NSEventType::NSRightMouseDown
|
| NSEventType::NSRightMouseDown
|
||||||
| NSEventType::NSOtherMouseDown => {
|
| NSEventType::NSOtherMouseDown => {
|
||||||
|
@ -229,72 +207,109 @@ impl Event {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn get_key_text(
|
unsafe fn parse_keystroke(native_event: id) -> Keystroke {
|
||||||
native_event: id,
|
use cocoa::appkit::*;
|
||||||
cmd: bool,
|
|
||||||
ctrl: bool,
|
let modifiers = native_event.modifierFlags();
|
||||||
function: bool,
|
let ctrl = modifiers.contains(NSEventModifierFlags::NSControlKeyMask);
|
||||||
) -> Option<(&'static str, Option<String>)> {
|
let alt = modifiers.contains(NSEventModifierFlags::NSAlternateKeyMask);
|
||||||
let unmodified_chars =
|
let mut shift = modifiers.contains(NSEventModifierFlags::NSShiftKeyMask);
|
||||||
|
let cmd = modifiers.contains(NSEventModifierFlags::NSCommandKeyMask);
|
||||||
|
|
||||||
|
let mut chars_ignoring_modifiers =
|
||||||
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
|
CStr::from_ptr(native_event.charactersIgnoringModifiers().UTF8String() as *mut c_char)
|
||||||
.to_str()
|
.to_str()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut input = None;
|
|
||||||
let first_char = unmodified_chars.chars().next()?;
|
|
||||||
use cocoa::appkit::*;
|
|
||||||
const BACKSPACE_KEY: u16 = 0x7f;
|
|
||||||
const ENTER_KEY: u16 = 0x0d;
|
|
||||||
const NUMPAD_ENTER_KEY: u16 = 0x03;
|
|
||||||
const ESCAPE_KEY: u16 = 0x1b;
|
|
||||||
const TAB_KEY: u16 = 0x09;
|
|
||||||
const SHIFT_TAB_KEY: u16 = 0x19;
|
|
||||||
const SPACE_KEY: u16 = b' ' as u16;
|
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
let unmodified_chars = match first_char as u16 {
|
let key = match chars_ignoring_modifiers.chars().next().map(|ch| ch as u16) {
|
||||||
SPACE_KEY => {
|
Some(SPACE_KEY) => "space",
|
||||||
input = Some(" ".to_string());
|
Some(BACKSPACE_KEY) => "backspace",
|
||||||
"space"
|
Some(ENTER_KEY) | Some(NUMPAD_ENTER_KEY) => "enter",
|
||||||
}
|
Some(ESCAPE_KEY) => "escape",
|
||||||
BACKSPACE_KEY => "backspace",
|
Some(TAB_KEY) => "tab",
|
||||||
ENTER_KEY | NUMPAD_ENTER_KEY => "enter",
|
Some(SHIFT_TAB_KEY) => "tab",
|
||||||
ESCAPE_KEY => "escape",
|
Some(NSUpArrowFunctionKey) => "up",
|
||||||
TAB_KEY => "tab",
|
Some(NSDownArrowFunctionKey) => "down",
|
||||||
SHIFT_TAB_KEY => "tab",
|
Some(NSLeftArrowFunctionKey) => "left",
|
||||||
|
Some(NSRightArrowFunctionKey) => "right",
|
||||||
NSUpArrowFunctionKey => "up",
|
Some(NSPageUpFunctionKey) => "pageup",
|
||||||
NSDownArrowFunctionKey => "down",
|
Some(NSPageDownFunctionKey) => "pagedown",
|
||||||
NSLeftArrowFunctionKey => "left",
|
Some(NSDeleteFunctionKey) => "delete",
|
||||||
NSRightArrowFunctionKey => "right",
|
Some(NSF1FunctionKey) => "f1",
|
||||||
NSPageUpFunctionKey => "pageup",
|
Some(NSF2FunctionKey) => "f2",
|
||||||
NSPageDownFunctionKey => "pagedown",
|
Some(NSF3FunctionKey) => "f3",
|
||||||
NSDeleteFunctionKey => "delete",
|
Some(NSF4FunctionKey) => "f4",
|
||||||
NSF1FunctionKey => "f1",
|
Some(NSF5FunctionKey) => "f5",
|
||||||
NSF2FunctionKey => "f2",
|
Some(NSF6FunctionKey) => "f6",
|
||||||
NSF3FunctionKey => "f3",
|
Some(NSF7FunctionKey) => "f7",
|
||||||
NSF4FunctionKey => "f4",
|
Some(NSF8FunctionKey) => "f8",
|
||||||
NSF5FunctionKey => "f5",
|
Some(NSF9FunctionKey) => "f9",
|
||||||
NSF6FunctionKey => "f6",
|
Some(NSF10FunctionKey) => "f10",
|
||||||
NSF7FunctionKey => "f7",
|
Some(NSF11FunctionKey) => "f11",
|
||||||
NSF8FunctionKey => "f8",
|
Some(NSF12FunctionKey) => "f12",
|
||||||
NSF9FunctionKey => "f9",
|
|
||||||
NSF10FunctionKey => "f10",
|
|
||||||
NSF11FunctionKey => "f11",
|
|
||||||
NSF12FunctionKey => "f12",
|
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if !cmd && !ctrl && !function {
|
let mut chars_ignoring_modifiers_and_shift =
|
||||||
input = Some(
|
chars_for_modified_key(native_event.keyCode(), false, false);
|
||||||
CStr::from_ptr(native_event.characters().UTF8String() as *mut c_char)
|
|
||||||
.to_str()
|
// Honor ⌘ when Dvorak-QWERTY is used.
|
||||||
.unwrap()
|
let chars_with_cmd = chars_for_modified_key(native_event.keyCode(), true, false);
|
||||||
.into(),
|
if cmd && chars_ignoring_modifiers_and_shift != chars_with_cmd {
|
||||||
);
|
chars_ignoring_modifiers =
|
||||||
|
chars_for_modified_key(native_event.keyCode(), true, shift);
|
||||||
|
chars_ignoring_modifiers_and_shift = chars_with_cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if shift {
|
||||||
|
if chars_ignoring_modifiers_and_shift
|
||||||
|
== chars_ignoring_modifiers.to_ascii_lowercase()
|
||||||
|
{
|
||||||
|
chars_ignoring_modifiers_and_shift
|
||||||
|
} else if chars_ignoring_modifiers_and_shift != chars_ignoring_modifiers {
|
||||||
|
shift = false;
|
||||||
|
chars_ignoring_modifiers
|
||||||
|
} else {
|
||||||
|
chars_ignoring_modifiers
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
chars_ignoring_modifiers
|
||||||
}
|
}
|
||||||
unmodified_chars
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Some((unmodified_chars, input))
|
Keystroke {
|
||||||
|
ctrl,
|
||||||
|
alt,
|
||||||
|
shift,
|
||||||
|
cmd,
|
||||||
|
key: key.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chars_for_modified_key<'a>(code: CGKeyCode, cmd: bool, shift: bool) -> &'a str {
|
||||||
|
// Ideally, we would use `[NSEvent charactersByApplyingModifiers]` but that
|
||||||
|
// always returns an empty string with certain keyboards, e.g. Japanese. Synthesizing
|
||||||
|
// an event with the given flags instead lets us access `characters`, which always
|
||||||
|
// returns a valid string.
|
||||||
|
let event = CGEvent::new_keyboard_event(
|
||||||
|
CGEventSource::new(CGEventSourceStateID::Private).unwrap(),
|
||||||
|
code,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let mut flags = CGEventFlags::empty();
|
||||||
|
if cmd {
|
||||||
|
flags |= CGEventFlags::CGEventFlagCommand;
|
||||||
|
}
|
||||||
|
if shift {
|
||||||
|
flags |= CGEventFlags::CGEventFlagShift;
|
||||||
|
}
|
||||||
|
event.set_flags(flags);
|
||||||
|
|
||||||
|
let event: id = unsafe { msg_send![class!(NSEvent), eventWithCGEvent: event] };
|
||||||
|
unsafe {
|
||||||
|
CStr::from_ptr(event.characters().UTF8String())
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use super::{geometry::RectFExt, renderer::Renderer};
|
||||||
use crate::{
|
use crate::{
|
||||||
executor,
|
executor,
|
||||||
geometry::{
|
geometry::{
|
||||||
|
@ -6,7 +7,8 @@ use crate::{
|
||||||
},
|
},
|
||||||
keymap::Keystroke,
|
keymap::Keystroke,
|
||||||
platform::{self, Event, WindowBounds, WindowContext},
|
platform::{self, Event, WindowBounds, WindowContext},
|
||||||
KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, Scene,
|
InputHandler, KeyDownEvent, ModifiersChangedEvent, MouseButton, MouseButtonEvent,
|
||||||
|
MouseMovedEvent, Scene,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -15,7 +17,9 @@ use cocoa::{
|
||||||
NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
|
NSViewHeightSizable, NSViewWidthSizable, NSWindow, NSWindowButton, NSWindowStyleMask,
|
||||||
},
|
},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSInteger, NSSize, NSString},
|
foundation::{
|
||||||
|
NSAutoreleasePool, NSInteger, NSNotFound, NSPoint, NSRect, NSSize, NSString, NSUInteger,
|
||||||
|
},
|
||||||
quartzcore::AutoresizingMask,
|
quartzcore::AutoresizingMask,
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGRect;
|
use core_graphics::display::CGRect;
|
||||||
|
@ -34,20 +38,71 @@ use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
convert::TryInto,
|
convert::TryInto,
|
||||||
ffi::c_void,
|
ffi::{c_void, CStr},
|
||||||
mem, ptr,
|
mem,
|
||||||
|
ops::Range,
|
||||||
|
os::raw::c_char,
|
||||||
|
ptr,
|
||||||
rc::{Rc, Weak},
|
rc::{Rc, Weak},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{geometry::RectFExt, renderer::Renderer};
|
|
||||||
|
|
||||||
const WINDOW_STATE_IVAR: &'static str = "windowState";
|
const WINDOW_STATE_IVAR: &'static str = "windowState";
|
||||||
|
|
||||||
static mut WINDOW_CLASS: *const Class = ptr::null();
|
static mut WINDOW_CLASS: *const Class = ptr::null();
|
||||||
static mut VIEW_CLASS: *const Class = ptr::null();
|
static mut VIEW_CLASS: *const Class = ptr::null();
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct NSRange {
|
||||||
|
pub location: NSUInteger,
|
||||||
|
pub length: NSUInteger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NSRange {
|
||||||
|
fn invalid() -> Self {
|
||||||
|
Self {
|
||||||
|
location: NSNotFound as NSUInteger,
|
||||||
|
length: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.location != NSNotFound as NSUInteger
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_range(&self) -> Option<Range<usize>> {
|
||||||
|
if self.is_valid() {
|
||||||
|
let start = self.location as usize;
|
||||||
|
let end = start + self.length as usize;
|
||||||
|
Some(start..end)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Range<usize>> for NSRange {
|
||||||
|
fn from(range: Range<usize>) -> Self {
|
||||||
|
NSRange {
|
||||||
|
location: range.start as NSUInteger,
|
||||||
|
length: range.len() as NSUInteger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl objc::Encode for NSRange {
|
||||||
|
fn encode() -> objc::Encoding {
|
||||||
|
let encoding = format!(
|
||||||
|
"{{NSRange={}{}}}",
|
||||||
|
NSUInteger::encode().as_str(),
|
||||||
|
NSUInteger::encode().as_str()
|
||||||
|
);
|
||||||
|
unsafe { objc::Encoding::from_str(&encoding) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
|
const NSViewLayerContentsRedrawDuringViewResize: NSInteger = 2;
|
||||||
|
|
||||||
|
@ -163,6 +218,48 @@ unsafe fn build_classes() {
|
||||||
display_layer as extern "C" fn(&Object, Sel, id),
|
display_layer as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
decl.add_protocol(Protocol::get("NSTextInputClient").unwrap());
|
||||||
|
decl.add_method(
|
||||||
|
sel!(validAttributesForMarkedText),
|
||||||
|
valid_attributes_for_marked_text as extern "C" fn(&Object, Sel) -> id,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(hasMarkedText),
|
||||||
|
has_marked_text as extern "C" fn(&Object, Sel) -> BOOL,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(markedRange),
|
||||||
|
marked_range as extern "C" fn(&Object, Sel) -> NSRange,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(selectedRange),
|
||||||
|
selected_range as extern "C" fn(&Object, Sel) -> NSRange,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(firstRectForCharacterRange:actualRange:),
|
||||||
|
first_rect_for_character_range as extern "C" fn(&Object, Sel, NSRange, id) -> NSRect,
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(insertText:replacementRange:),
|
||||||
|
insert_text as extern "C" fn(&Object, Sel, id, NSRange),
|
||||||
|
);
|
||||||
|
decl.add_method(
|
||||||
|
sel!(setMarkedText:selectedRange:replacementRange:),
|
||||||
|
set_marked_text as extern "C" fn(&Object, Sel, id, NSRange, NSRange),
|
||||||
|
);
|
||||||
|
decl.add_method(sel!(unmarkText), unmark_text as extern "C" fn(&Object, Sel));
|
||||||
|
decl.add_method(
|
||||||
|
sel!(attributedSubstringForProposedRange:actualRange:),
|
||||||
|
attributed_substring_for_proposed_range
|
||||||
|
as extern "C" fn(&Object, Sel, NSRange, *mut c_void) -> id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Suppress beep on keystrokes with modifier keys.
|
||||||
|
decl.add_method(
|
||||||
|
sel!(doCommandBySelector:),
|
||||||
|
do_command_by_selector as extern "C" fn(&Object, Sel, Sel),
|
||||||
|
);
|
||||||
|
|
||||||
decl.register()
|
decl.register()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -177,12 +274,14 @@ struct WindowState {
|
||||||
resize_callback: Option<Box<dyn FnMut()>>,
|
resize_callback: Option<Box<dyn FnMut()>>,
|
||||||
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
should_close_callback: Option<Box<dyn FnMut() -> bool>>,
|
||||||
close_callback: Option<Box<dyn FnOnce()>>,
|
close_callback: Option<Box<dyn FnOnce()>>,
|
||||||
|
input_handler: Option<Box<dyn InputHandler>>,
|
||||||
|
pending_key_down_event: Option<KeyDownEvent>,
|
||||||
synthetic_drag_counter: usize,
|
synthetic_drag_counter: usize,
|
||||||
executor: Rc<executor::Foreground>,
|
executor: Rc<executor::Foreground>,
|
||||||
scene_to_render: Option<Scene>,
|
scene_to_render: Option<Scene>,
|
||||||
renderer: Renderer,
|
renderer: Renderer,
|
||||||
command_queue: metal::CommandQueue,
|
command_queue: metal::CommandQueue,
|
||||||
last_fresh_keydown: Option<(Keystroke, Option<String>)>,
|
last_fresh_keydown: Option<Keystroke>,
|
||||||
layer: id,
|
layer: id,
|
||||||
traffic_light_position: Option<Vector2F>,
|
traffic_light_position: Option<Vector2F>,
|
||||||
previous_modifiers_changed_event: Option<Event>,
|
previous_modifiers_changed_event: Option<Event>,
|
||||||
|
@ -263,6 +362,8 @@ impl Window {
|
||||||
should_close_callback: None,
|
should_close_callback: None,
|
||||||
close_callback: None,
|
close_callback: None,
|
||||||
activate_callback: None,
|
activate_callback: None,
|
||||||
|
input_handler: None,
|
||||||
|
pending_key_down_event: None,
|
||||||
synthetic_drag_counter: 0,
|
synthetic_drag_counter: 0,
|
||||||
executor,
|
executor,
|
||||||
scene_to_render: Default::default(),
|
scene_to_render: Default::default(),
|
||||||
|
@ -371,6 +472,10 @@ impl platform::Window for Window {
|
||||||
self.0.as_ref().borrow_mut().activate_callback = Some(callback);
|
self.0.as_ref().borrow_mut().activate_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_input_handler(&mut self, input_handler: Box<dyn InputHandler>) {
|
||||||
|
self.0.as_ref().borrow_mut().input_handler = Some(input_handler);
|
||||||
|
}
|
||||||
|
|
||||||
fn prompt(
|
fn prompt(
|
||||||
&self,
|
&self,
|
||||||
level: platform::PromptLevel,
|
level: platform::PromptLevel,
|
||||||
|
@ -448,6 +553,14 @@ impl platform::Window for Window {
|
||||||
// so we have to move it again.
|
// so we have to move it again.
|
||||||
self.0.borrow().move_traffic_light();
|
self.0.borrow().move_traffic_light();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_character_palette(&self) {
|
||||||
|
unsafe {
|
||||||
|
let app = NSApplication::sharedApplication(nil);
|
||||||
|
let window = self.0.borrow().native_window;
|
||||||
|
let _: () = msg_send![app, orderFrontCharacterPalette: window];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl platform::WindowContext for Window {
|
impl platform::WindowContext for Window {
|
||||||
|
@ -581,38 +694,53 @@ extern "C" fn dealloc_view(this: &Object, _: Sel) {
|
||||||
|
|
||||||
extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
|
extern "C" fn handle_key_equivalent(this: &Object, _: Sel, native_event: id) -> BOOL {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
|
|
||||||
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
|
||||||
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
match &event {
|
window_state_borrow.pending_key_down_event = match event {
|
||||||
Event::KeyDown(KeyDownEvent {
|
Event::KeyDown(event) => {
|
||||||
keystroke,
|
let keydown = event.keystroke.clone();
|
||||||
input,
|
|
||||||
is_held,
|
|
||||||
}) => {
|
|
||||||
let keydown = (keystroke.clone(), input.clone());
|
|
||||||
// Ignore events from held-down keys after some of the initially-pressed keys
|
// Ignore events from held-down keys after some of the initially-pressed keys
|
||||||
// were released.
|
// were released.
|
||||||
if *is_held {
|
if event.is_held {
|
||||||
if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
|
if window_state_borrow.last_fresh_keydown.as_ref() != Some(&keydown) {
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
window_state_borrow.last_fresh_keydown = Some(keydown);
|
window_state_borrow.last_fresh_keydown = Some(keydown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(event)
|
||||||
}
|
}
|
||||||
_ => return NO,
|
_ => return NO,
|
||||||
|
};
|
||||||
|
drop(window_state_borrow);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let input_context: id = msg_send![this, inputContext];
|
||||||
|
let _: BOOL = msg_send![input_context, handleEvent: native_event];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut window_state_borrow = window_state.borrow_mut();
|
||||||
|
if let Some(event) = window_state_borrow.pending_key_down_event.take() {
|
||||||
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
||||||
drop(window_state_borrow);
|
drop(window_state_borrow);
|
||||||
let handled = callback(event);
|
|
||||||
window_state.borrow_mut().event_callback = Some(callback);
|
let is_composing =
|
||||||
handled as BOOL
|
with_input_handler(this, |input_handler| input_handler.marked_text_range())
|
||||||
} else {
|
.flatten()
|
||||||
NO
|
.is_some();
|
||||||
|
if !is_composing {
|
||||||
|
callback(Event::KeyDown(event));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window_state.borrow_mut().event_callback = Some(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
YES
|
||||||
} else {
|
} else {
|
||||||
NO
|
NO
|
||||||
}
|
}
|
||||||
|
@ -624,7 +752,6 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) {
|
||||||
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
|
||||||
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
let event = unsafe { Event::from_native(native_event, Some(window_state_borrow.size().y())) };
|
||||||
|
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
match &event {
|
match &event {
|
||||||
Event::MouseMoved(
|
Event::MouseMoved(
|
||||||
|
@ -691,21 +818,19 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
|
||||||
let window_state = unsafe { get_window_state(this) };
|
let window_state = unsafe { get_window_state(this) };
|
||||||
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
|
||||||
let chars = ".".to_string();
|
|
||||||
let keystroke = Keystroke {
|
let keystroke = Keystroke {
|
||||||
cmd: true,
|
cmd: true,
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
key: chars.clone(),
|
key: ".".into(),
|
||||||
};
|
};
|
||||||
let event = Event::KeyDown(KeyDownEvent {
|
let event = Event::KeyDown(KeyDownEvent {
|
||||||
keystroke: keystroke.clone(),
|
keystroke: keystroke.clone(),
|
||||||
input: Some(chars.clone()),
|
|
||||||
is_held: false,
|
is_held: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
window_state_borrow.last_fresh_keydown = Some((keystroke, Some(chars)));
|
window_state_borrow.last_fresh_keydown = Some(keystroke);
|
||||||
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
if let Some(mut callback) = window_state_borrow.event_callback.take() {
|
||||||
drop(window_state_borrow);
|
drop(window_state_borrow);
|
||||||
callback(event);
|
callback(event);
|
||||||
|
@ -866,6 +991,164 @@ extern "C" fn display_layer(this: &Object, _: Sel, _: id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn valid_attributes_for_marked_text(_: &Object, _: Sel) -> id {
|
||||||
|
unsafe { msg_send![class!(NSArray), array] }
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn has_marked_text(this: &Object, _: Sel) -> BOOL {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.marked_text_range())
|
||||||
|
.flatten()
|
||||||
|
.is_some() as BOOL
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn marked_range(this: &Object, _: Sel) -> NSRange {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.marked_text_range())
|
||||||
|
.flatten()
|
||||||
|
.map_or(NSRange::invalid(), |range| range.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn selected_range(this: &Object, _: Sel) -> NSRange {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.selected_text_range())
|
||||||
|
.flatten()
|
||||||
|
.map_or(NSRange::invalid(), |range| range.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn first_rect_for_character_range(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
range: NSRange,
|
||||||
|
_: id,
|
||||||
|
) -> NSRect {
|
||||||
|
let frame = unsafe {
|
||||||
|
let window = get_window_state(this).borrow().native_window;
|
||||||
|
NSView::frame(window)
|
||||||
|
};
|
||||||
|
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.rect_for_range(range.to_range()?)
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.map_or(
|
||||||
|
NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.)),
|
||||||
|
|rect| {
|
||||||
|
NSRect::new(
|
||||||
|
NSPoint::new(
|
||||||
|
frame.origin.x + rect.origin_x() as f64,
|
||||||
|
frame.origin.y + frame.size.height - rect.origin_y() as f64,
|
||||||
|
),
|
||||||
|
NSSize::new(rect.width() as f64, rect.height() as f64),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn insert_text(this: &Object, _: Sel, text: id, replacement_range: NSRange) {
|
||||||
|
unsafe {
|
||||||
|
let window_state = get_window_state(this);
|
||||||
|
let mut window_state_borrow = window_state.borrow_mut();
|
||||||
|
let pending_key_down_event = window_state_borrow.pending_key_down_event.take();
|
||||||
|
drop(window_state_borrow);
|
||||||
|
|
||||||
|
let is_attributed_string: BOOL =
|
||||||
|
msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
|
||||||
|
let text: id = if is_attributed_string == YES {
|
||||||
|
msg_send![text, string]
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
let replacement_range = replacement_range.to_range();
|
||||||
|
|
||||||
|
let is_composing =
|
||||||
|
with_input_handler(this, |input_handler| input_handler.marked_text_range())
|
||||||
|
.flatten()
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
if is_composing || text.chars().count() > 1 || pending_key_down_event.is_none() {
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.replace_text_in_range(replacement_range, text)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let mut handled = false;
|
||||||
|
|
||||||
|
let event_callback = window_state.borrow_mut().event_callback.take();
|
||||||
|
if let Some(mut event_callback) = event_callback {
|
||||||
|
handled = event_callback(Event::KeyDown(pending_key_down_event.unwrap()));
|
||||||
|
window_state.borrow_mut().event_callback = Some(event_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !handled {
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.replace_text_in_range(replacement_range, text)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn set_marked_text(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
text: id,
|
||||||
|
selected_range: NSRange,
|
||||||
|
replacement_range: NSRange,
|
||||||
|
) {
|
||||||
|
unsafe {
|
||||||
|
get_window_state(this)
|
||||||
|
.borrow_mut()
|
||||||
|
.pending_key_down_event
|
||||||
|
.take();
|
||||||
|
|
||||||
|
let is_attributed_string: BOOL =
|
||||||
|
msg_send![text, isKindOfClass: [class!(NSAttributedString)]];
|
||||||
|
let text: id = if is_attributed_string == YES {
|
||||||
|
msg_send![text, string]
|
||||||
|
} else {
|
||||||
|
text
|
||||||
|
};
|
||||||
|
let selected_range = selected_range.to_range();
|
||||||
|
let replacement_range = replacement_range.to_range();
|
||||||
|
let text = CStr::from_ptr(text.UTF8String() as *mut c_char)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
input_handler.replace_and_mark_text_in_range(replacement_range, text, selected_range);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn unmark_text(this: &Object, _: Sel) {
|
||||||
|
with_input_handler(this, |input_handler| input_handler.unmark_text());
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn attributed_substring_for_proposed_range(
|
||||||
|
this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
range: NSRange,
|
||||||
|
_actual_range: *mut c_void,
|
||||||
|
) -> id {
|
||||||
|
with_input_handler(this, |input_handler| {
|
||||||
|
let range = range.to_range()?;
|
||||||
|
if range.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected_text = input_handler.text_for_range(range)?;
|
||||||
|
unsafe {
|
||||||
|
let string: id = msg_send![class!(NSAttributedString), alloc];
|
||||||
|
let string: id = msg_send![string, initWithString: ns_string(&selected_text)];
|
||||||
|
Some(string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.unwrap_or(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn do_command_by_selector(_: &Object, _: Sel, _: Sel) {}
|
||||||
|
|
||||||
async fn synthetic_drag(
|
async fn synthetic_drag(
|
||||||
window_state: Weak<RefCell<WindowState>>,
|
window_state: Weak<RefCell<WindowState>>,
|
||||||
drag_id: usize,
|
drag_id: usize,
|
||||||
|
@ -891,3 +1174,19 @@ async fn synthetic_drag(
|
||||||
unsafe fn ns_string(string: &str) -> id {
|
unsafe fn ns_string(string: &str) -> id {
|
||||||
NSString::alloc(nil).init_str(string).autorelease()
|
NSString::alloc(nil).init_str(string).autorelease()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_input_handler<F, R>(window: &Object, f: F) -> Option<R>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut dyn InputHandler) -> R,
|
||||||
|
{
|
||||||
|
let window_state = unsafe { get_window_state(window) };
|
||||||
|
let mut window_state_borrow = window_state.as_ref().borrow_mut();
|
||||||
|
if let Some(mut input_handler) = window_state_borrow.input_handler.take() {
|
||||||
|
drop(window_state_borrow);
|
||||||
|
let result = f(input_handler.as_mut());
|
||||||
|
window_state.borrow_mut().input_handler = Some(input_handler);
|
||||||
|
Some(result)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -255,6 +255,8 @@ impl super::Window for Window {
|
||||||
self.close_handlers.push(callback);
|
self.close_handlers.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_input_handler(&mut self, _: Box<dyn crate::InputHandler>) {}
|
||||||
|
|
||||||
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
|
fn prompt(&self, _: crate::PromptLevel, _: &str, _: &[&str]) -> oneshot::Receiver<usize> {
|
||||||
let (done_tx, done_rx) = oneshot::channel();
|
let (done_tx, done_rx) = oneshot::channel();
|
||||||
self.pending_prompts.borrow_mut().push_back(done_tx);
|
self.pending_prompts.borrow_mut().push_back(done_tx);
|
||||||
|
@ -274,6 +276,8 @@ impl super::Window for Window {
|
||||||
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
|
fn on_should_close(&mut self, callback: Box<dyn FnMut() -> bool>) {
|
||||||
self.should_close_handler = Some(callback);
|
self.should_close_handler = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn show_character_palette(&self) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn platform() -> Platform {
|
pub fn platform() -> Platform {
|
||||||
|
|
|
@ -19,7 +19,7 @@ use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut, Range},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -224,6 +224,17 @@ impl Presenter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rect_for_text_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<RectF> {
|
||||||
|
cx.focused_view_id(self.window_id).and_then(|view_id| {
|
||||||
|
let cx = MeasurementContext {
|
||||||
|
app: cx,
|
||||||
|
rendered_views: &self.rendered_views,
|
||||||
|
window_id: self.window_id,
|
||||||
|
};
|
||||||
|
cx.rect_for_text_range(view_id, range_utf16)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool {
|
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool {
|
||||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||||
let mut invalidated_views = Vec::new();
|
let mut invalidated_views = Vec::new();
|
||||||
|
@ -717,6 +728,27 @@ impl<'a> DerefMut for EventContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct MeasurementContext<'a> {
|
||||||
|
app: &'a AppContext,
|
||||||
|
rendered_views: &'a HashMap<usize, ElementBox>,
|
||||||
|
pub window_id: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Deref for MeasurementContext<'a> {
|
||||||
|
type Target = AppContext;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MeasurementContext<'a> {
|
||||||
|
fn rect_for_text_range(&self, view_id: usize, range_utf16: Range<usize>) -> Option<RectF> {
|
||||||
|
let element = self.rendered_views.get(&view_id)?;
|
||||||
|
element.rect_for_text_range(range_utf16, self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DebugContext<'a> {
|
pub struct DebugContext<'a> {
|
||||||
rendered_views: &'a HashMap<usize, ElementBox>,
|
rendered_views: &'a HashMap<usize, ElementBox>,
|
||||||
pub font_cache: &'a FontCache,
|
pub font_cache: &'a FontCache,
|
||||||
|
@ -876,6 +908,18 @@ impl Element for ChildView {
|
||||||
cx.dispatch_event(self.view.id(), event)
|
cx.dispatch_event(self.view.id(), event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
range_utf16: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
cx: &MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
cx.rect_for_text_range(self.view.id(), range_utf16)
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
|
|
|
@ -1076,6 +1076,10 @@ impl Buffer {
|
||||||
self.text.finalize_last_transaction()
|
self.text.finalize_last_transaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn group_until_transaction(&mut self, transaction_id: TransactionId) {
|
||||||
|
self.text.group_until_transaction(transaction_id);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
|
pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
|
||||||
self.text.forget_transaction(transaction_id);
|
self.text.forget_transaction(transaction_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,11 +39,6 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation {
|
||||||
local_timestamp: undo.id.value,
|
local_timestamp: undo.id.value,
|
||||||
lamport_timestamp: lamport_timestamp.value,
|
lamport_timestamp: lamport_timestamp.value,
|
||||||
version: serialize_version(&undo.version),
|
version: serialize_version(&undo.version),
|
||||||
transaction_ranges: undo
|
|
||||||
.transaction_ranges
|
|
||||||
.iter()
|
|
||||||
.map(serialize_range)
|
|
||||||
.collect(),
|
|
||||||
transaction_version: serialize_version(&undo.transaction_version),
|
transaction_version: serialize_version(&undo.transaction_version),
|
||||||
counts: undo
|
counts: undo
|
||||||
.counts
|
.counts
|
||||||
|
@ -204,11 +199,6 @@ pub fn deserialize_operation(message: proto::Operation) -> Result<Operation> {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
transaction_ranges: undo
|
|
||||||
.transaction_ranges
|
|
||||||
.into_iter()
|
|
||||||
.map(deserialize_range)
|
|
||||||
.collect(),
|
|
||||||
transaction_version: deserialize_version(undo.transaction_version),
|
transaction_version: deserialize_version(undo.transaction_version),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
@ -460,8 +450,6 @@ pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction {
|
||||||
.map(serialize_local_timestamp)
|
.map(serialize_local_timestamp)
|
||||||
.collect(),
|
.collect(),
|
||||||
start: serialize_version(&transaction.start),
|
start: serialize_version(&transaction.start),
|
||||||
end: serialize_version(&transaction.end),
|
|
||||||
ranges: transaction.ranges.iter().map(serialize_range).collect(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,12 +466,6 @@ pub fn deserialize_transaction(transaction: proto::Transaction) -> Result<Transa
|
||||||
.map(deserialize_local_timestamp)
|
.map(deserialize_local_timestamp)
|
||||||
.collect(),
|
.collect(),
|
||||||
start: deserialize_version(transaction.start.into()),
|
start: deserialize_version(transaction.start.into()),
|
||||||
end: deserialize_version(transaction.end),
|
|
||||||
ranges: transaction
|
|
||||||
.ranges
|
|
||||||
.into_iter()
|
|
||||||
.map(deserialize_range)
|
|
||||||
.collect(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,7 @@ impl Fs for RealFs {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
async fn save(&self, path: &Path, text: &Rope, line_ending: LineEnding) -> Result<()> {
|
||||||
let buffer_size = text.summary().bytes.min(10 * 1024);
|
let buffer_size = text.summary().len.min(10 * 1024);
|
||||||
let file = smol::fs::File::create(path).await?;
|
let file = smol::fs::File::create(path).await?;
|
||||||
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
let mut writer = smol::io::BufWriter::with_capacity(buffer_size, file);
|
||||||
for chunk in chunks(text, line_ending) {
|
for chunk in chunks(text, line_ending) {
|
||||||
|
|
|
@ -534,8 +534,6 @@ message Transaction {
|
||||||
LocalTimestamp id = 1;
|
LocalTimestamp id = 1;
|
||||||
repeated LocalTimestamp edit_ids = 2;
|
repeated LocalTimestamp edit_ids = 2;
|
||||||
repeated VectorClockEntry start = 3;
|
repeated VectorClockEntry start = 3;
|
||||||
repeated VectorClockEntry end = 4;
|
|
||||||
repeated Range ranges = 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message LocalTimestamp {
|
message LocalTimestamp {
|
||||||
|
@ -890,7 +888,6 @@ message Operation {
|
||||||
uint32 local_timestamp = 2;
|
uint32 local_timestamp = 2;
|
||||||
uint32 lamport_timestamp = 3;
|
uint32 lamport_timestamp = 3;
|
||||||
repeated VectorClockEntry version = 4;
|
repeated VectorClockEntry version = 4;
|
||||||
repeated Range transaction_ranges = 5;
|
|
||||||
repeated VectorClockEntry transaction_version = 6;
|
repeated VectorClockEntry transaction_version = 6;
|
||||||
repeated UndoCount counts = 7;
|
repeated UndoCount counts = 7;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,4 @@ pub use conn::Connection;
|
||||||
pub use peer::*;
|
pub use peer::*;
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub const PROTOCOL_VERSION: u32 = 27;
|
pub const PROTOCOL_VERSION: u32 = 28;
|
||||||
|
|
|
@ -796,6 +796,27 @@ impl Element for TerminalEl {
|
||||||
"type": "TerminalElement",
|
"type": "TerminalElement",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
bounds: RectF,
|
||||||
|
_: RectF,
|
||||||
|
layout: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &gpui::MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
// Use the same origin that's passed to `Cursor::paint` in the paint
|
||||||
|
// method bove.
|
||||||
|
let mut origin = bounds.origin() + vec2f(layout.size.cell_width, 0.);
|
||||||
|
|
||||||
|
// TODO - Why is it necessary to move downward one line to get correct
|
||||||
|
// positioning? I would think that we'd want the same rect that is
|
||||||
|
// painted for the cursor.
|
||||||
|
origin += vec2f(0., layout.size.line_height);
|
||||||
|
|
||||||
|
Some(layout.cursor.as_ref()?.bounding_rect(origin))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod test {
|
mod test {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, keymap::Keystroke, ClipboardItem, Element, ElementBox, ModelHandle, MutableAppContext,
|
actions, keymap::Keystroke, AppContext, ClipboardItem, Element, ElementBox, ModelHandle,
|
||||||
View, ViewContext,
|
MutableAppContext, View, ViewContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -159,4 +159,18 @@ impl View for ConnectedView {
|
||||||
fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
|
fn on_focus(&mut self, _cx: &mut ViewContext<Self>) {
|
||||||
self.has_new_content = false;
|
self.has_new_content = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selected_text_range(&self, _: &AppContext) -> Option<std::ops::Range<usize>> {
|
||||||
|
Some(0..0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_text_in_range(
|
||||||
|
&mut self,
|
||||||
|
_: Option<std::ops::Range<usize>>,
|
||||||
|
text: &str,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.terminal
|
||||||
|
.update(cx, |terminal, _| terminal.write_to_pty(text.into()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -231,54 +231,8 @@ pub fn to_esc_str(keystroke: &Keystroke, mode: &TermMode) -> Option<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fallback to sending the keystroke input directly
|
|
||||||
//Skin colors in utf8 are implemented as a seperate, invisible character
|
|
||||||
//that modifies the associated emoji. Some languages may have similarly
|
|
||||||
//implemented modifiers, e.g. certain diacritics that can be typed as a single character.
|
|
||||||
//This means that we need to assume some user input can result in multi-byte,
|
|
||||||
//multi-char strings. This is somewhat difficult, as GPUI normalizes all
|
|
||||||
//keys into a string representation. Hence, the check here to filter out GPUI
|
|
||||||
//keys that weren't captured above.
|
|
||||||
if !matches_gpui_key_str(&keystroke.key) {
|
|
||||||
return Some(keystroke.key.clone());
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
///Checks if the given string matches a GPUI key string.
|
|
||||||
///Table made from reading the source at gpui/src/platform/mac/event.rs
|
|
||||||
fn matches_gpui_key_str(str: &str) -> bool {
|
|
||||||
match str {
|
|
||||||
"backspace" => true,
|
|
||||||
"up" => true,
|
|
||||||
"down" => true,
|
|
||||||
"left" => true,
|
|
||||||
"right" => true,
|
|
||||||
"pageup" => true,
|
|
||||||
"pagedown" => true,
|
|
||||||
"home" => true,
|
|
||||||
"end" => true,
|
|
||||||
"delete" => true,
|
|
||||||
"enter" => true,
|
|
||||||
"escape" => true,
|
|
||||||
"tab" => true,
|
|
||||||
"f1" => true,
|
|
||||||
"f2" => true,
|
|
||||||
"f3" => true,
|
|
||||||
"f4" => true,
|
|
||||||
"f5" => true,
|
|
||||||
"f6" => true,
|
|
||||||
"f7" => true,
|
|
||||||
"f8" => true,
|
|
||||||
"f9" => true,
|
|
||||||
"f10" => true,
|
|
||||||
"f11" => true,
|
|
||||||
"f12" => true,
|
|
||||||
"space" => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Code Modifiers
|
/// Code Modifiers
|
||||||
/// ---------+---------------------------
|
/// ---------+---------------------------
|
||||||
|
@ -351,17 +305,15 @@ mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multi_char_fallthrough() {
|
fn test_plain_inputs() {
|
||||||
let ks = Keystroke {
|
let ks = Keystroke {
|
||||||
ctrl: false,
|
ctrl: false,
|
||||||
alt: false,
|
alt: false,
|
||||||
shift: false,
|
shift: false,
|
||||||
cmd: false,
|
cmd: false,
|
||||||
|
|
||||||
key: "🖖🏻".to_string(), //2 char string
|
key: "🖖🏻".to_string(), //2 char string
|
||||||
};
|
};
|
||||||
|
assert_eq!(to_esc_str(&ks, &TermMode::NONE), None);
|
||||||
assert_eq!(to_esc_str(&ks, &TermMode::NONE), Some("🖖🏻".to_string()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
50
crates/text/src/offset_utf16.rs
Normal file
50
crates/text/src/offset_utf16.rs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
use std::ops::{Add, AddAssign, Sub};
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub struct OffsetUtf16(pub usize);
|
||||||
|
|
||||||
|
impl<'a> Add<&'a Self> for OffsetUtf16 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: &'a Self) -> Self::Output {
|
||||||
|
Self(self.0 + other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for OffsetUtf16 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn add(self, other: Self) -> Self::Output {
|
||||||
|
Self(self.0 + other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Sub<&'a Self> for OffsetUtf16 {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn sub(self, other: &'a Self) -> Self::Output {
|
||||||
|
debug_assert!(*other <= self);
|
||||||
|
Self(self.0 - other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub for OffsetUtf16 {
|
||||||
|
type Output = OffsetUtf16;
|
||||||
|
|
||||||
|
fn sub(self, other: Self) -> Self::Output {
|
||||||
|
debug_assert!(other <= self);
|
||||||
|
Self(self.0 - other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AddAssign<&'a Self> for OffsetUtf16 {
|
||||||
|
fn add_assign(&mut self, other: &'a Self) {
|
||||||
|
self.0 += other.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddAssign<Self> for OffsetUtf16 {
|
||||||
|
fn add_assign(&mut self, other: Self) {
|
||||||
|
self.0 += other.0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::PointUtf16;
|
|
||||||
|
|
||||||
use super::Point;
|
use super::Point;
|
||||||
|
use crate::{OffsetUtf16, PointUtf16};
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use bromberg_sl2::{DigestString, HashMatrix};
|
use bromberg_sl2::{DigestString, HashMatrix};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
@ -165,8 +164,34 @@ impl Rope {
|
||||||
Chunks::new(self, range, true)
|
Chunks::new(self, range, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
|
||||||
|
if offset >= self.summary().len {
|
||||||
|
return self.summary().len_utf16;
|
||||||
|
}
|
||||||
|
let mut cursor = self.chunks.cursor::<(usize, OffsetUtf16)>();
|
||||||
|
cursor.seek(&offset, Bias::Left, &());
|
||||||
|
let overshoot = offset - cursor.start().0;
|
||||||
|
cursor.start().1
|
||||||
|
+ cursor.item().map_or(Default::default(), |chunk| {
|
||||||
|
chunk.offset_to_offset_utf16(overshoot)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
|
||||||
|
if offset >= self.summary().len_utf16 {
|
||||||
|
return self.summary().len;
|
||||||
|
}
|
||||||
|
let mut cursor = self.chunks.cursor::<(OffsetUtf16, usize)>();
|
||||||
|
cursor.seek(&offset, Bias::Left, &());
|
||||||
|
let overshoot = offset - cursor.start().0;
|
||||||
|
cursor.start().1
|
||||||
|
+ cursor.item().map_or(Default::default(), |chunk| {
|
||||||
|
chunk.offset_utf16_to_offset(overshoot)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn offset_to_point(&self, offset: usize) -> Point {
|
pub fn offset_to_point(&self, offset: usize) -> Point {
|
||||||
if offset >= self.summary().bytes {
|
if offset >= self.summary().len {
|
||||||
return self.summary().lines;
|
return self.summary().lines;
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(usize, Point)>();
|
let mut cursor = self.chunks.cursor::<(usize, Point)>();
|
||||||
|
@ -179,8 +204,8 @@ impl Rope {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
|
pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 {
|
||||||
if offset >= self.summary().bytes {
|
if offset >= self.summary().len {
|
||||||
return self.summary().lines_utf16;
|
return self.summary().lines_utf16();
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
|
let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>();
|
||||||
cursor.seek(&offset, Bias::Left, &());
|
cursor.seek(&offset, Bias::Left, &());
|
||||||
|
@ -193,7 +218,7 @@ impl Rope {
|
||||||
|
|
||||||
pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
|
pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 {
|
||||||
if point >= self.summary().lines {
|
if point >= self.summary().lines {
|
||||||
return self.summary().lines_utf16;
|
return self.summary().lines_utf16();
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>();
|
let mut cursor = self.chunks.cursor::<(Point, PointUtf16)>();
|
||||||
cursor.seek(&point, Bias::Left, &());
|
cursor.seek(&point, Bias::Left, &());
|
||||||
|
@ -206,7 +231,7 @@ impl Rope {
|
||||||
|
|
||||||
pub fn point_to_offset(&self, point: Point) -> usize {
|
pub fn point_to_offset(&self, point: Point) -> usize {
|
||||||
if point >= self.summary().lines {
|
if point >= self.summary().lines {
|
||||||
return self.summary().bytes;
|
return self.summary().len;
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(Point, usize)>();
|
let mut cursor = self.chunks.cursor::<(Point, usize)>();
|
||||||
cursor.seek(&point, Bias::Left, &());
|
cursor.seek(&point, Bias::Left, &());
|
||||||
|
@ -218,8 +243,8 @@ impl Rope {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize {
|
||||||
if point >= self.summary().lines_utf16 {
|
if point >= self.summary().lines_utf16() {
|
||||||
return self.summary().bytes;
|
return self.summary().len;
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
|
let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>();
|
||||||
cursor.seek(&point, Bias::Left, &());
|
cursor.seek(&point, Bias::Left, &());
|
||||||
|
@ -231,7 +256,7 @@ impl Rope {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point {
|
||||||
if point >= self.summary().lines_utf16 {
|
if point >= self.summary().lines_utf16() {
|
||||||
return self.summary().lines;
|
return self.summary().lines;
|
||||||
}
|
}
|
||||||
let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>();
|
let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>();
|
||||||
|
@ -262,7 +287,18 @@ impl Rope {
|
||||||
}
|
}
|
||||||
offset
|
offset
|
||||||
} else {
|
} else {
|
||||||
self.summary().bytes
|
self.summary().len
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
|
||||||
|
let mut cursor = self.chunks.cursor::<OffsetUtf16>();
|
||||||
|
cursor.seek(&offset, Bias::Right, &());
|
||||||
|
if let Some(chunk) = cursor.item() {
|
||||||
|
let overshoot = offset - cursor.start();
|
||||||
|
*cursor.start() + chunk.clip_offset_utf16(overshoot, bias)
|
||||||
|
} else {
|
||||||
|
self.summary().len_utf16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,7 +320,7 @@ impl Rope {
|
||||||
let overshoot = point - cursor.start();
|
let overshoot = point - cursor.start();
|
||||||
*cursor.start() + chunk.clip_point_utf16(overshoot, bias)
|
*cursor.start() + chunk.clip_point_utf16(overshoot, bias)
|
||||||
} else {
|
} else {
|
||||||
self.summary().lines_utf16
|
self.summary().lines_utf16()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,6 +579,34 @@ impl<'a> io::Read for Bytes<'a> {
|
||||||
struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
|
struct Chunk(ArrayString<{ 2 * CHUNK_BASE }>);
|
||||||
|
|
||||||
impl Chunk {
|
impl Chunk {
|
||||||
|
fn offset_to_offset_utf16(&self, target: usize) -> OffsetUtf16 {
|
||||||
|
let mut offset = 0;
|
||||||
|
let mut offset_utf16 = OffsetUtf16(0);
|
||||||
|
for ch in self.0.chars() {
|
||||||
|
if offset >= target {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += ch.len_utf8();
|
||||||
|
offset_utf16.0 += ch.len_utf16();
|
||||||
|
}
|
||||||
|
offset_utf16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_utf16_to_offset(&self, target: OffsetUtf16) -> usize {
|
||||||
|
let mut offset_utf16 = OffsetUtf16(0);
|
||||||
|
let mut offset = 0;
|
||||||
|
for ch in self.0.chars() {
|
||||||
|
if offset_utf16 >= target {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += ch.len_utf8();
|
||||||
|
offset_utf16.0 += ch.len_utf16();
|
||||||
|
}
|
||||||
|
offset
|
||||||
|
}
|
||||||
|
|
||||||
fn offset_to_point(&self, target: usize) -> Point {
|
fn offset_to_point(&self, target: usize) -> Point {
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut point = Point::new(0, 0);
|
let mut point = Point::new(0, 0);
|
||||||
|
@ -712,6 +776,18 @@ impl Chunk {
|
||||||
}
|
}
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clip_offset_utf16(&self, target: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
|
||||||
|
let mut code_units = self.0.encode_utf16();
|
||||||
|
let mut offset = code_units.by_ref().take(target.0 as usize).count();
|
||||||
|
if char::decode_utf16(code_units).next().transpose().is_err() {
|
||||||
|
match bias {
|
||||||
|
Bias::Left => offset -= 1,
|
||||||
|
Bias::Right => offset += 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OffsetUtf16(offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sum_tree::Item for Chunk {
|
impl sum_tree::Item for Chunk {
|
||||||
|
@ -748,31 +824,44 @@ impl sum_tree::Summary for ChunkSummary {
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||||
pub struct TextSummary {
|
pub struct TextSummary {
|
||||||
pub bytes: usize,
|
pub len: usize,
|
||||||
|
pub len_utf16: OffsetUtf16,
|
||||||
pub lines: Point,
|
pub lines: Point,
|
||||||
pub lines_utf16: PointUtf16,
|
|
||||||
pub first_line_chars: u32,
|
pub first_line_chars: u32,
|
||||||
pub last_line_chars: u32,
|
pub last_line_chars: u32,
|
||||||
|
pub last_line_len_utf16: u32,
|
||||||
pub longest_row: u32,
|
pub longest_row: u32,
|
||||||
pub longest_row_chars: u32,
|
pub longest_row_chars: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TextSummary {
|
||||||
|
pub fn lines_utf16(&self) -> PointUtf16 {
|
||||||
|
PointUtf16 {
|
||||||
|
row: self.lines.row,
|
||||||
|
column: self.last_line_len_utf16,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a str> for TextSummary {
|
impl<'a> From<&'a str> for TextSummary {
|
||||||
fn from(text: &'a str) -> Self {
|
fn from(text: &'a str) -> Self {
|
||||||
|
let mut len_utf16 = OffsetUtf16(0);
|
||||||
let mut lines = Point::new(0, 0);
|
let mut lines = Point::new(0, 0);
|
||||||
let mut lines_utf16 = PointUtf16::new(0, 0);
|
|
||||||
let mut first_line_chars = 0;
|
let mut first_line_chars = 0;
|
||||||
let mut last_line_chars = 0;
|
let mut last_line_chars = 0;
|
||||||
|
let mut last_line_len_utf16 = 0;
|
||||||
let mut longest_row = 0;
|
let mut longest_row = 0;
|
||||||
let mut longest_row_chars = 0;
|
let mut longest_row_chars = 0;
|
||||||
for c in text.chars() {
|
for c in text.chars() {
|
||||||
|
len_utf16.0 += c.len_utf16();
|
||||||
|
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
lines += Point::new(1, 0);
|
lines += Point::new(1, 0);
|
||||||
lines_utf16 += PointUtf16::new(1, 0);
|
last_line_len_utf16 = 0;
|
||||||
last_line_chars = 0;
|
last_line_chars = 0;
|
||||||
} else {
|
} else {
|
||||||
lines.column += c.len_utf8() as u32;
|
lines.column += c.len_utf8() as u32;
|
||||||
lines_utf16.column += c.len_utf16() as u32;
|
last_line_len_utf16 += c.len_utf16() as u32;
|
||||||
last_line_chars += 1;
|
last_line_chars += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,11 +876,12 @@ impl<'a> From<&'a str> for TextSummary {
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: text.len(),
|
len: text.len(),
|
||||||
|
len_utf16,
|
||||||
lines,
|
lines,
|
||||||
lines_utf16,
|
|
||||||
first_line_chars,
|
first_line_chars,
|
||||||
last_line_chars,
|
last_line_chars,
|
||||||
|
last_line_len_utf16,
|
||||||
longest_row,
|
longest_row,
|
||||||
longest_row_chars,
|
longest_row_chars,
|
||||||
}
|
}
|
||||||
|
@ -833,13 +923,15 @@ impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
|
||||||
|
|
||||||
if other.lines.row == 0 {
|
if other.lines.row == 0 {
|
||||||
self.last_line_chars += other.first_line_chars;
|
self.last_line_chars += other.first_line_chars;
|
||||||
|
self.last_line_len_utf16 += other.last_line_len_utf16;
|
||||||
} else {
|
} else {
|
||||||
self.last_line_chars = other.last_line_chars;
|
self.last_line_chars = other.last_line_chars;
|
||||||
|
self.last_line_len_utf16 = other.last_line_len_utf16;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.bytes += other.bytes;
|
self.len += other.len;
|
||||||
|
self.len_utf16 += other.len_utf16;
|
||||||
self.lines += other.lines;
|
self.lines += other.lines;
|
||||||
self.lines_utf16 += other.lines_utf16;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,13 +978,29 @@ impl TextDimension for TextSummary {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for usize {
|
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for usize {
|
||||||
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
||||||
*self += summary.text.bytes;
|
*self += summary.text.len;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextDimension for usize {
|
impl TextDimension for usize {
|
||||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||||
summary.bytes
|
summary.len
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_assign(&mut self, other: &Self) {
|
||||||
|
*self += other;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for OffsetUtf16 {
|
||||||
|
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
||||||
|
*self += summary.text.len_utf16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextDimension for OffsetUtf16 {
|
||||||
|
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||||
|
summary.len_utf16
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_assign(&mut self, other: &Self) {
|
fn add_assign(&mut self, other: &Self) {
|
||||||
|
@ -918,13 +1026,13 @@ impl TextDimension for Point {
|
||||||
|
|
||||||
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for PointUtf16 {
|
impl<'a> sum_tree::Dimension<'a, ChunkSummary> for PointUtf16 {
|
||||||
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
fn add_summary(&mut self, summary: &'a ChunkSummary, _: &()) {
|
||||||
*self += summary.text.lines_utf16;
|
*self += summary.text.lines_utf16();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextDimension for PointUtf16 {
|
impl TextDimension for PointUtf16 {
|
||||||
fn from_text_summary(summary: &TextSummary) -> Self {
|
fn from_text_summary(summary: &TextSummary) -> Self {
|
||||||
summary.lines_utf16
|
summary.lines_utf16()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_assign(&mut self, other: &Self) {
|
fn add_assign(&mut self, other: &Self) {
|
||||||
|
@ -1000,6 +1108,19 @@ mod tests {
|
||||||
rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
|
rope.clip_point_utf16(PointUtf16::new(0, 3), Bias::Right),
|
||||||
PointUtf16::new(0, 2)
|
PointUtf16::new(0, 2)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
rope.clip_offset_utf16(OffsetUtf16(1), Bias::Left),
|
||||||
|
OffsetUtf16(0)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rope.clip_offset_utf16(OffsetUtf16(1), Bias::Right),
|
||||||
|
OffsetUtf16(2)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rope.clip_offset_utf16(OffsetUtf16(3), Bias::Right),
|
||||||
|
OffsetUtf16(2)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 100)]
|
#[gpui::test(iterations = 100)]
|
||||||
|
@ -1054,6 +1175,7 @@ mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut offset_utf16 = OffsetUtf16(0);
|
||||||
let mut point = Point::new(0, 0);
|
let mut point = Point::new(0, 0);
|
||||||
let mut point_utf16 = PointUtf16::new(0, 0);
|
let mut point_utf16 = PointUtf16::new(0, 0);
|
||||||
for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
|
for (ix, ch) in expected.char_indices().chain(Some((expected.len(), '\0'))) {
|
||||||
|
@ -1076,6 +1198,18 @@ mod tests {
|
||||||
"point_utf16_to_offset({:?})",
|
"point_utf16_to_offset({:?})",
|
||||||
point_utf16
|
point_utf16
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
actual.offset_to_offset_utf16(ix),
|
||||||
|
offset_utf16,
|
||||||
|
"offset_to_offset_utf16({:?})",
|
||||||
|
ix
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
actual.offset_utf16_to_offset(offset_utf16),
|
||||||
|
ix,
|
||||||
|
"offset_utf16_to_offset({:?})",
|
||||||
|
offset_utf16
|
||||||
|
);
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
point += Point::new(1, 0);
|
point += Point::new(1, 0);
|
||||||
point_utf16 += PointUtf16::new(1, 0);
|
point_utf16 += PointUtf16::new(1, 0);
|
||||||
|
@ -1083,10 +1217,19 @@ mod tests {
|
||||||
point.column += ch.len_utf8() as u32;
|
point.column += ch.len_utf8() as u32;
|
||||||
point_utf16.column += ch.len_utf16() as u32;
|
point_utf16.column += ch.len_utf16() as u32;
|
||||||
}
|
}
|
||||||
|
offset_utf16.0 += ch.len_utf16();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut offset_utf16 = OffsetUtf16(0);
|
||||||
let mut point_utf16 = PointUtf16::zero();
|
let mut point_utf16 = PointUtf16::zero();
|
||||||
for unit in expected.encode_utf16() {
|
for unit in expected.encode_utf16() {
|
||||||
|
let left_offset = actual.clip_offset_utf16(offset_utf16, Bias::Left);
|
||||||
|
let right_offset = actual.clip_offset_utf16(offset_utf16, Bias::Right);
|
||||||
|
assert!(right_offset >= left_offset);
|
||||||
|
// Ensure translating UTF-16 offsets to UTF-8 offsets doesn't panic.
|
||||||
|
actual.offset_utf16_to_offset(left_offset);
|
||||||
|
actual.offset_utf16_to_offset(right_offset);
|
||||||
|
|
||||||
let left_point = actual.clip_point_utf16(point_utf16, Bias::Left);
|
let left_point = actual.clip_point_utf16(point_utf16, Bias::Left);
|
||||||
let right_point = actual.clip_point_utf16(point_utf16, Bias::Right);
|
let right_point = actual.clip_point_utf16(point_utf16, Bias::Right);
|
||||||
assert!(right_point >= left_point);
|
assert!(right_point >= left_point);
|
||||||
|
@ -1094,6 +1237,7 @@ mod tests {
|
||||||
actual.point_utf16_to_offset(left_point);
|
actual.point_utf16_to_offset(left_point);
|
||||||
actual.point_utf16_to_offset(right_point);
|
actual.point_utf16_to_offset(right_point);
|
||||||
|
|
||||||
|
offset_utf16.0 += 1;
|
||||||
if unit == b'\n' as u16 {
|
if unit == b'\n' as u16 {
|
||||||
point_utf16 += PointUtf16::new(1, 0);
|
point_utf16 += PointUtf16::new(1, 0);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -247,11 +247,12 @@ fn test_text_summary_for_range() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(1..3),
|
buffer.text_summary_for_range::<TextSummary, _>(1..3),
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: 2,
|
len: 2,
|
||||||
|
len_utf16: OffsetUtf16(2),
|
||||||
lines: Point::new(1, 0),
|
lines: Point::new(1, 0),
|
||||||
lines_utf16: PointUtf16::new(1, 0),
|
|
||||||
first_line_chars: 1,
|
first_line_chars: 1,
|
||||||
last_line_chars: 0,
|
last_line_chars: 0,
|
||||||
|
last_line_len_utf16: 0,
|
||||||
longest_row: 0,
|
longest_row: 0,
|
||||||
longest_row_chars: 1,
|
longest_row_chars: 1,
|
||||||
}
|
}
|
||||||
|
@ -259,11 +260,12 @@ fn test_text_summary_for_range() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(1..12),
|
buffer.text_summary_for_range::<TextSummary, _>(1..12),
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: 11,
|
len: 11,
|
||||||
|
len_utf16: OffsetUtf16(11),
|
||||||
lines: Point::new(3, 0),
|
lines: Point::new(3, 0),
|
||||||
lines_utf16: PointUtf16::new(3, 0),
|
|
||||||
first_line_chars: 1,
|
first_line_chars: 1,
|
||||||
last_line_chars: 0,
|
last_line_chars: 0,
|
||||||
|
last_line_len_utf16: 0,
|
||||||
longest_row: 2,
|
longest_row: 2,
|
||||||
longest_row_chars: 4,
|
longest_row_chars: 4,
|
||||||
}
|
}
|
||||||
|
@ -271,11 +273,12 @@ fn test_text_summary_for_range() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(0..20),
|
buffer.text_summary_for_range::<TextSummary, _>(0..20),
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: 20,
|
len: 20,
|
||||||
|
len_utf16: OffsetUtf16(20),
|
||||||
lines: Point::new(4, 1),
|
lines: Point::new(4, 1),
|
||||||
lines_utf16: PointUtf16::new(4, 1),
|
|
||||||
first_line_chars: 2,
|
first_line_chars: 2,
|
||||||
last_line_chars: 1,
|
last_line_chars: 1,
|
||||||
|
last_line_len_utf16: 1,
|
||||||
longest_row: 3,
|
longest_row: 3,
|
||||||
longest_row_chars: 6,
|
longest_row_chars: 6,
|
||||||
}
|
}
|
||||||
|
@ -283,11 +286,12 @@ fn test_text_summary_for_range() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(0..22),
|
buffer.text_summary_for_range::<TextSummary, _>(0..22),
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: 22,
|
len: 22,
|
||||||
|
len_utf16: OffsetUtf16(22),
|
||||||
lines: Point::new(4, 3),
|
lines: Point::new(4, 3),
|
||||||
lines_utf16: PointUtf16::new(4, 3),
|
|
||||||
first_line_chars: 2,
|
first_line_chars: 2,
|
||||||
last_line_chars: 3,
|
last_line_chars: 3,
|
||||||
|
last_line_len_utf16: 3,
|
||||||
longest_row: 3,
|
longest_row: 3,
|
||||||
longest_row_chars: 6,
|
longest_row_chars: 6,
|
||||||
}
|
}
|
||||||
|
@ -295,11 +299,12 @@ fn test_text_summary_for_range() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
buffer.text_summary_for_range::<TextSummary, _>(7..22),
|
buffer.text_summary_for_range::<TextSummary, _>(7..22),
|
||||||
TextSummary {
|
TextSummary {
|
||||||
bytes: 15,
|
len: 15,
|
||||||
|
len_utf16: OffsetUtf16(15),
|
||||||
lines: Point::new(2, 3),
|
lines: Point::new(2, 3),
|
||||||
lines_utf16: PointUtf16::new(2, 3),
|
|
||||||
first_line_chars: 4,
|
first_line_chars: 4,
|
||||||
last_line_chars: 3,
|
last_line_chars: 3,
|
||||||
|
last_line_len_utf16: 3,
|
||||||
longest_row: 1,
|
longest_row: 1,
|
||||||
longest_row_chars: 6,
|
longest_row_chars: 6,
|
||||||
}
|
}
|
||||||
|
@ -520,7 +525,7 @@ fn test_history() {
|
||||||
let mut now = Instant::now();
|
let mut now = Instant::now();
|
||||||
let mut buffer = Buffer::new(0, 0, "123456".into());
|
let mut buffer = Buffer::new(0, 0, "123456".into());
|
||||||
|
|
||||||
buffer.start_transaction_at(now);
|
let transaction_1 = buffer.start_transaction_at(now).unwrap();
|
||||||
buffer.edit([(2..4, "cd")]);
|
buffer.edit([(2..4, "cd")]);
|
||||||
buffer.end_transaction_at(now);
|
buffer.end_transaction_at(now);
|
||||||
assert_eq!(buffer.text(), "12cd56");
|
assert_eq!(buffer.text(), "12cd56");
|
||||||
|
@ -559,7 +564,9 @@ fn test_history() {
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
|
|
||||||
// Redo stack gets cleared after performing an edit.
|
// Redo stack gets cleared after performing an edit.
|
||||||
|
buffer.start_transaction_at(now);
|
||||||
buffer.edit([(0..0, "X")]);
|
buffer.edit([(0..0, "X")]);
|
||||||
|
buffer.end_transaction_at(now);
|
||||||
assert_eq!(buffer.text(), "X12cde6");
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
buffer.redo();
|
buffer.redo();
|
||||||
assert_eq!(buffer.text(), "X12cde6");
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
|
@ -567,6 +574,16 @@ fn test_history() {
|
||||||
assert_eq!(buffer.text(), "12cde6");
|
assert_eq!(buffer.text(), "12cde6");
|
||||||
buffer.undo();
|
buffer.undo();
|
||||||
assert_eq!(buffer.text(), "123456");
|
assert_eq!(buffer.text(), "123456");
|
||||||
|
|
||||||
|
// Transactions can be grouped manually.
|
||||||
|
buffer.redo();
|
||||||
|
buffer.redo();
|
||||||
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
|
buffer.group_until_transaction(transaction_1);
|
||||||
|
buffer.undo();
|
||||||
|
assert_eq!(buffer.text(), "123456");
|
||||||
|
buffer.redo();
|
||||||
|
assert_eq!(buffer.text(), "X12cde6");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -2,6 +2,7 @@ mod anchor;
|
||||||
pub mod locator;
|
pub mod locator;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
mod offset_utf16;
|
||||||
pub mod operation_queue;
|
pub mod operation_queue;
|
||||||
mod patch;
|
mod patch;
|
||||||
mod point;
|
mod point;
|
||||||
|
@ -20,6 +21,7 @@ use clock::ReplicaId;
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use locator::Locator;
|
use locator::Locator;
|
||||||
|
pub use offset_utf16::*;
|
||||||
use operation_queue::OperationQueue;
|
use operation_queue::OperationQueue;
|
||||||
pub use patch::Patch;
|
pub use patch::Patch;
|
||||||
pub use point::*;
|
pub use point::*;
|
||||||
|
@ -33,7 +35,7 @@ pub use rope::{Chunks, Rope, TextSummary};
|
||||||
pub use selection::*;
|
pub use selection::*;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::{self, Ordering},
|
cmp::{self, Ordering, Reverse},
|
||||||
future::Future,
|
future::Future,
|
||||||
iter::Iterator,
|
iter::Iterator,
|
||||||
ops::{self, Deref, Range, Sub},
|
ops::{self, Deref, Range, Sub},
|
||||||
|
@ -90,8 +92,6 @@ pub struct Transaction {
|
||||||
pub id: TransactionId,
|
pub id: TransactionId,
|
||||||
pub edit_ids: Vec<clock::Local>,
|
pub edit_ids: Vec<clock::Local>,
|
||||||
pub start: clock::Global,
|
pub start: clock::Global,
|
||||||
pub end: clock::Global,
|
|
||||||
pub ranges: Vec<Range<FullOffset>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
@ -104,79 +104,31 @@ impl HistoryEntry {
|
||||||
pub fn transaction_id(&self) -> TransactionId {
|
pub fn transaction_id(&self) -> TransactionId {
|
||||||
self.transaction.id
|
self.transaction.id
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_edit(&mut self, edit_operation: &EditOperation) {
|
|
||||||
self.transaction
|
|
||||||
.edit_ids
|
|
||||||
.push(edit_operation.timestamp.local());
|
|
||||||
self.transaction
|
|
||||||
.end
|
|
||||||
.observe(edit_operation.timestamp.local());
|
|
||||||
|
|
||||||
let mut edits = edit_operation
|
|
||||||
.ranges
|
|
||||||
.iter()
|
|
||||||
.zip(edit_operation.new_text.iter())
|
|
||||||
.peekable();
|
|
||||||
let mut new_ranges = Vec::new();
|
|
||||||
let mut delta = 0;
|
|
||||||
|
|
||||||
for mut self_range in self.transaction.ranges.iter().cloned() {
|
|
||||||
self_range.start += delta;
|
|
||||||
self_range.end += delta;
|
|
||||||
|
|
||||||
while let Some((other_range, new_text)) = edits.peek() {
|
|
||||||
let insertion_len = new_text.len();
|
|
||||||
let mut other_range = (*other_range).clone();
|
|
||||||
other_range.start += delta;
|
|
||||||
other_range.end += delta;
|
|
||||||
|
|
||||||
if other_range.start <= self_range.end {
|
|
||||||
edits.next().unwrap();
|
|
||||||
delta += insertion_len;
|
|
||||||
|
|
||||||
if other_range.end < self_range.start {
|
|
||||||
new_ranges.push(other_range.start..other_range.end + insertion_len);
|
|
||||||
self_range.start += insertion_len;
|
|
||||||
self_range.end += insertion_len;
|
|
||||||
} else {
|
|
||||||
self_range.start = cmp::min(self_range.start, other_range.start);
|
|
||||||
self_range.end = cmp::max(self_range.end, other_range.end) + insertion_len;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
new_ranges.push(self_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (other_range, new_text) in edits {
|
|
||||||
let insertion_len = new_text.len();
|
|
||||||
new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len);
|
|
||||||
delta += insertion_len;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.transaction.ranges = new_ranges;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct History {
|
struct History {
|
||||||
// TODO: Turn this into a String or Rope, maybe.
|
// TODO: Turn this into a String or Rope, maybe.
|
||||||
base_text: Arc<str>,
|
base_text: Arc<str>,
|
||||||
operations: HashMap<clock::Local, Operation>,
|
operations: HashMap<clock::Local, Operation>,
|
||||||
|
insertion_slices: HashMap<clock::Local, Vec<InsertionSlice>>,
|
||||||
undo_stack: Vec<HistoryEntry>,
|
undo_stack: Vec<HistoryEntry>,
|
||||||
redo_stack: Vec<HistoryEntry>,
|
redo_stack: Vec<HistoryEntry>,
|
||||||
transaction_depth: usize,
|
transaction_depth: usize,
|
||||||
group_interval: Duration,
|
group_interval: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct InsertionSlice {
|
||||||
|
insertion_id: clock::Local,
|
||||||
|
range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
impl History {
|
impl History {
|
||||||
pub fn new(base_text: Arc<str>) -> Self {
|
pub fn new(base_text: Arc<str>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_text,
|
base_text,
|
||||||
operations: Default::default(),
|
operations: Default::default(),
|
||||||
|
insertion_slices: Default::default(),
|
||||||
undo_stack: Vec::new(),
|
undo_stack: Vec::new(),
|
||||||
redo_stack: Vec::new(),
|
redo_stack: Vec::new(),
|
||||||
transaction_depth: 0,
|
transaction_depth: 0,
|
||||||
|
@ -200,10 +152,8 @@ impl History {
|
||||||
self.undo_stack.push(HistoryEntry {
|
self.undo_stack.push(HistoryEntry {
|
||||||
transaction: Transaction {
|
transaction: Transaction {
|
||||||
id,
|
id,
|
||||||
start: start.clone(),
|
start,
|
||||||
end: start,
|
|
||||||
edit_ids: Default::default(),
|
edit_ids: Default::default(),
|
||||||
ranges: Default::default(),
|
|
||||||
},
|
},
|
||||||
first_edit_at: now,
|
first_edit_at: now,
|
||||||
last_edit_at: now,
|
last_edit_at: now,
|
||||||
|
@ -224,7 +174,7 @@ impl History {
|
||||||
.last()
|
.last()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.transaction
|
.transaction
|
||||||
.ranges
|
.edit_ids
|
||||||
.is_empty()
|
.is_empty()
|
||||||
{
|
{
|
||||||
self.undo_stack.pop();
|
self.undo_stack.pop();
|
||||||
|
@ -241,34 +191,49 @@ impl History {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group(&mut self) -> Option<TransactionId> {
|
fn group(&mut self) -> Option<TransactionId> {
|
||||||
let mut new_len = self.undo_stack.len();
|
let mut count = 0;
|
||||||
let mut entries = self.undo_stack.iter_mut();
|
let mut entries = self.undo_stack.iter();
|
||||||
|
|
||||||
if let Some(mut entry) = entries.next_back() {
|
if let Some(mut entry) = entries.next_back() {
|
||||||
while let Some(prev_entry) = entries.next_back() {
|
while let Some(prev_entry) = entries.next_back() {
|
||||||
if !prev_entry.suppress_grouping
|
if !prev_entry.suppress_grouping
|
||||||
&& entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval
|
&& entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval
|
||||||
&& entry.transaction.start == prev_entry.transaction.end
|
|
||||||
{
|
{
|
||||||
entry = prev_entry;
|
entry = prev_entry;
|
||||||
new_len -= 1;
|
count += 1;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.group_trailing(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_until(&mut self, transaction_id: TransactionId) {
|
||||||
|
let mut count = 0;
|
||||||
|
for entry in self.undo_stack.iter().rev() {
|
||||||
|
if entry.transaction_id() == transaction_id {
|
||||||
|
self.group_trailing(count);
|
||||||
|
break;
|
||||||
|
} else if entry.suppress_grouping {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_trailing(&mut self, n: usize) -> Option<TransactionId> {
|
||||||
|
let new_len = self.undo_stack.len() - n;
|
||||||
let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len);
|
let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len);
|
||||||
if let Some(last_entry) = entries_to_keep.last_mut() {
|
if let Some(last_entry) = entries_to_keep.last_mut() {
|
||||||
for entry in &*entries_to_merge {
|
for entry in &*entries_to_merge {
|
||||||
for edit_id in &entry.transaction.edit_ids {
|
for edit_id in &entry.transaction.edit_ids {
|
||||||
last_entry.push_edit(self.operations[edit_id].as_edit().unwrap());
|
last_entry.transaction.edit_ids.push(*edit_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(entry) = entries_to_merge.last_mut() {
|
if let Some(entry) = entries_to_merge.last_mut() {
|
||||||
last_entry.last_edit_at = entry.last_edit_at;
|
last_entry.last_edit_at = entry.last_edit_at;
|
||||||
last_entry.transaction.end = entry.transaction.end.clone();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,9 +261,9 @@ impl History {
|
||||||
|
|
||||||
fn push_undo(&mut self, op_id: clock::Local) {
|
fn push_undo(&mut self, op_id: clock::Local) {
|
||||||
assert_ne!(self.transaction_depth, 0);
|
assert_ne!(self.transaction_depth, 0);
|
||||||
if let Some(Operation::Edit(edit)) = self.operations.get(&op_id) {
|
if let Some(Operation::Edit(_)) = self.operations.get(&op_id) {
|
||||||
let last_transaction = self.undo_stack.last_mut().unwrap();
|
let last_transaction = self.undo_stack.last_mut().unwrap();
|
||||||
last_transaction.push_edit(&edit);
|
last_transaction.transaction.edit_ids.push(op_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,7 +512,6 @@ pub struct EditOperation {
|
||||||
pub struct UndoOperation {
|
pub struct UndoOperation {
|
||||||
pub id: clock::Local,
|
pub id: clock::Local,
|
||||||
pub counts: HashMap<clock::Local, u32>,
|
pub counts: HashMap<clock::Local, u32>,
|
||||||
pub transaction_ranges: Vec<Range<FullOffset>>,
|
|
||||||
pub transaction_version: clock::Global,
|
pub transaction_version: clock::Global,
|
||||||
pub version: clock::Global,
|
pub version: clock::Global,
|
||||||
}
|
}
|
||||||
|
@ -677,6 +641,7 @@ impl Buffer {
|
||||||
};
|
};
|
||||||
let mut new_insertions = Vec::new();
|
let mut new_insertions = Vec::new();
|
||||||
let mut insertion_offset = 0;
|
let mut insertion_offset = 0;
|
||||||
|
let mut insertion_slices = Vec::new();
|
||||||
|
|
||||||
let mut edits = edits
|
let mut edits = edits
|
||||||
.map(|(range, new_text)| (range.to_offset(&*self), new_text))
|
.map(|(range, new_text)| (range.to_offset(&*self), new_text))
|
||||||
|
@ -735,10 +700,6 @@ impl Buffer {
|
||||||
if !new_text.is_empty() {
|
if !new_text.is_empty() {
|
||||||
let new_start = new_fragments.summary().text.visible;
|
let new_start = new_fragments.summary().text.visible;
|
||||||
|
|
||||||
edits_patch.push(Edit {
|
|
||||||
old: fragment_start..fragment_start,
|
|
||||||
new: new_start..new_start + new_text.len(),
|
|
||||||
});
|
|
||||||
let fragment = Fragment {
|
let fragment = Fragment {
|
||||||
id: Locator::between(
|
id: Locator::between(
|
||||||
&new_fragments.summary().max_id,
|
&new_fragments.summary().max_id,
|
||||||
|
@ -753,6 +714,11 @@ impl Buffer {
|
||||||
max_undos: Default::default(),
|
max_undos: Default::default(),
|
||||||
visible: true,
|
visible: true,
|
||||||
};
|
};
|
||||||
|
edits_patch.push(Edit {
|
||||||
|
old: fragment_start..fragment_start,
|
||||||
|
new: new_start..new_start + new_text.len(),
|
||||||
|
});
|
||||||
|
insertion_slices.push(fragment.insertion_slice());
|
||||||
new_insertions.push(InsertionFragment::insert_new(&fragment));
|
new_insertions.push(InsertionFragment::insert_new(&fragment));
|
||||||
new_ropes.push_str(new_text.as_ref());
|
new_ropes.push_str(new_text.as_ref());
|
||||||
new_fragments.push(fragment, &None);
|
new_fragments.push(fragment, &None);
|
||||||
|
@ -781,6 +747,7 @@ impl Buffer {
|
||||||
old: fragment_start..intersection_end,
|
old: fragment_start..intersection_end,
|
||||||
new: new_start..new_start,
|
new: new_start..new_start,
|
||||||
});
|
});
|
||||||
|
insertion_slices.push(intersection.insertion_slice());
|
||||||
}
|
}
|
||||||
new_insertions.push(InsertionFragment::insert_new(&intersection));
|
new_insertions.push(InsertionFragment::insert_new(&intersection));
|
||||||
new_ropes.push_fragment(&intersection, fragment.visible);
|
new_ropes.push_fragment(&intersection, fragment.visible);
|
||||||
|
@ -823,6 +790,9 @@ impl Buffer {
|
||||||
self.snapshot.visible_text = visible_text;
|
self.snapshot.visible_text = visible_text;
|
||||||
self.snapshot.deleted_text = deleted_text;
|
self.snapshot.deleted_text = deleted_text;
|
||||||
self.subscriptions.publish_mut(&edits_patch);
|
self.subscriptions.publish_mut(&edits_patch);
|
||||||
|
self.history
|
||||||
|
.insertion_slices
|
||||||
|
.insert(timestamp.local(), insertion_slices);
|
||||||
edit_op
|
edit_op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -892,6 +862,7 @@ impl Buffer {
|
||||||
|
|
||||||
let edits = ranges.into_iter().zip(new_text.into_iter());
|
let edits = ranges.into_iter().zip(new_text.into_iter());
|
||||||
let mut edits_patch = Patch::default();
|
let mut edits_patch = Patch::default();
|
||||||
|
let mut insertion_slices = Vec::new();
|
||||||
let cx = Some(version.clone());
|
let cx = Some(version.clone());
|
||||||
let mut new_insertions = Vec::new();
|
let mut new_insertions = Vec::new();
|
||||||
let mut insertion_offset = 0;
|
let mut insertion_offset = 0;
|
||||||
|
@ -982,10 +953,6 @@ impl Buffer {
|
||||||
old_start += fragment_start.0 - old_fragments.start().0.full_offset().0;
|
old_start += fragment_start.0 - old_fragments.start().0.full_offset().0;
|
||||||
}
|
}
|
||||||
let new_start = new_fragments.summary().text.visible;
|
let new_start = new_fragments.summary().text.visible;
|
||||||
edits_patch.push(Edit {
|
|
||||||
old: old_start..old_start,
|
|
||||||
new: new_start..new_start + new_text.len(),
|
|
||||||
});
|
|
||||||
let fragment = Fragment {
|
let fragment = Fragment {
|
||||||
id: Locator::between(
|
id: Locator::between(
|
||||||
&new_fragments.summary().max_id,
|
&new_fragments.summary().max_id,
|
||||||
|
@ -1000,6 +967,11 @@ impl Buffer {
|
||||||
max_undos: Default::default(),
|
max_undos: Default::default(),
|
||||||
visible: true,
|
visible: true,
|
||||||
};
|
};
|
||||||
|
edits_patch.push(Edit {
|
||||||
|
old: old_start..old_start,
|
||||||
|
new: new_start..new_start + new_text.len(),
|
||||||
|
});
|
||||||
|
insertion_slices.push(fragment.insertion_slice());
|
||||||
new_insertions.push(InsertionFragment::insert_new(&fragment));
|
new_insertions.push(InsertionFragment::insert_new(&fragment));
|
||||||
new_ropes.push_str(new_text);
|
new_ropes.push_str(new_text);
|
||||||
new_fragments.push(fragment, &None);
|
new_fragments.push(fragment, &None);
|
||||||
|
@ -1021,6 +993,7 @@ impl Buffer {
|
||||||
Locator::between(&new_fragments.summary().max_id, &intersection.id);
|
Locator::between(&new_fragments.summary().max_id, &intersection.id);
|
||||||
intersection.deletions.insert(timestamp.local());
|
intersection.deletions.insert(timestamp.local());
|
||||||
intersection.visible = false;
|
intersection.visible = false;
|
||||||
|
insertion_slices.push(intersection.insertion_slice());
|
||||||
}
|
}
|
||||||
if intersection.len > 0 {
|
if intersection.len > 0 {
|
||||||
if fragment.visible && !intersection.visible {
|
if fragment.visible && !intersection.visible {
|
||||||
|
@ -1068,43 +1041,70 @@ impl Buffer {
|
||||||
self.snapshot.visible_text = visible_text;
|
self.snapshot.visible_text = visible_text;
|
||||||
self.snapshot.deleted_text = deleted_text;
|
self.snapshot.deleted_text = deleted_text;
|
||||||
self.snapshot.insertions.edit(new_insertions, &());
|
self.snapshot.insertions.edit(new_insertions, &());
|
||||||
|
self.history
|
||||||
|
.insertion_slices
|
||||||
|
.insert(timestamp.local(), insertion_slices);
|
||||||
self.subscriptions.publish_mut(&edits_patch)
|
self.subscriptions.publish_mut(&edits_patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fragment_ids_for_edits<'a>(
|
||||||
|
&'a self,
|
||||||
|
edit_ids: impl Iterator<Item = &'a clock::Local>,
|
||||||
|
) -> Vec<&'a Locator> {
|
||||||
|
// Get all of the insertion slices changed by the given edits.
|
||||||
|
let mut insertion_slices = Vec::new();
|
||||||
|
for edit_id in edit_ids {
|
||||||
|
if let Some(slices) = self.history.insertion_slices.get(edit_id) {
|
||||||
|
insertion_slices.extend_from_slice(slices)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
insertion_slices
|
||||||
|
.sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end)));
|
||||||
|
|
||||||
|
// Get all of the fragments corresponding to these insertion slices.
|
||||||
|
let mut fragment_ids = Vec::new();
|
||||||
|
let mut insertions_cursor = self.insertions.cursor::<InsertionFragmentKey>();
|
||||||
|
for insertion_slice in &insertion_slices {
|
||||||
|
if insertion_slice.insertion_id != insertions_cursor.start().timestamp
|
||||||
|
|| insertion_slice.range.start > insertions_cursor.start().split_offset
|
||||||
|
{
|
||||||
|
insertions_cursor.seek_forward(
|
||||||
|
&InsertionFragmentKey {
|
||||||
|
timestamp: insertion_slice.insertion_id,
|
||||||
|
split_offset: insertion_slice.range.start,
|
||||||
|
},
|
||||||
|
Bias::Left,
|
||||||
|
&(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
while let Some(item) = insertions_cursor.item() {
|
||||||
|
if item.timestamp != insertion_slice.insertion_id
|
||||||
|
|| item.split_offset >= insertion_slice.range.end
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fragment_ids.push(&item.fragment_id);
|
||||||
|
insertions_cursor.next(&());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fragment_ids.sort_unstable();
|
||||||
|
fragment_ids
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> {
|
fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> {
|
||||||
let mut edits = Patch::default();
|
|
||||||
self.snapshot.undo_map.insert(undo);
|
self.snapshot.undo_map.insert(undo);
|
||||||
|
|
||||||
let mut cx = undo.transaction_version.clone();
|
let mut edits = Patch::default();
|
||||||
for edit_id in undo.counts.keys().copied() {
|
let mut old_fragments = self.fragments.cursor::<(Option<&Locator>, usize)>();
|
||||||
cx.observe(edit_id);
|
let mut new_fragments = SumTree::new();
|
||||||
}
|
|
||||||
let cx = Some(cx);
|
|
||||||
|
|
||||||
let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
|
||||||
let mut new_fragments = old_fragments.slice(
|
|
||||||
&VersionedFullOffset::Offset(undo.transaction_ranges[0].start),
|
|
||||||
Bias::Right,
|
|
||||||
&cx,
|
|
||||||
);
|
|
||||||
let mut new_ropes =
|
let mut new_ropes =
|
||||||
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
|
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
|
||||||
new_ropes.push_tree(new_fragments.summary().text);
|
|
||||||
|
|
||||||
for range in &undo.transaction_ranges {
|
for fragment_id in self.fragment_ids_for_edits(undo.counts.keys()) {
|
||||||
let mut end_offset = old_fragments.end(&cx).0.full_offset();
|
let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None);
|
||||||
|
|
||||||
if end_offset < range.start {
|
|
||||||
let preceding_fragments = old_fragments.slice(
|
|
||||||
&VersionedFullOffset::Offset(range.start),
|
|
||||||
Bias::Right,
|
|
||||||
&cx,
|
|
||||||
);
|
|
||||||
new_ropes.push_tree(preceding_fragments.summary().text);
|
new_ropes.push_tree(preceding_fragments.summary().text);
|
||||||
new_fragments.push_tree(preceding_fragments, &None);
|
new_fragments.push_tree(preceding_fragments, &None);
|
||||||
}
|
|
||||||
|
|
||||||
while end_offset <= range.end {
|
|
||||||
if let Some(fragment) = old_fragments.item() {
|
if let Some(fragment) = old_fragments.item() {
|
||||||
let mut fragment = fragment.clone();
|
let mut fragment = fragment.clone();
|
||||||
let fragment_was_visible = fragment.visible;
|
let fragment_was_visible = fragment.visible;
|
||||||
|
@ -1134,24 +1134,11 @@ impl Buffer {
|
||||||
new_ropes.push_fragment(&fragment, fragment_was_visible);
|
new_ropes.push_fragment(&fragment, fragment_was_visible);
|
||||||
new_fragments.push(fragment, &None);
|
new_fragments.push(fragment, &None);
|
||||||
|
|
||||||
old_fragments.next(&cx);
|
old_fragments.next(&None);
|
||||||
if end_offset == old_fragments.end(&cx).0.full_offset() {
|
|
||||||
let unseen_fragments = old_fragments.slice(
|
|
||||||
&VersionedFullOffset::Offset(end_offset),
|
|
||||||
Bias::Right,
|
|
||||||
&cx,
|
|
||||||
);
|
|
||||||
new_ropes.push_tree(unseen_fragments.summary().text);
|
|
||||||
new_fragments.push_tree(unseen_fragments, &None);
|
|
||||||
}
|
|
||||||
end_offset = old_fragments.end(&cx).0.full_offset();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let suffix = old_fragments.suffix(&cx);
|
let suffix = old_fragments.suffix(&None);
|
||||||
new_ropes.push_tree(suffix.summary().text);
|
new_ropes.push_tree(suffix.summary().text);
|
||||||
new_fragments.push_tree(suffix, &None);
|
new_fragments.push_tree(suffix, &None);
|
||||||
|
|
||||||
|
@ -1225,6 +1212,10 @@ impl Buffer {
|
||||||
self.history.finalize_last_transaction()
|
self.history.finalize_last_transaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn group_until_transaction(&mut self, transaction_id: TransactionId) {
|
||||||
|
self.history.group_until(transaction_id);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn base_text(&self) -> &Arc<str> {
|
pub fn base_text(&self) -> &Arc<str> {
|
||||||
&self.history.base_text
|
&self.history.base_text
|
||||||
}
|
}
|
||||||
|
@ -1302,7 +1293,6 @@ impl Buffer {
|
||||||
id: self.local_clock.tick(),
|
id: self.local_clock.tick(),
|
||||||
version: self.version(),
|
version: self.version(),
|
||||||
counts,
|
counts,
|
||||||
transaction_ranges: transaction.ranges,
|
|
||||||
transaction_version: transaction.start.clone(),
|
transaction_version: transaction.start.clone(),
|
||||||
};
|
};
|
||||||
self.apply_undo(&undo)?;
|
self.apply_undo(&undo)?;
|
||||||
|
@ -1320,6 +1310,55 @@ impl Buffer {
|
||||||
self.history.finalize_last_transaction();
|
self.history.finalize_last_transaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn edited_ranges_for_transaction<'a, D>(
|
||||||
|
&'a self,
|
||||||
|
transaction: &'a Transaction,
|
||||||
|
) -> impl 'a + Iterator<Item = Range<D>>
|
||||||
|
where
|
||||||
|
D: TextDimension,
|
||||||
|
{
|
||||||
|
// get fragment ranges
|
||||||
|
let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>();
|
||||||
|
let offset_ranges = self
|
||||||
|
.fragment_ids_for_edits(transaction.edit_ids.iter())
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(move |fragment_id| {
|
||||||
|
cursor.seek_forward(&Some(fragment_id), Bias::Left, &None);
|
||||||
|
let fragment = cursor.item()?;
|
||||||
|
let start_offset = cursor.start().1;
|
||||||
|
let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 };
|
||||||
|
Some(start_offset..end_offset)
|
||||||
|
});
|
||||||
|
|
||||||
|
// combine adjacent ranges
|
||||||
|
let mut prev_range: Option<Range<usize>> = None;
|
||||||
|
let disjoint_ranges = offset_ranges
|
||||||
|
.map(Some)
|
||||||
|
.chain([None])
|
||||||
|
.filter_map(move |range| {
|
||||||
|
if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) {
|
||||||
|
if prev_range.end == range.start {
|
||||||
|
prev_range.end = range.end;
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = prev_range.clone();
|
||||||
|
prev_range = range;
|
||||||
|
result
|
||||||
|
});
|
||||||
|
|
||||||
|
// convert to the desired text dimension.
|
||||||
|
let mut position = D::default();
|
||||||
|
let mut rope_cursor = self.visible_text.cursor(0);
|
||||||
|
disjoint_ranges.map(move |range| {
|
||||||
|
position.add_assign(&rope_cursor.summary(range.start));
|
||||||
|
let start = position.clone();
|
||||||
|
position.add_assign(&rope_cursor.summary(range.end));
|
||||||
|
let end = position.clone();
|
||||||
|
start..end
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn subscribe(&mut self) -> Subscription {
|
pub fn subscribe(&mut self) -> Subscription {
|
||||||
self.subscriptions.subscribe()
|
self.subscriptions.subscribe()
|
||||||
}
|
}
|
||||||
|
@ -1621,6 +1660,14 @@ impl BufferSnapshot {
|
||||||
self.visible_text.point_utf16_to_point(point)
|
self.visible_text.point_utf16_to_point(point)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize {
|
||||||
|
self.visible_text.offset_utf16_to_offset(offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 {
|
||||||
|
self.visible_text.offset_to_offset_utf16(offset)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn offset_to_point(&self, offset: usize) -> Point {
|
pub fn offset_to_point(&self, offset: usize) -> Point {
|
||||||
self.visible_text.offset_to_point(offset)
|
self.visible_text.offset_to_point(offset)
|
||||||
}
|
}
|
||||||
|
@ -1854,6 +1901,10 @@ impl BufferSnapshot {
|
||||||
self.visible_text.clip_point(point, bias)
|
self.visible_text.clip_point(point, bias)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 {
|
||||||
|
self.visible_text.clip_offset_utf16(offset, bias)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
pub fn clip_point_utf16(&self, point: PointUtf16, bias: Bias) -> PointUtf16 {
|
||||||
self.visible_text.clip_point_utf16(point, bias)
|
self.visible_text.clip_point_utf16(point, bias)
|
||||||
}
|
}
|
||||||
|
@ -1868,42 +1919,6 @@ impl BufferSnapshot {
|
||||||
self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
|
self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edited_ranges_for_transaction<'a, D>(
|
|
||||||
&'a self,
|
|
||||||
transaction: &'a Transaction,
|
|
||||||
) -> impl 'a + Iterator<Item = Range<D>>
|
|
||||||
where
|
|
||||||
D: TextDimension,
|
|
||||||
{
|
|
||||||
let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>();
|
|
||||||
let mut rope_cursor = self.visible_text.cursor(0);
|
|
||||||
let cx = Some(transaction.end.clone());
|
|
||||||
let mut position = D::default();
|
|
||||||
transaction.ranges.iter().map(move |range| {
|
|
||||||
cursor.seek_forward(&VersionedFullOffset::Offset(range.start), Bias::Right, &cx);
|
|
||||||
let mut start_offset = cursor.start().1;
|
|
||||||
if cursor
|
|
||||||
.item()
|
|
||||||
.map_or(false, |fragment| fragment.is_visible(&self.undo_map))
|
|
||||||
{
|
|
||||||
start_offset += range.start - cursor.start().0.full_offset()
|
|
||||||
}
|
|
||||||
position.add_assign(&rope_cursor.summary(start_offset));
|
|
||||||
let start = position.clone();
|
|
||||||
|
|
||||||
cursor.seek_forward(&VersionedFullOffset::Offset(range.end), Bias::Left, &cx);
|
|
||||||
let mut end_offset = cursor.start().1;
|
|
||||||
if cursor
|
|
||||||
.item()
|
|
||||||
.map_or(false, |fragment| fragment.is_visible(&self.undo_map))
|
|
||||||
{
|
|
||||||
end_offset += range.end - cursor.start().0.full_offset();
|
|
||||||
}
|
|
||||||
position.add_assign(&rope_cursor.summary(end_offset));
|
|
||||||
start..position.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn edits_since_in_range<'a, D>(
|
pub fn edits_since_in_range<'a, D>(
|
||||||
&'a self,
|
&'a self,
|
||||||
since: &'a clock::Global,
|
since: &'a clock::Global,
|
||||||
|
@ -2090,6 +2105,13 @@ impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator fo
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fragment {
|
impl Fragment {
|
||||||
|
fn insertion_slice(&self) -> InsertionSlice {
|
||||||
|
InsertionSlice {
|
||||||
|
insertion_id: self.insertion_timestamp.local(),
|
||||||
|
range: self.insertion_offset..self.insertion_offset + self.len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_visible(&self, undos: &UndoMap) -> bool {
|
fn is_visible(&self, undos: &UndoMap) -> bool {
|
||||||
!undos.is_undone(self.insertion_timestamp.local())
|
!undos.is_undone(self.insertion_timestamp.local())
|
||||||
&& self.deletions.iter().all(|d| undos.is_undone(*d))
|
&& self.deletions.iter().all(|d| undos.is_undone(*d))
|
||||||
|
@ -2423,6 +2445,12 @@ impl ToOffset for usize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToOffset for OffsetUtf16 {
|
||||||
|
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||||
|
snapshot.offset_utf16_to_offset(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToOffset for Anchor {
|
impl ToOffset for Anchor {
|
||||||
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
fn to_offset<'a>(&self, snapshot: &BufferSnapshot) -> usize {
|
||||||
snapshot.summary_for_anchor(self)
|
snapshot.summary_for_anchor(self)
|
||||||
|
@ -2491,6 +2519,28 @@ impl ToPointUtf16 for Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait ToOffsetUtf16 {
|
||||||
|
fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for Anchor {
|
||||||
|
fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||||
|
snapshot.summary_for_anchor(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for usize {
|
||||||
|
fn to_offset_utf16<'a>(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||||
|
snapshot.offset_to_offset_utf16(*self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToOffsetUtf16 for OffsetUtf16 {
|
||||||
|
fn to_offset_utf16<'a>(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Clip {
|
pub trait Clip {
|
||||||
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self;
|
fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self;
|
||||||
}
|
}
|
||||||
|
|
|
@ -452,6 +452,7 @@ pub struct Editor {
|
||||||
pub unnecessary_code_fade: f32,
|
pub unnecessary_code_fade: f32,
|
||||||
pub hover_popover: HoverPopover,
|
pub hover_popover: HoverPopover,
|
||||||
pub link_definition: HighlightStyle,
|
pub link_definition: HighlightStyle,
|
||||||
|
pub composition_mark: HighlightStyle,
|
||||||
pub jump_icon: Interactive<IconButton>,
|
pub jump_icon: Interactive<IconButton>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,21 @@ fn editor_focused(EditorFocused(editor): &EditorFocused, cx: &mut MutableAppCont
|
||||||
vim.active_editor = Some(editor.downgrade());
|
vim.active_editor = Some(editor.downgrade());
|
||||||
vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
|
vim.selection_subscription = Some(cx.subscribe(editor, |editor, event, cx| {
|
||||||
if editor.read(cx).leader_replica_id().is_none() {
|
if editor.read(cx).leader_replica_id().is_none() {
|
||||||
if let editor::Event::SelectionsChanged { local: true } = event {
|
match event {
|
||||||
let newest_empty = editor.read(cx).selections.newest::<usize>(cx).is_empty();
|
editor::Event::SelectionsChanged { local: true } => {
|
||||||
|
let newest_empty =
|
||||||
|
editor.read(cx).selections.newest::<usize>(cx).is_empty();
|
||||||
editor_local_selections_changed(newest_empty, cx);
|
editor_local_selections_changed(newest_empty, cx);
|
||||||
}
|
}
|
||||||
|
editor::Event::IgnoredInput => {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
if vim.active_operator().is_some() {
|
||||||
|
vim.clear_operator(cx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod visual;
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use command_palette::CommandPaletteFilter;
|
use command_palette::CommandPaletteFilter;
|
||||||
use editor::{Bias, Cancel, CursorShape, Editor, Input};
|
use editor::{Bias, Cancel, CursorShape, Editor};
|
||||||
use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle};
|
use gpui::{impl_actions, MutableAppContext, Subscription, ViewContext, WeakViewHandle};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -45,16 +45,6 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Editor Actions
|
// Editor Actions
|
||||||
cx.add_action(|_: &mut Editor, _: &Input, cx| {
|
|
||||||
// If we have an unbound input with an active operator, cancel that operator. Otherwise forward
|
|
||||||
// the input to the editor
|
|
||||||
if Vim::read(cx).active_operator().is_some() {
|
|
||||||
// Defer without updating editor
|
|
||||||
MutableAppContext::defer(cx, |cx| Vim::update(cx, |vim, cx| vim.clear_operator(cx)))
|
|
||||||
} else {
|
|
||||||
cx.propagate_action()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cx.add_action(|_: &mut Editor, _: &Cancel, cx| {
|
cx.add_action(|_: &mut Editor, _: &Cancel, cx| {
|
||||||
// If we are in a non normal mode or have an active operator, swap to normal mode
|
// If we are in a non normal mode or have an active operator, swap to normal mode
|
||||||
// Otherwise forward cancel on to the editor
|
// Otherwise forward cancel on to the editor
|
||||||
|
|
|
@ -43,6 +43,7 @@ use std::{
|
||||||
fmt,
|
fmt,
|
||||||
future::Future,
|
future::Future,
|
||||||
mem,
|
mem,
|
||||||
|
ops::Range,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
|
@ -2546,6 +2547,18 @@ impl Element for AvatarRibbon {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rect_for_text_range(
|
||||||
|
&self,
|
||||||
|
_: Range<usize>,
|
||||||
|
_: RectF,
|
||||||
|
_: RectF,
|
||||||
|
_: &Self::LayoutState,
|
||||||
|
_: &Self::PaintState,
|
||||||
|
_: &gpui::MeasurementContext,
|
||||||
|
) -> Option<RectF> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn debug(
|
fn debug(
|
||||||
&self,
|
&self,
|
||||||
bounds: gpui::geometry::rect::RectF,
|
bounds: gpui::geometry::rect::RectF,
|
||||||
|
|
|
@ -131,6 +131,10 @@ pub fn menus() -> Vec<Menu<'static>> {
|
||||||
name: "Toggle Line Comment",
|
name: "Toggle Line Comment",
|
||||||
action: Box::new(editor::ToggleComments),
|
action: Box::new(editor::ToggleComments),
|
||||||
},
|
},
|
||||||
|
MenuItem::Action {
|
||||||
|
name: "Emoji & Symbols",
|
||||||
|
action: Box::new(editor::ShowCharacterPalette),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
Menu {
|
Menu {
|
||||||
|
|
|
@ -920,11 +920,7 @@ mod tests {
|
||||||
item.downcast::<Editor>().unwrap()
|
item.downcast::<Editor>().unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.update(|cx| {
|
cx.update(|cx| editor.update(cx, |editor, cx| editor.handle_input("x", cx)));
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
editor.handle_input(&editor::Input("x".into()), cx)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
app_state
|
app_state
|
||||||
.fs
|
.fs
|
||||||
.as_fake()
|
.as_fake()
|
||||||
|
@ -971,7 +967,7 @@ mod tests {
|
||||||
editor.language_at(0, cx).unwrap(),
|
editor.language_at(0, cx).unwrap(),
|
||||||
&languages::PLAIN_TEXT
|
&languages::PLAIN_TEXT
|
||||||
));
|
));
|
||||||
editor.handle_input(&editor::Input("hi".into()), cx);
|
editor.handle_input("hi", cx);
|
||||||
assert!(editor.is_dirty(cx));
|
assert!(editor.is_dirty(cx));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -997,7 +993,7 @@ mod tests {
|
||||||
|
|
||||||
// Edit the file and save it again. This time, there is no filename prompt.
|
// Edit the file and save it again. This time, there is no filename prompt.
|
||||||
editor.update(cx, |editor, cx| {
|
editor.update(cx, |editor, cx| {
|
||||||
editor.handle_input(&editor::Input(" there".into()), cx);
|
editor.handle_input(" there", cx);
|
||||||
assert_eq!(editor.is_dirty(cx.as_ref()), true);
|
assert_eq!(editor.is_dirty(cx.as_ref()), true);
|
||||||
});
|
});
|
||||||
let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
|
let save_task = workspace.update(cx, |workspace, cx| workspace.save_active_item(false, cx));
|
||||||
|
@ -1057,7 +1053,7 @@ mod tests {
|
||||||
editor.language_at(0, cx).unwrap(),
|
editor.language_at(0, cx).unwrap(),
|
||||||
&languages::PLAIN_TEXT
|
&languages::PLAIN_TEXT
|
||||||
));
|
));
|
||||||
editor.handle_input(&editor::Input("hi".into()), cx);
|
editor.handle_input("hi", cx);
|
||||||
assert!(editor.is_dirty(cx.as_ref()));
|
assert!(editor.is_dirty(cx.as_ref()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
1
styles/package-lock.json
generated
1
styles/package-lock.json
generated
|
@ -5,6 +5,7 @@
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "styles",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Theme from "../themes/common/theme";
|
||||||
import {
|
import {
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
border,
|
border,
|
||||||
|
borderColor,
|
||||||
iconColor,
|
iconColor,
|
||||||
player,
|
player,
|
||||||
popoverShadow,
|
popoverShadow,
|
||||||
|
@ -138,8 +139,8 @@ export default function editor(theme: Theme) {
|
||||||
invalidHintDiagnostic: diagnostic(theme, "muted"),
|
invalidHintDiagnostic: diagnostic(theme, "muted"),
|
||||||
invalidInformationDiagnostic: diagnostic(theme, "muted"),
|
invalidInformationDiagnostic: diagnostic(theme, "muted"),
|
||||||
invalidWarningDiagnostic: diagnostic(theme, "muted"),
|
invalidWarningDiagnostic: diagnostic(theme, "muted"),
|
||||||
hover_popover: hoverPopover(theme),
|
hoverPopover: hoverPopover(theme),
|
||||||
link_definition: {
|
linkDefinition: {
|
||||||
color: theme.syntax.linkUri.color,
|
color: theme.syntax.linkUri.color,
|
||||||
underline: theme.syntax.linkUri.underline,
|
underline: theme.syntax.linkUri.underline,
|
||||||
},
|
},
|
||||||
|
@ -159,6 +160,12 @@ export default function editor(theme: Theme) {
|
||||||
background: backgroundColor(theme, "on500", "base"),
|
background: backgroundColor(theme, "on500", "base"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
compositionMark: {
|
||||||
|
underline: {
|
||||||
|
thickness: 1.0,
|
||||||
|
color: borderColor(theme, "active")
|
||||||
|
},
|
||||||
|
},
|
||||||
syntax,
|
syntax,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue