Visual line mode handles soft wraps
This commit is contained in:
parent
33940b5dd9
commit
61f0daa5c5
14 changed files with 314 additions and 96 deletions
|
@ -9,7 +9,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"h": "vim::Left",
|
"h": "vim::Left",
|
||||||
"backspace": "vim::Left",
|
"backspace": "editor::Backspace", // "vim::Left",
|
||||||
"j": "vim::Down",
|
"j": "vim::Down",
|
||||||
"k": "vim::Up",
|
"k": "vim::Up",
|
||||||
"l": "vim::Right",
|
"l": "vim::Right",
|
||||||
|
@ -57,6 +57,10 @@
|
||||||
"Delete"
|
"Delete"
|
||||||
],
|
],
|
||||||
"shift-D": "vim::DeleteToEndOfLine",
|
"shift-D": "vim::DeleteToEndOfLine",
|
||||||
|
"y": [
|
||||||
|
"vim::PushOperator",
|
||||||
|
"Yank"
|
||||||
|
],
|
||||||
"i": [
|
"i": [
|
||||||
"vim::SwitchMode",
|
"vim::SwitchMode",
|
||||||
"Insert"
|
"Insert"
|
||||||
|
@ -77,7 +81,10 @@
|
||||||
"vim::SwitchMode",
|
"vim::SwitchMode",
|
||||||
"VisualLine"
|
"VisualLine"
|
||||||
],
|
],
|
||||||
"p": "vim::Paste"
|
"p": "vim::Paste",
|
||||||
|
"u": "editor::Undo",
|
||||||
|
"ctrl-r": "editor::Redo",
|
||||||
|
"ctrl-o": "pane::GoBack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -109,12 +116,19 @@
|
||||||
"d": "vim::CurrentLine"
|
"d": "vim::CurrentLine"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "Editor && vim_operator == y",
|
||||||
|
"bindings": {
|
||||||
|
"y": "vim::CurrentLine"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && vim_mode == visual",
|
"context": "Editor && vim_mode == visual",
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"c": "vim::VisualChange",
|
"c": "vim::VisualChange",
|
||||||
"d": "vim::VisualDelete",
|
"d": "vim::VisualDelete",
|
||||||
"x": "vim::VisualDelete"
|
"x": "vim::VisualDelete",
|
||||||
|
"y": "vim::VisualYank"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -122,7 +136,8 @@
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"c": "vim::VisualLineChange",
|
"c": "vim::VisualLineChange",
|
||||||
"d": "vim::VisualLineDelete",
|
"d": "vim::VisualLineDelete",
|
||||||
"x": "vim::VisualLineDelete"
|
"x": "vim::VisualLineDelete",
|
||||||
|
"y": "vim::VisualLineYank"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -279,6 +279,18 @@ impl DisplaySnapshot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand_to_line(&self, mut range: Range<Point>) -> Range<Point> {
|
||||||
|
(range.start, _) = self.prev_line_boundary(range.start);
|
||||||
|
(range.end, _) = self.next_line_boundary(range.end);
|
||||||
|
|
||||||
|
if range.is_empty() && range.start.row > 0 {
|
||||||
|
range.start.row -= 1;
|
||||||
|
range.start.column = self.buffer_snapshot.line_len(range.start.row);
|
||||||
|
}
|
||||||
|
|
||||||
|
range
|
||||||
|
}
|
||||||
|
|
||||||
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint {
|
||||||
let fold_point = self.folds_snapshot.to_fold_point(point, bias);
|
let fold_point = self.folds_snapshot.to_fold_point(point, bias);
|
||||||
let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
|
let tab_point = self.tabs_snapshot.to_tab_point(fold_point);
|
||||||
|
|
|
@ -1860,7 +1860,7 @@ impl Editor {
|
||||||
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
pub fn insert(&mut self, text: &str, cx: &mut ViewContext<Self>) {
|
||||||
let text: Arc<str> = text.into();
|
let text: Arc<str> = text.into();
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
let old_selections = this.selections.all::<usize>(cx);
|
let old_selections = this.selections.all_adjusted(cx);
|
||||||
let selection_anchors = this.buffer.update(cx, |buffer, cx| {
|
let selection_anchors = this.buffer.update(cx, |buffer, cx| {
|
||||||
let anchors = {
|
let anchors = {
|
||||||
let snapshot = buffer.read(cx);
|
let snapshot = buffer.read(cx);
|
||||||
|
@ -2750,7 +2750,7 @@ impl Editor {
|
||||||
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
|
||||||
let mut selections = self.selections.all::<Point>(cx);
|
let mut selections = self.selections.all::<Point>(cx);
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
if selection.is_empty() {
|
if selection.is_empty() && !self.selections.line_mode {
|
||||||
let old_head = selection.head();
|
let old_head = selection.head();
|
||||||
let mut new_head =
|
let mut new_head =
|
||||||
movement::left(&display_map, old_head.to_display_point(&display_map))
|
movement::left(&display_map, old_head.to_display_point(&display_map))
|
||||||
|
@ -2783,8 +2783,9 @@ impl Editor {
|
||||||
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
pub fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
|
let line_mode = s.line_mode;
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() {
|
if selection.is_empty() && !line_mode {
|
||||||
let cursor = movement::right(map, selection.head());
|
let cursor = movement::right(map, selection.head());
|
||||||
selection.set_head(cursor, SelectionGoal::None);
|
selection.set_head(cursor, SelectionGoal::None);
|
||||||
}
|
}
|
||||||
|
@ -2807,7 +2808,7 @@ impl Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut selections = self.selections.all::<Point>(cx);
|
let mut selections = self.selections.all_adjusted(cx);
|
||||||
if selections.iter().all(|s| s.is_empty()) {
|
if selections.iter().all(|s| s.is_empty()) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
this.buffer.update(cx, |buffer, cx| {
|
this.buffer.update(cx, |buffer, cx| {
|
||||||
|
@ -3347,7 +3348,7 @@ impl Editor {
|
||||||
{
|
{
|
||||||
let max_point = buffer.max_point();
|
let max_point = buffer.max_point();
|
||||||
for selection in &mut selections {
|
for selection in &mut selections {
|
||||||
let is_entire_line = selection.is_empty();
|
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
selection.start = Point::new(selection.start.row, 0);
|
selection.start = Point::new(selection.start.row, 0);
|
||||||
selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
|
selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
|
||||||
|
@ -3378,16 +3379,17 @@ impl Editor {
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
let buffer = self.buffer.read(cx).read(cx);
|
let buffer = self.buffer.read(cx).read(cx);
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
|
|
||||||
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
let mut clipboard_selections = Vec::with_capacity(selections.len());
|
||||||
{
|
{
|
||||||
let max_point = buffer.max_point();
|
let max_point = buffer.max_point();
|
||||||
for selection in selections.iter() {
|
for selection in selections.iter() {
|
||||||
let mut start = selection.start;
|
let mut start = selection.start;
|
||||||
let mut end = selection.end;
|
let mut end = selection.end;
|
||||||
let is_entire_line = selection.is_empty();
|
let is_entire_line = selection.is_empty() || self.selections.line_mode;
|
||||||
if is_entire_line {
|
if is_entire_line {
|
||||||
start = Point::new(start.row, 0);
|
start = Point::new(start.row, 0);
|
||||||
end = cmp::min(max_point, Point::new(start.row + 1, 0));
|
end = cmp::min(max_point, Point::new(end.row + 1, 0));
|
||||||
}
|
}
|
||||||
let mut len = 0;
|
let mut len = 0;
|
||||||
for chunk in buffer.text_for_range(start..end) {
|
for chunk in buffer.text_for_range(start..end) {
|
||||||
|
@ -3453,7 +3455,7 @@ impl Editor {
|
||||||
let line_start = selection.start - column;
|
let line_start = selection.start - column;
|
||||||
line_start..line_start
|
line_start..line_start
|
||||||
} else {
|
} else {
|
||||||
selection.start..selection.end
|
selection.range()
|
||||||
};
|
};
|
||||||
|
|
||||||
edits.push((range, to_insert));
|
edits.push((range, to_insert));
|
||||||
|
@ -3670,8 +3672,9 @@ impl Editor {
|
||||||
) {
|
) {
|
||||||
self.transact(cx, |this, cx| {
|
self.transact(cx, |this, cx| {
|
||||||
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
this.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
|
let line_mode = s.line_mode;
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if selection.is_empty() {
|
if selection.is_empty() && !line_mode {
|
||||||
let cursor = movement::previous_word_start(map, selection.head());
|
let cursor = movement::previous_word_start(map, selection.head());
|
||||||
selection.set_head(cursor, SelectionGoal::None);
|
selection.set_head(cursor, SelectionGoal::None);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,10 @@ use super::{
|
||||||
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
|
Anchor, DisplayPoint, Editor, EditorMode, EditorSnapshot, Input, Scroll, Select, SelectPhase,
|
||||||
SoftWrap, ToPoint, MAX_LINE_LEN,
|
SoftWrap, ToPoint, MAX_LINE_LEN,
|
||||||
};
|
};
|
||||||
use crate::{display_map::TransformBlock, EditorStyle};
|
use crate::{
|
||||||
|
display_map::{DisplaySnapshot, TransformBlock},
|
||||||
|
EditorStyle,
|
||||||
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{BTreeMap, HashMap};
|
use collections::{BTreeMap, HashMap};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -22,7 +25,7 @@ use gpui::{
|
||||||
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use json::json;
|
use json::json;
|
||||||
use language::{Bias, DiagnosticSeverity};
|
use language::{Bias, DiagnosticSeverity, Selection};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -32,6 +35,35 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SelectionLayout {
|
||||||
|
head: DisplayPoint,
|
||||||
|
range: Range<DisplayPoint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectionLayout {
|
||||||
|
fn from<T: ToPoint + ToDisplayPoint + Clone>(
|
||||||
|
selection: Selection<T>,
|
||||||
|
line_mode: bool,
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
) -> Self {
|
||||||
|
if line_mode {
|
||||||
|
let selection = selection.map(|p| p.to_point(&map.buffer_snapshot));
|
||||||
|
let point_range = map.expand_to_line(selection.range());
|
||||||
|
Self {
|
||||||
|
head: selection.head().to_display_point(map),
|
||||||
|
range: point_range.start.to_display_point(map)
|
||||||
|
..point_range.end.to_display_point(map),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let selection = selection.map(|p| p.to_display_point(map));
|
||||||
|
Self {
|
||||||
|
head: selection.head(),
|
||||||
|
range: selection.range(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct EditorElement {
|
pub struct EditorElement {
|
||||||
view: WeakViewHandle<Editor>,
|
view: WeakViewHandle<Editor>,
|
||||||
style: EditorStyle,
|
style: EditorStyle,
|
||||||
|
@ -345,19 +377,18 @@ impl EditorElement {
|
||||||
scroll_top,
|
scroll_top,
|
||||||
scroll_left,
|
scroll_left,
|
||||||
bounds,
|
bounds,
|
||||||
false,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cursors = SmallVec::<[Cursor; 32]>::new();
|
let mut cursors = SmallVec::<[Cursor; 32]>::new();
|
||||||
for ((replica_id, line_mode), selections) in &layout.selections {
|
for (replica_id, selections) in &layout.selections {
|
||||||
let selection_style = style.replica_selection_style(*replica_id);
|
let selection_style = style.replica_selection_style(*replica_id);
|
||||||
let corner_radius = 0.15 * layout.line_height;
|
let corner_radius = 0.15 * layout.line_height;
|
||||||
|
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
self.paint_highlighted_range(
|
self.paint_highlighted_range(
|
||||||
selection.start..selection.end,
|
selection.range.clone(),
|
||||||
start_row,
|
start_row,
|
||||||
end_row,
|
end_row,
|
||||||
selection_style.selection,
|
selection_style.selection,
|
||||||
|
@ -368,12 +399,11 @@ impl EditorElement {
|
||||||
scroll_top,
|
scroll_top,
|
||||||
scroll_left,
|
scroll_left,
|
||||||
bounds,
|
bounds,
|
||||||
*line_mode,
|
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
if view.show_local_cursors() || *replica_id != local_replica_id {
|
if view.show_local_cursors() || *replica_id != local_replica_id {
|
||||||
let cursor_position = selection.head();
|
let cursor_position = selection.head;
|
||||||
if (start_row..end_row).contains(&cursor_position.row()) {
|
if (start_row..end_row).contains(&cursor_position.row()) {
|
||||||
let cursor_row_layout =
|
let cursor_row_layout =
|
||||||
&layout.line_layouts[(cursor_position.row() - start_row) as usize];
|
&layout.line_layouts[(cursor_position.row() - start_row) as usize];
|
||||||
|
@ -485,11 +515,10 @@ impl EditorElement {
|
||||||
scroll_top: f32,
|
scroll_top: f32,
|
||||||
scroll_left: f32,
|
scroll_left: f32,
|
||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
line_mode: bool,
|
|
||||||
cx: &mut PaintContext,
|
cx: &mut PaintContext,
|
||||||
) {
|
) {
|
||||||
if range.start != range.end || line_mode {
|
if range.start != range.end {
|
||||||
let row_range = if range.end.column() == 0 && !line_mode {
|
let row_range = if range.end.column() == 0 {
|
||||||
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
|
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row(), end_row)
|
||||||
} else {
|
} else {
|
||||||
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
|
cmp::max(range.start.row(), start_row)..cmp::min(range.end.row() + 1, end_row)
|
||||||
|
@ -506,14 +535,14 @@ impl EditorElement {
|
||||||
.map(|row| {
|
.map(|row| {
|
||||||
let line_layout = &layout.line_layouts[(row - start_row) as usize];
|
let line_layout = &layout.line_layouts[(row - start_row) as usize];
|
||||||
HighlightedRangeLine {
|
HighlightedRangeLine {
|
||||||
start_x: if row == range.start.row() && !line_mode {
|
start_x: if row == range.start.row() {
|
||||||
content_origin.x()
|
content_origin.x()
|
||||||
+ line_layout.x_for_index(range.start.column() as usize)
|
+ line_layout.x_for_index(range.start.column() as usize)
|
||||||
- scroll_left
|
- scroll_left
|
||||||
} else {
|
} else {
|
||||||
content_origin.x() - scroll_left
|
content_origin.x() - scroll_left
|
||||||
},
|
},
|
||||||
end_x: if row == range.end.row() && !line_mode {
|
end_x: if row == range.end.row() {
|
||||||
content_origin.x()
|
content_origin.x()
|
||||||
+ line_layout.x_for_index(range.end.column() as usize)
|
+ line_layout.x_for_index(range.end.column() as usize)
|
||||||
- scroll_left
|
- scroll_left
|
||||||
|
@ -921,7 +950,7 @@ impl Element for EditorElement {
|
||||||
.anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
|
.anchor_before(DisplayPoint::new(end_row, 0).to_offset(&snapshot, Bias::Right))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut selections = Vec::new();
|
let mut selections: Vec<(ReplicaId, Vec<SelectionLayout>)> = Vec::new();
|
||||||
let mut active_rows = BTreeMap::new();
|
let mut active_rows = BTreeMap::new();
|
||||||
let mut highlighted_rows = None;
|
let mut highlighted_rows = None;
|
||||||
let mut highlighted_ranges = Vec::new();
|
let mut highlighted_ranges = Vec::new();
|
||||||
|
@ -945,17 +974,10 @@ impl Element for EditorElement {
|
||||||
if Some(replica_id) == view.leader_replica_id {
|
if Some(replica_id) == view.leader_replica_id {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
remote_selections
|
remote_selections
|
||||||
.entry((replica_id, line_mode))
|
.entry(replica_id)
|
||||||
.or_insert(Vec::new())
|
.or_insert(Vec::new())
|
||||||
.push(crate::Selection {
|
.push(SelectionLayout::from(selection, line_mode, &display_map));
|
||||||
id: selection.id,
|
|
||||||
goal: selection.goal,
|
|
||||||
reversed: selection.reversed,
|
|
||||||
start: selection.start.to_display_point(&display_map),
|
|
||||||
end: selection.end.to_display_point(&display_map),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
selections.extend(remote_selections);
|
selections.extend(remote_selections);
|
||||||
|
|
||||||
|
@ -981,15 +1003,15 @@ impl Element for EditorElement {
|
||||||
let local_replica_id = view.leader_replica_id.unwrap_or(view.replica_id(cx));
|
let local_replica_id = view.leader_replica_id.unwrap_or(view.replica_id(cx));
|
||||||
|
|
||||||
selections.push((
|
selections.push((
|
||||||
(local_replica_id, view.selections.line_mode),
|
local_replica_id,
|
||||||
local_selections
|
local_selections
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|selection| crate::Selection {
|
.map(|selection| {
|
||||||
id: selection.id,
|
SelectionLayout::from(
|
||||||
goal: selection.goal,
|
selection,
|
||||||
reversed: selection.reversed,
|
view.selections.line_mode,
|
||||||
start: selection.start.to_display_point(&display_map),
|
&display_map,
|
||||||
end: selection.end.to_display_point(&display_map),
|
)
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
));
|
));
|
||||||
|
@ -1240,7 +1262,7 @@ pub struct LayoutState {
|
||||||
em_width: f32,
|
em_width: f32,
|
||||||
em_advance: f32,
|
em_advance: f32,
|
||||||
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
|
highlighted_ranges: Vec<(Range<DisplayPoint>, Color)>,
|
||||||
selections: Vec<((ReplicaId, bool), Vec<text::Selection<DisplayPoint>>)>,
|
selections: Vec<(ReplicaId, Vec<SelectionLayout>)>,
|
||||||
context_menu: Option<(DisplayPoint, ElementBox)>,
|
context_menu: Option<(DisplayPoint, ElementBox)>,
|
||||||
code_actions_indicator: Option<(u32, ElementBox)>,
|
code_actions_indicator: Option<(u32, ElementBox)>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,20 @@ impl SelectionsCollection {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns all of the selections, adjusted to take into account the selection line_mode
|
||||||
|
pub fn all_adjusted(&self, cx: &mut MutableAppContext) -> Vec<Selection<Point>> {
|
||||||
|
let mut selections = self.all::<Point>(cx);
|
||||||
|
if self.line_mode {
|
||||||
|
let map = self.display_map(cx);
|
||||||
|
for selection in &mut selections {
|
||||||
|
let new_range = map.expand_to_line(selection.range());
|
||||||
|
selection.start = new_range.start;
|
||||||
|
selection.end = new_range.end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selections
|
||||||
|
}
|
||||||
|
|
||||||
pub fn disjoint_in_range<'a, D>(
|
pub fn disjoint_in_range<'a, D>(
|
||||||
&self,
|
&self,
|
||||||
range: Range<Anchor>,
|
range: Range<Anchor>,
|
||||||
|
|
|
@ -755,7 +755,7 @@ type SubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext) -> b
|
||||||
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
type GlobalSubscriptionCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
||||||
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
type ObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||||
type FocusObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
type FocusObservationCallback = Box<dyn FnMut(&mut MutableAppContext) -> bool>;
|
||||||
type GlobalObservationCallback = Box<dyn FnMut(&dyn Any, &mut MutableAppContext)>;
|
type GlobalObservationCallback = Box<dyn FnMut(&mut MutableAppContext)>;
|
||||||
type ReleaseObservationCallback = Box<dyn FnOnce(&dyn Any, &mut MutableAppContext)>;
|
type ReleaseObservationCallback = Box<dyn FnOnce(&dyn Any, &mut MutableAppContext)>;
|
||||||
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
|
type DeserializeActionCallback = fn(json: &str) -> anyhow::Result<Box<dyn Action>>;
|
||||||
|
|
||||||
|
@ -1263,7 +1263,7 @@ impl MutableAppContext {
|
||||||
pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
|
pub fn observe_global<G, F>(&mut self, mut observe: F) -> Subscription
|
||||||
where
|
where
|
||||||
G: Any,
|
G: Any,
|
||||||
F: 'static + FnMut(&G, &mut MutableAppContext),
|
F: 'static + FnMut(&mut MutableAppContext),
|
||||||
{
|
{
|
||||||
let type_id = TypeId::of::<G>();
|
let type_id = TypeId::of::<G>();
|
||||||
let id = post_inc(&mut self.next_subscription_id);
|
let id = post_inc(&mut self.next_subscription_id);
|
||||||
|
@ -1274,11 +1274,8 @@ impl MutableAppContext {
|
||||||
.or_default()
|
.or_default()
|
||||||
.insert(
|
.insert(
|
||||||
id,
|
id,
|
||||||
Some(
|
Some(Box::new(move |cx: &mut MutableAppContext| observe(cx))
|
||||||
Box::new(move |global: &dyn Any, cx: &mut MutableAppContext| {
|
as GlobalObservationCallback),
|
||||||
observe(global.downcast_ref().unwrap(), cx)
|
|
||||||
}) as GlobalObservationCallback,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Subscription::GlobalObservation {
|
Subscription::GlobalObservation {
|
||||||
|
@ -2261,27 +2258,24 @@ impl MutableAppContext {
|
||||||
fn handle_global_notification_effect(&mut self, observed_type_id: TypeId) {
|
fn handle_global_notification_effect(&mut self, observed_type_id: TypeId) {
|
||||||
let callbacks = self.global_observations.lock().remove(&observed_type_id);
|
let callbacks = self.global_observations.lock().remove(&observed_type_id);
|
||||||
if let Some(callbacks) = callbacks {
|
if let Some(callbacks) = callbacks {
|
||||||
if let Some(global) = self.cx.globals.remove(&observed_type_id) {
|
for (id, callback) in callbacks {
|
||||||
for (id, callback) in callbacks {
|
if let Some(mut callback) = callback {
|
||||||
if let Some(mut callback) = callback {
|
callback(self);
|
||||||
callback(global.as_ref(), self);
|
match self
|
||||||
match self
|
.global_observations
|
||||||
.global_observations
|
.lock()
|
||||||
.lock()
|
.entry(observed_type_id)
|
||||||
.entry(observed_type_id)
|
.or_default()
|
||||||
.or_default()
|
.entry(id)
|
||||||
.entry(id)
|
{
|
||||||
{
|
collections::btree_map::Entry::Vacant(entry) => {
|
||||||
collections::btree_map::Entry::Vacant(entry) => {
|
entry.insert(Some(callback));
|
||||||
entry.insert(Some(callback));
|
}
|
||||||
}
|
collections::btree_map::Entry::Occupied(entry) => {
|
||||||
collections::btree_map::Entry::Occupied(entry) => {
|
entry.remove();
|
||||||
entry.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.cx.globals.insert(observed_type_id, global);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5599,7 +5593,7 @@ mod tests {
|
||||||
let observation_count = Rc::new(RefCell::new(0));
|
let observation_count = Rc::new(RefCell::new(0));
|
||||||
let subscription = cx.observe_global::<Global, _>({
|
let subscription = cx.observe_global::<Global, _>({
|
||||||
let observation_count = observation_count.clone();
|
let observation_count = observation_count.clone();
|
||||||
move |_, _| {
|
move |_| {
|
||||||
*observation_count.borrow_mut() += 1;
|
*observation_count.borrow_mut() += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -5629,7 +5623,7 @@ mod tests {
|
||||||
let observation_count = Rc::new(RefCell::new(0));
|
let observation_count = Rc::new(RefCell::new(0));
|
||||||
cx.observe_global::<OtherGlobal, _>({
|
cx.observe_global::<OtherGlobal, _>({
|
||||||
let observation_count = observation_count.clone();
|
let observation_count = observation_count.clone();
|
||||||
move |_, _| {
|
move |_| {
|
||||||
*observation_count.borrow_mut() += 1;
|
*observation_count.borrow_mut() += 1;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -6003,7 +5997,7 @@ mod tests {
|
||||||
*subscription.borrow_mut() = Some(cx.observe_global::<(), _>({
|
*subscription.borrow_mut() = Some(cx.observe_global::<(), _>({
|
||||||
let observation_count = observation_count.clone();
|
let observation_count = observation_count.clone();
|
||||||
let subscription = subscription.clone();
|
let subscription = subscription.clone();
|
||||||
move |_, _| {
|
move |_| {
|
||||||
subscription.borrow_mut().take();
|
subscription.borrow_mut().take();
|
||||||
*observation_count.borrow_mut() += 1;
|
*observation_count.borrow_mut() += 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,11 +193,13 @@ impl Motion {
|
||||||
if selection.end.row() < map.max_point().row() {
|
if selection.end.row() < map.max_point().row() {
|
||||||
*selection.end.row_mut() += 1;
|
*selection.end.row_mut() += 1;
|
||||||
*selection.end.column_mut() = 0;
|
*selection.end.column_mut() = 0;
|
||||||
|
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||||
// Don't reset the end here
|
// Don't reset the end here
|
||||||
return;
|
return;
|
||||||
} else if selection.start.row() > 0 {
|
} else if selection.start.row() > 0 {
|
||||||
*selection.start.row_mut() -= 1;
|
*selection.start.row_mut() -= 1;
|
||||||
*selection.start.column_mut() = map.line_len(selection.start.row());
|
*selection.start.column_mut() = map.line_len(selection.start.row());
|
||||||
|
selection.start = map.clip_point(selection.start, Bias::Left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
mod change;
|
mod change;
|
||||||
mod delete;
|
mod delete;
|
||||||
|
mod yank;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ use gpui::{actions, MutableAppContext, ViewContext};
|
||||||
use language::{Point, SelectionGoal};
|
use language::{Point, SelectionGoal};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
use self::{change::change_over, delete::delete_over};
|
use self::{change::change_over, delete::delete_over, yank::yank_over};
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
vim,
|
vim,
|
||||||
|
@ -69,11 +70,12 @@ pub fn normal_motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
match vim.state.operator_stack.pop() {
|
match vim.state.operator_stack.pop() {
|
||||||
None => move_cursor(vim, motion, cx),
|
None => move_cursor(vim, motion, cx),
|
||||||
Some(Operator::Change) => change_over(vim, motion, cx),
|
|
||||||
Some(Operator::Delete) => delete_over(vim, motion, cx),
|
|
||||||
Some(Operator::Namespace(_)) => {
|
Some(Operator::Namespace(_)) => {
|
||||||
// Can't do anything for a namespace operator. Ignoring
|
// Can't do anything for a namespace operator. Ignoring
|
||||||
}
|
}
|
||||||
|
Some(Operator::Change) => change_over(vim, motion, cx),
|
||||||
|
Some(Operator::Delete) => delete_over(vim, motion, cx),
|
||||||
|
Some(Operator::Yank) => yank_over(vim, motion, cx),
|
||||||
}
|
}
|
||||||
vim.clear_operator(cx);
|
vim.clear_operator(cx);
|
||||||
});
|
});
|
||||||
|
|
26
crates/vim/src/normal/yank.rs
Normal file
26
crates/vim/src/normal/yank.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::{motion::Motion, utils::copy_selections_content, Vim};
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::MutableAppContext;
|
||||||
|
|
||||||
|
pub fn yank_over(vim: &mut Vim, motion: Motion, cx: &mut MutableAppContext) {
|
||||||
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
|
editor.transact(cx, |editor, cx| {
|
||||||
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
let mut original_positions: HashMap<_, _> = Default::default();
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let original_position = (selection.head(), selection.goal);
|
||||||
|
motion.expand_selection(map, selection, true);
|
||||||
|
original_positions.insert(selection.id, original_position);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
copy_selections_content(editor, motion.linewise(), cx);
|
||||||
|
editor.change_selections(None, cx, |s| {
|
||||||
|
s.move_with(|_, selection| {
|
||||||
|
let (head, goal) = original_positions.remove(&selection.id).unwrap();
|
||||||
|
selection.collapse_to(head, goal);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ pub enum Operator {
|
||||||
Namespace(Namespace),
|
Namespace(Namespace),
|
||||||
Change,
|
Change,
|
||||||
Delete,
|
Delete,
|
||||||
|
Yank,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -80,6 +81,7 @@ impl Operator {
|
||||||
Operator::Namespace(Namespace::G) => "g",
|
Operator::Namespace(Namespace::G) => "g",
|
||||||
Operator::Change => "c",
|
Operator::Change => "c",
|
||||||
Operator::Delete => "d",
|
Operator::Delete => "d",
|
||||||
|
Operator::Yank => "y",
|
||||||
}
|
}
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
|
|
|
@ -42,8 +42,10 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.observe_global::<Settings, _>(|settings, cx| {
|
cx.observe_global::<Settings, _>(|cx| {
|
||||||
Vim::update(cx, |state, cx| state.set_enabled(settings.vim_mode, cx))
|
Vim::update(cx, |state, cx| {
|
||||||
|
state.set_enabled(cx.global::<Settings>().vim_mode, cx)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
@ -141,14 +143,11 @@ impl Vim {
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.empty_selections_only() {
|
if state.empty_selections_only() {
|
||||||
// Defer so that access to global settings object doesn't panic
|
editor.change_selections(None, cx, |s| {
|
||||||
cx.defer(|editor, cx| {
|
s.move_with(|_, selection| {
|
||||||
editor.change_selections(None, cx, |s| {
|
selection.collapse_to(selection.head(), selection.goal)
|
||||||
s.move_with(|_, selection| {
|
});
|
||||||
selection.collapse_to(selection.head(), selection.goal)
|
})
|
||||||
});
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,6 +337,14 @@ impl<'a> VimTestContext<'a> {
|
||||||
let mode = self.mode();
|
let mode = self.mode();
|
||||||
VimBindingTestContext::new(keystrokes, mode, mode, self)
|
VimBindingTestContext::new(keystrokes, mode, mode, self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn assert_clipboard_content(&mut self, expected_content: Option<&str>) {
|
||||||
|
self.cx.update(|cx| {
|
||||||
|
let actual_content = cx.read_from_clipboard().map(|item| item.text().to_owned());
|
||||||
|
let expected_content = expected_content.map(|content| content.to_owned());
|
||||||
|
assert_eq!(actual_content, expected_content);
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for VimTestContext<'a> {
|
impl<'a> Deref for VimTestContext<'a> {
|
||||||
|
|
|
@ -9,9 +9,11 @@ actions!(
|
||||||
vim,
|
vim,
|
||||||
[
|
[
|
||||||
VisualDelete,
|
VisualDelete,
|
||||||
VisualChange,
|
|
||||||
VisualLineDelete,
|
VisualLineDelete,
|
||||||
VisualLineChange
|
VisualChange,
|
||||||
|
VisualLineChange,
|
||||||
|
VisualYank,
|
||||||
|
VisualLineYank,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -20,6 +22,8 @@ pub fn init(cx: &mut MutableAppContext) {
|
||||||
cx.add_action(change_line);
|
cx.add_action(change_line);
|
||||||
cx.add_action(delete);
|
cx.add_action(delete);
|
||||||
cx.add_action(delete_line);
|
cx.add_action(delete_line);
|
||||||
|
cx.add_action(yank);
|
||||||
|
cx.add_action(yank_line);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
|
pub fn visual_motion(motion: Motion, cx: &mut MutableAppContext) {
|
||||||
|
@ -56,8 +60,8 @@ pub fn change(_: &mut Workspace, _: &VisualChange, cx: &mut ViewContext<Workspac
|
||||||
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
s.move_with(|map, selection| {
|
s.move_with(|map, selection| {
|
||||||
if !selection.reversed {
|
if !selection.reversed {
|
||||||
// Head was at the end of the selection, and now is at the start. We need to move the end
|
// Head is at the end of the selection. Adjust the end position to
|
||||||
// forward by one if possible in order to compensate for this change.
|
// to include the character under the cursor.
|
||||||
*selection.end.column_mut() = selection.end.column() + 1;
|
*selection.end.column_mut() = selection.end.column() + 1;
|
||||||
selection.end = map.clip_point(selection.end, Bias::Left);
|
selection.end = map.clip_point(selection.end, Bias::Left);
|
||||||
}
|
}
|
||||||
|
@ -74,12 +78,9 @@ pub fn change_line(_: &mut Workspace, _: &VisualLineChange, cx: &mut ViewContext
|
||||||
Vim::update(cx, |vim, cx| {
|
Vim::update(cx, |vim, cx| {
|
||||||
vim.update_active_editor(cx, |editor, cx| {
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
editor.set_clip_at_line_ends(false, cx);
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
|
||||||
s.move_with(|map, selection| {
|
let adjusted = editor.selections.all_adjusted(cx);
|
||||||
selection.start = map.prev_line_boundary(selection.start.to_point(map)).1;
|
editor.change_selections(None, cx, |s| s.select(adjusted));
|
||||||
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
copy_selections_content(editor, true, cx);
|
copy_selections_content(editor, true, cx);
|
||||||
editor.insert("", cx);
|
editor.insert("", cx);
|
||||||
});
|
});
|
||||||
|
@ -131,11 +132,13 @@ pub fn delete_line(_: &mut Workspace, _: &VisualLineDelete, cx: &mut ViewContext
|
||||||
if selection.end.row() < map.max_point().row() {
|
if selection.end.row() < map.max_point().row() {
|
||||||
*selection.end.row_mut() += 1;
|
*selection.end.row_mut() += 1;
|
||||||
*selection.end.column_mut() = 0;
|
*selection.end.column_mut() = 0;
|
||||||
|
selection.end = map.clip_point(selection.end, Bias::Right);
|
||||||
// Don't reset the end here
|
// Don't reset the end here
|
||||||
return;
|
return;
|
||||||
} else if selection.start.row() > 0 {
|
} else if selection.start.row() > 0 {
|
||||||
*selection.start.row_mut() -= 1;
|
*selection.start.row_mut() -= 1;
|
||||||
*selection.start.column_mut() = map.line_len(selection.start.row());
|
*selection.start.column_mut() = map.line_len(selection.start.row());
|
||||||
|
selection.start = map.clip_point(selection.start, Bias::Left);
|
||||||
}
|
}
|
||||||
|
|
||||||
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
selection.end = map.next_line_boundary(selection.end.to_point(map)).1;
|
||||||
|
@ -161,6 +164,38 @@ pub fn delete_line(_: &mut Workspace, _: &VisualLineDelete, cx: &mut ViewContext
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn yank(_: &mut Workspace, _: &VisualYank, cx: &mut ViewContext<Workspace>) {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
editor.change_selections(Some(Autoscroll::Fit), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
if !selection.reversed {
|
||||||
|
// Head is at the end of the selection. Adjust the end position to
|
||||||
|
// to include the character under the cursor.
|
||||||
|
*selection.end.column_mut() = selection.end.column() + 1;
|
||||||
|
selection.end = map.clip_point(selection.end, Bias::Left);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
copy_selections_content(editor, false, cx);
|
||||||
|
});
|
||||||
|
vim.switch_mode(Mode::Normal, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn yank_line(_: &mut Workspace, _: &VisualLineYank, cx: &mut ViewContext<Workspace>) {
|
||||||
|
Vim::update(cx, |vim, cx| {
|
||||||
|
vim.update_active_editor(cx, |editor, cx| {
|
||||||
|
editor.set_clip_at_line_ends(false, cx);
|
||||||
|
let adjusted = editor.selections.all_adjusted(cx);
|
||||||
|
editor.change_selections(None, cx, |s| s.select(adjusted));
|
||||||
|
copy_selections_content(editor, true, cx);
|
||||||
|
});
|
||||||
|
vim.switch_mode(Mode::Normal, cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
|
@ -521,4 +556,88 @@ mod test {
|
||||||
|"},
|
|"},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_visual_yank(cx: &mut gpui::TestAppContext) {
|
||||||
|
let cx = VimTestContext::new(cx, true).await;
|
||||||
|
let mut cx = cx.binding(["v", "w", "y"]);
|
||||||
|
cx.assert("The quick |brown", "The quick |brown");
|
||||||
|
cx.assert_clipboard_content(Some("brown"));
|
||||||
|
let mut cx = cx.binding(["v", "w", "j", "y"]);
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The |quick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The |quick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some(indoc! {"
|
||||||
|
quick brown
|
||||||
|
fox jumps ov"}));
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps over
|
||||||
|
the |lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps over
|
||||||
|
the |lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some("lazy d"));
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps |over
|
||||||
|
the lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps |over
|
||||||
|
the lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some(indoc! {"
|
||||||
|
over
|
||||||
|
t"}));
|
||||||
|
let mut cx = cx.binding(["v", "b", "k", "y"]);
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The |quick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The |quick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some("The q"));
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps over
|
||||||
|
the |lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps over
|
||||||
|
the |lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some(indoc! {"
|
||||||
|
fox jumps over
|
||||||
|
the l"}));
|
||||||
|
cx.assert(
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps |over
|
||||||
|
the lazy dog"},
|
||||||
|
indoc! {"
|
||||||
|
The quick brown
|
||||||
|
fox jumps |over
|
||||||
|
the lazy dog"},
|
||||||
|
);
|
||||||
|
cx.assert_clipboard_content(Some(indoc! {"
|
||||||
|
quick brown
|
||||||
|
fox jumps o"}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,8 +179,8 @@ fn main() {
|
||||||
|
|
||||||
cx.observe_global::<Settings, _>({
|
cx.observe_global::<Settings, _>({
|
||||||
let languages = languages.clone();
|
let languages = languages.clone();
|
||||||
move |settings, _| {
|
move |cx| {
|
||||||
languages.set_theme(&settings.theme.editor.syntax);
|
languages.set_theme(&cx.global::<Settings>().theme.editor.syntax);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue