use std::borrow::{Borrow, Cow};
use std::cmp::min;
use std::collections::BTreeSet;
use serde_json::Value;
use xi_rope::diff::{Diff, LineHashDiff};
use xi_rope::engine::{Engine, RevId, RevToken};
use xi_rope::rope::count_newlines;
use xi_rope::spans::SpansBuilder;
use xi_rope::{Cursor, DeltaBuilder, Interval, LinesMetric, Rope, RopeDelta, Transformer};
use xi_trace::{trace_block, trace_payload};
use crate::annotations::{AnnotationType, Annotations};
use crate::config::BufferItems;
use crate::edit_types::BufferEvent;
use crate::event_context::MAX_SIZE_LIMIT;
use crate::layers::Layers;
use crate::movement::{region_movement, Movement};
use crate::plugins::rpc::{DataSpan, GetDataResponse, PluginEdit, ScopeSpan, TextUnit};
use crate::plugins::PluginId;
use crate::rpc::SelectionModifier;
use crate::selection::{InsertDrift, SelRegion, Selection};
use crate::styles::ThemeStyleMap;
use crate::view::{Replace, View};
use crate::word_boundaries::WordCursor;
#[cfg(not(feature = "ledger"))]
pub struct SyncStore;
use crate::backspace::offset_for_delete_backwards;
#[cfg(feature = "ledger")]
use fuchsia::sync::SyncStore;
const MAX_UNDOS: usize = 20;
enum IndentDirection {
In,
Out,
}
pub struct Editor {
text: Rope,
engine: Engine,
last_rev_id: RevId,
pristine_rev_id: RevId,
undo_group_id: usize,
live_undos: Vec<usize>,
cur_undo: usize,
undos: BTreeSet<usize>,
gc_undos: BTreeSet<usize>,
force_undo_group: bool,
this_edit_type: EditType,
last_edit_type: EditType,
revs_in_flight: usize,
#[allow(dead_code)]
sync_store: Option<SyncStore>,
#[allow(dead_code)]
last_synced_rev: RevId,
layers: Layers,
}
impl Editor {
pub fn new() -> Editor {
Self::with_text("")
}
pub fn with_text<T: Into<Rope>>(text: T) -> Editor {
let engine = Engine::new(text.into());
let buffer = engine.get_head().clone();
let last_rev_id = engine.get_head_rev_id();
Editor {
text: buffer,
engine,
last_rev_id,
pristine_rev_id: last_rev_id,
undo_group_id: 1,
live_undos: vec![0],
cur_undo: 1,
undos: BTreeSet::new(),
gc_undos: BTreeSet::new(),
force_undo_group: false,
last_edit_type: EditType::Other,
this_edit_type: EditType::Other,
layers: Layers::default(),
revs_in_flight: 0,
sync_store: None,
last_synced_rev: last_rev_id,
}
}
pub(crate) fn get_buffer(&self) -> &Rope {
&self.text
}
pub(crate) fn get_layers(&self) -> &Layers {
&self.layers
}
pub(crate) fn get_layers_mut(&mut self) -> &mut Layers {
&mut self.layers
}
pub(crate) fn get_head_rev_token(&self) -> u64 {
self.engine.get_head_rev_id().token()
}
pub(crate) fn get_edit_type(&self) -> EditType {
self.this_edit_type
}
pub(crate) fn get_active_undo_group(&self) -> usize {
*self.live_undos.last().unwrap_or(&0)
}
pub(crate) fn update_edit_type(&mut self) {
self.last_edit_type = self.this_edit_type;
self.this_edit_type = EditType::Other
}
pub(crate) fn set_pristine(&mut self) {
self.pristine_rev_id = self.engine.get_head_rev_id();
}
pub(crate) fn is_pristine(&self) -> bool {
self.engine.is_equivalent_revision(self.pristine_rev_id, self.engine.get_head_rev_id())
}
pub(crate) fn set_force_undo_group(&mut self, force_undo_group: bool) {
trace_payload("Editor::set_force_undo_group", &["core"], force_undo_group.to_string());
self.force_undo_group = force_undo_group;
}
pub fn reload(&mut self, text: Rope) {
let delta = LineHashDiff::compute_delta(self.get_buffer(), &text);
self.add_delta(delta);
self.set_pristine();
}
pub fn increment_revs_in_flight(&mut self) {
self.revs_in_flight += 1;
}
pub fn dec_revs_in_flight(&mut self) {
self.revs_in_flight -= 1;
self.gc_undos();
}
fn insert<T: Into<Rope>>(&mut self, view: &View, text: T) {
let rope = text.into();
let mut builder = DeltaBuilder::new(self.text.len());
for region in view.sel_regions() {
let iv = Interval::new(region.min(), region.max());
builder.replace(iv, rope.clone());
}
self.add_delta(builder.build());
}
fn surround<BT, AT>(&mut self, view: &View, before_text: BT, after_text: AT)
where
BT: Into<Rope>,
AT: Into<Rope>,
{
let mut builder = DeltaBuilder::new(self.text.len());
let before_rope = before_text.into();
let after_rope = after_text.into();
for region in view.sel_regions() {
let before_iv = Interval::new(region.min(), region.min());
builder.replace(before_iv, before_rope.clone());
let after_iv = Interval::new(region.max(), region.max());
builder.replace(after_iv, after_rope.clone());
}
self.add_delta(builder.build());
}
fn add_delta(&mut self, delta: RopeDelta) {
let head_rev_id = self.engine.get_head_rev_id();
let undo_group = self.calculate_undo_group();
self.last_edit_type = self.this_edit_type;
let priority = 0x10000;
self.engine.edit_rev(priority, undo_group, head_rev_id.token(), delta);
self.text = self.engine.get_head().clone();
}
pub(crate) fn calculate_undo_group(&mut self) -> usize {
let has_undos = !self.live_undos.is_empty();
let force_undo_group = self.force_undo_group;
let is_unbroken_group = !self.this_edit_type.breaks_undo_group(self.last_edit_type);
if has_undos && (force_undo_group || is_unbroken_group) {
*self.live_undos.last().unwrap()
} else {
let undo_group = self.undo_group_id;
self.gc_undos.extend(&self.live_undos[self.cur_undo..]);
self.live_undos.truncate(self.cur_undo);
self.live_undos.push(undo_group);
if self.live_undos.len() <= MAX_UNDOS {
self.cur_undo += 1;
} else {
self.gc_undos.insert(self.live_undos.remove(0));
}
self.undo_group_id += 1;
undo_group
}
}
pub fn apply_plugin_edit(&mut self, edit: PluginEdit) {
let _t = trace_block("Editor::apply_plugin_edit", &["core"]);
let PluginEdit { rev, delta, priority, undo_group, .. } = edit;
let priority = priority as usize;
let undo_group = undo_group.unwrap_or_else(|| self.calculate_undo_group());
match self.engine.try_edit_rev(priority, undo_group, rev, delta) {
Err(e) => error!("Error applying plugin edit: {}", e),
Ok(_) => self.text = self.engine.get_head().clone(),
};
}
pub(crate) fn commit_delta(&mut self) -> Option<(RopeDelta, Rope, InsertDrift)> {
let _t = trace_block("Editor::commit_delta", &["core"]);
if self.engine.get_head_rev_id() == self.last_rev_id {
return None;
}
let last_token = self.last_rev_id.token();
let delta = self.engine.try_delta_rev_head(last_token).expect("last_rev not found");
let last_text = self.engine.get_rev(last_token).expect("last_rev not found");
let drift = match self.this_edit_type {
EditType::Transpose => InsertDrift::Inside,
EditType::Surround => InsertDrift::Outside,
_ => InsertDrift::Default,
};
self.layers.update_all(&delta);
self.last_rev_id = self.engine.get_head_rev_id();
self.sync_state_changed();
Some((delta, last_text, drift))
}
pub(crate) fn delta_rev_head(&self, target_rev_id: RevToken) -> Option<RopeDelta> {
self.engine.try_delta_rev_head(target_rev_id).ok()
}
#[cfg(not(target_os = "fuchsia"))]
fn gc_undos(&mut self) {
if self.revs_in_flight == 0 && !self.gc_undos.is_empty() {
self.engine.gc(&self.gc_undos);
self.undos = &self.undos - &self.gc_undos;
self.gc_undos.clear();
}
}
#[cfg(target_os = "fuchsia")]
fn gc_undos(&mut self) {
}
pub fn merge_new_state(&mut self, new_engine: Engine) {
self.engine.merge(&new_engine);
self.text = self.engine.get_head().clone();
self.undo_group_id = self.engine.max_undo_group_id() + 1;
self.last_synced_rev = self.engine.get_head_rev_id();
self.commit_delta();
}
pub fn set_session_id(&mut self, session: (u64, u32)) {
self.engine.set_session_id(session);
}
#[cfg(feature = "ledger")]
pub fn set_sync_store(&mut self, sync_store: SyncStore) {
self.sync_store = Some(sync_store);
}
#[cfg(not(feature = "ledger"))]
pub fn sync_state_changed(&mut self) {}
#[cfg(feature = "ledger")]
pub fn sync_state_changed(&mut self) {
if let Some(sync_store) = self.sync_store.as_mut() {
if self.last_synced_rev != self.engine.get_head_rev_id() {
self.last_synced_rev = self.engine.get_head_rev_id();
sync_store.state_changed();
}
}
}
#[cfg(feature = "ledger")]
pub fn transaction_ready(&mut self) {
if let Some(sync_store) = self.sync_store.as_mut() {
sync_store.commit_transaction(&self.engine);
}
}
fn delete_backward(&mut self, view: &View, config: &BufferItems) {
let mut builder = DeltaBuilder::new(self.text.len());
for region in view.sel_regions() {
let start = offset_for_delete_backwards(&view, ®ion, &self.text, &config);
let iv = Interval::new(start, region.max());
if !iv.is_empty() {
builder.delete(iv);
}
}
if !builder.is_empty() {
self.this_edit_type = EditType::Delete;
self.add_delta(builder.build());
}
}
fn delete_by_movement(
&mut self,
view: &View,
movement: Movement,
save: bool,
kill_ring: &mut Rope,
) {
let mut deletions = Selection::new();
for &r in view.sel_regions() {
if r.is_caret() {
let new_region = region_movement(movement, r, view, &self.text, true);
deletions.add_region(new_region);
} else {
deletions.add_region(r);
}
}
if save {
let saved = self.extract_sel_regions(&deletions).unwrap_or_default();
*kill_ring = Rope::from(saved);
}
self.delete_sel_regions(&deletions);
}
fn delete_sel_regions(&mut self, sel_regions: &[SelRegion]) {
let mut builder = DeltaBuilder::new(self.text.len());
for region in sel_regions {
let iv = Interval::new(region.min(), region.max());
if !iv.is_empty() {
builder.delete(iv);
}
}
if !builder.is_empty() {
self.this_edit_type = EditType::Delete;
self.add_delta(builder.build());
}
}
fn extract_sel_regions(&self, sel_regions: &[SelRegion]) -> Option<Cow<str>> {
let mut saved = None;
for region in sel_regions {
if !region.is_caret() {
let val = self.text.slice_to_cow(region);
match saved {
None => saved = Some(val),
Some(ref mut s) => {
s.to_mut().push('\n');
s.to_mut().push_str(&val);
}
}
}
}
saved
}
fn insert_newline(&mut self, view: &View, config: &BufferItems) {
self.this_edit_type = EditType::InsertNewline;
self.insert(view, &config.line_ending);
}
fn insert_tab(&mut self, view: &View, config: &BufferItems) {
self.this_edit_type = EditType::InsertChars;
let mut builder = DeltaBuilder::new(self.text.len());
let const_tab_text = self.get_tab_text(config, None);
if view.sel_regions().len() > 1 {
self.this_edit_type = EditType::Indent;
}
for region in view.sel_regions() {
let line_range = view.get_line_range(&self.text, region);
if line_range.len() > 1 {
self.this_edit_type = EditType::Indent;
for line in line_range {
let offset = view.line_col_to_offset(&self.text, line, 0);
let iv = Interval::new(offset, offset);
builder.replace(iv, Rope::from(const_tab_text));
}
} else {
let (_, col) = view.offset_to_line_col(&self.text, region.start);
let mut tab_size = config.tab_size;
tab_size = tab_size - (col % tab_size);
let tab_text = self.get_tab_text(config, Some(tab_size));
let iv = Interval::new(region.min(), region.max());
builder.replace(iv, Rope::from(tab_text));
}
}
self.add_delta(builder.build());
}
fn modify_indent(&mut self, view: &View, config: &BufferItems, direction: IndentDirection) {
self.this_edit_type = EditType::Indent;
let mut lines = BTreeSet::new();
let tab_text = self.get_tab_text(config, None);
for region in view.sel_regions() {
let line_range = view.get_line_range(&self.text, region);
for line in line_range {
lines.insert(line);
}
}
match direction {
IndentDirection::In => self.indent(view, lines, tab_text),
IndentDirection::Out => self.outdent(view, lines, tab_text),
};
}
fn indent(&mut self, view: &View, lines: BTreeSet<usize>, tab_text: &str) {
let mut builder = DeltaBuilder::new(self.text.len());
for line in lines {
let offset = view.line_col_to_offset(&self.text, line, 0);
let interval = Interval::new(offset, offset);
builder.replace(interval, Rope::from(tab_text));
}
self.this_edit_type = EditType::InsertChars;
self.add_delta(builder.build());
}
fn outdent(&mut self, view: &View, lines: BTreeSet<usize>, tab_text: &str) {
let mut builder = DeltaBuilder::new(self.text.len());
for line in lines {
let offset = view.line_col_to_offset(&self.text, line, 0);
let tab_offset = view.line_col_to_offset(&self.text, line, tab_text.len());
let interval = Interval::new(offset, tab_offset);
let leading_slice = self.text.slice_to_cow(interval.start()..interval.end());
if leading_slice == tab_text {
builder.delete(interval);
} else if let Some(first_char_col) = leading_slice.find(|c: char| !c.is_whitespace()) {
let first_char_offset = view.line_col_to_offset(&self.text, line, first_char_col);
let interval = Interval::new(offset, first_char_offset);
builder.delete(interval);
}
}
self.this_edit_type = EditType::Delete;
self.add_delta(builder.build());
}
fn get_tab_text(&self, config: &BufferItems, tab_size: Option<usize>) -> &'static str {
let tab_size = tab_size.unwrap_or(config.tab_size);
let tab_text = if config.translate_tabs_to_spaces { n_spaces(tab_size) } else { "\t" };
tab_text
}
fn do_insert(&mut self, view: &View, config: &BufferItems, chars: &str) {
let pair_search = config.surrounding_pairs.iter().find(|pair| pair.0 == chars);
let caret_exists = view.sel_regions().iter().any(|region| region.is_caret());
if let (Some(pair), false) = (pair_search, caret_exists) {
self.this_edit_type = EditType::Surround;
self.surround(view, pair.0.to_string(), pair.1.to_string());
} else {
self.this_edit_type = EditType::InsertChars;
self.insert(view, chars);
}
}
fn do_paste(&mut self, view: &View, chars: &str) {
if view.sel_regions().len() == 1 || view.sel_regions().len() != count_lines(chars) {
self.insert(view, chars);
} else {
let mut builder = DeltaBuilder::new(self.text.len());
for (sel, line) in view.sel_regions().iter().zip(chars.lines()) {
let iv = Interval::new(sel.min(), sel.max());
builder.replace(iv, line.into());
}
self.add_delta(builder.build());
}
}
pub(crate) fn do_cut(&mut self, view: &mut View) -> Value {
let result = self.do_copy(view);
self.delete_sel_regions(&view.sel_regions());
result
}
pub(crate) fn do_copy(&self, view: &View) -> Value {
if let Some(val) = self.extract_sel_regions(view.sel_regions()) {
Value::String(val.into_owned())
} else {
Value::Null
}
}
fn do_undo(&mut self) {
if self.cur_undo > 1 {
self.cur_undo -= 1;
assert!(self.undos.insert(self.live_undos[self.cur_undo]));
self.this_edit_type = EditType::Undo;
self.update_undos();
}
}
fn do_redo(&mut self) {
if self.cur_undo < self.live_undos.len() {
assert!(self.undos.remove(&self.live_undos[self.cur_undo]));
self.cur_undo += 1;
self.this_edit_type = EditType::Redo;
self.update_undos();
}
}
fn update_undos(&mut self) {
self.engine.undo(self.undos.clone());
self.text = self.engine.get_head().clone();
}
fn sel_region_to_interval_and_rope(&self, region: SelRegion) -> (Interval, Rope) {
let as_interval = Interval::new(region.min(), region.max());
let interval_rope = self.text.subseq(as_interval);
(as_interval, interval_rope)
}
fn do_transpose(&mut self, view: &View) {
let mut builder = DeltaBuilder::new(self.text.len());
let mut last = 0;
let mut optional_previous_selection: Option<(Interval, Rope)> =
last_selection_region(view.sel_regions())
.map(|®ion| self.sel_region_to_interval_and_rope(region));
for ®ion in view.sel_regions() {
if region.is_caret() {
let mut middle = region.end;
let mut start = self.text.prev_grapheme_offset(middle).unwrap_or(0);
let mut end = self.text.next_grapheme_offset(middle).unwrap_or(middle);
if start >= last {
let end_line_offset =
view.offset_of_line(&self.text, view.line_of_offset(&self.text, end));
if (end == middle || end == end_line_offset) && end != self.text.len() {
middle = start;
start = self.text.prev_grapheme_offset(middle).unwrap_or(0);
end = middle.wrapping_add(1);
}
let interval = Interval::new(start, end);
let before = self.text.slice_to_cow(start..middle);
let after = self.text.slice_to_cow(middle..end);
let swapped: String = [after, before].concat();
builder.replace(interval, Rope::from(swapped));
last = end;
}
} else if let Some(previous_selection) = optional_previous_selection {
let current_interval = self.sel_region_to_interval_and_rope(region);
builder.replace(current_interval.0, previous_selection.1);
optional_previous_selection = Some(current_interval);
}
}
if !builder.is_empty() {
self.this_edit_type = EditType::Transpose;
self.add_delta(builder.build());
}
}
fn yank(&mut self, view: &View, kill_ring: &mut Rope) {
self.insert(view, kill_ring.clone());
}
fn replace(&mut self, view: &mut View, replace_all: bool) {
if let Some(Replace { chars, .. }) = view.get_replace() {
let mut old_selection = Selection::new();
for ®ion in view.sel_regions() {
old_selection.add_region(region);
}
view.collapse_selections(&self.text);
if replace_all {
view.do_find_all(&self.text);
} else {
view.do_find_next(&self.text, false, true, true, &SelectionModifier::Set);
}
match last_selection_region(view.sel_regions()) {
Some(_) => self.insert(view, chars),
None => return,
};
}
}
fn transform_text<F: Fn(&str) -> String>(&mut self, view: &View, transform_function: F) {
let mut builder = DeltaBuilder::new(self.text.len());
for region in view.sel_regions() {
let selected_text = self.text.slice_to_cow(region);
let interval = Interval::new(region.min(), region.max());
builder.replace(interval, Rope::from(transform_function(&selected_text)));
}
if !builder.is_empty() {
self.this_edit_type = EditType::Other;
self.add_delta(builder.build());
}
}
fn change_number<F: Fn(i128) -> Option<i128>>(&mut self, view: &View, transform_function: F) {
let mut builder = DeltaBuilder::new(self.text.len());
for region in view.sel_regions() {
let mut cursor = WordCursor::new(&self.text, region.end);
let (mut start, end) = cursor.select_word();
if start > 0 && self.text.byte_at(start - 1) == (b'-') {
start -= 1;
}
let word = self.text.slice_to_cow(start..end);
if let Some(number) = word.parse::<i128>().ok().and_then(&transform_function) {
let interval = Interval::new(start, end);
builder.replace(interval, Rope::from(number.to_string()));
}
}
if !builder.is_empty() {
self.this_edit_type = EditType::Other;
self.add_delta(builder.build());
}
}
fn capitalize_text(&mut self, view: &mut View) {
let mut builder = DeltaBuilder::new(self.text.len());
let mut final_selection = Selection::new();
for ®ion in view.sel_regions() {
final_selection.add_region(SelRegion::new(region.max(), region.max()));
let mut word_cursor = WordCursor::new(&self.text, region.min());
loop {
let (start, end) = word_cursor.select_word();
if start < end {
let interval = Interval::new(start, end);
let word = self.text.slice_to_cow(start..end);
let (first_char, rest) = word.split_at(1);
let capitalized_text =
[first_char.to_uppercase(), rest.to_lowercase()].concat();
builder.replace(interval, Rope::from(capitalized_text));
}
if word_cursor.next_boundary().is_none() || end > region.max() {
break;
}
}
}
if !builder.is_empty() {
self.this_edit_type = EditType::Other;
self.add_delta(builder.build());
}
view.collapse_selections(&self.text);
view.set_selection(&self.text, final_selection);
}
fn duplicate_line(&mut self, view: &View, config: &BufferItems) {
let mut builder = DeltaBuilder::new(self.text.len());
let mut to_duplicate = BTreeSet::new();
for region in view.sel_regions() {
let (first_line, _) = view.offset_to_line_col(&self.text, region.min());
let line_start = view.offset_of_line(&self.text, first_line);
let mut cursor = match region.is_caret() {
true => Cursor::new(&self.text, line_start),
false => {
let (last_line, _) = view.offset_to_line_col(&self.text, region.max());
let line_end = view.offset_of_line(&self.text, last_line);
Cursor::new(&self.text, line_end)
}
};
if let Some(line_end) = cursor.next::<LinesMetric>() {
to_duplicate.insert((line_start, line_end));
}
}
for (start, end) in to_duplicate {
let iv = Interval::new(start, start);
builder.replace(iv, self.text.slice(start..end));
if end == self.text.len() {
builder.replace(iv, Rope::from(&config.line_ending))
}
}
self.this_edit_type = EditType::Other;
self.add_delta(builder.build());
}
pub(crate) fn do_edit(
&mut self,
view: &mut View,
kill_ring: &mut Rope,
config: &BufferItems,
cmd: BufferEvent,
) {
use self::BufferEvent::*;
match cmd {
Delete { movement, kill } => self.delete_by_movement(view, movement, kill, kill_ring),
Backspace => self.delete_backward(view, config),
Transpose => self.do_transpose(view),
Undo => self.do_undo(),
Redo => self.do_redo(),
Uppercase => self.transform_text(view, |s| s.to_uppercase()),
Lowercase => self.transform_text(view, |s| s.to_lowercase()),
Capitalize => self.capitalize_text(view),
Indent => self.modify_indent(view, config, IndentDirection::In),
Outdent => self.modify_indent(view, config, IndentDirection::Out),
InsertNewline => self.insert_newline(view, config),
InsertTab => self.insert_tab(view, config),
Insert(chars) => self.do_insert(view, config, &chars),
Paste(chars) => self.do_paste(view, &chars),
Yank => self.yank(view, kill_ring),
ReplaceNext => self.replace(view, false),
ReplaceAll => self.replace(view, true),
DuplicateLine => self.duplicate_line(view, config),
IncreaseNumber => self.change_number(view, |s| s.checked_add(1)),
DecreaseNumber => self.change_number(view, |s| s.checked_sub(1)),
}
}
pub fn theme_changed(&mut self, style_map: &ThemeStyleMap) {
self.layers.theme_changed(style_map);
}
pub fn plugin_n_lines(&self) -> usize {
self.text.measure::<LinesMetric>() + 1
}
pub fn update_spans(
&mut self,
view: &mut View,
plugin: PluginId,
start: usize,
len: usize,
spans: Vec<ScopeSpan>,
rev: RevToken,
) {
let _t = trace_block("Editor::update_spans", &["core"]);
let mut start = start;
let mut end_offset = start + len;
let mut sb = SpansBuilder::new(len);
for span in spans {
sb.add_span(Interval::new(span.start, span.end), span.scope_id);
}
let mut spans = sb.build();
if rev != self.engine.get_head_rev_id().token() {
if let Ok(delta) = self.engine.try_delta_rev_head(rev) {
let mut transformer = Transformer::new(&delta);
let new_start = transformer.transform(start, false);
if !transformer.interval_untouched(Interval::new(start, end_offset)) {
spans = spans.transform(start, end_offset, &mut transformer);
}
start = new_start;
end_offset = transformer.transform(end_offset, true);
} else {
error!("Revision {} not found", rev);
}
}
let iv = Interval::new(start, end_offset);
self.layers.update_layer(plugin, iv, spans);
view.invalidate_styles(&self.text, start, end_offset);
}
pub fn update_annotations(
&mut self,
view: &mut View,
plugin: PluginId,
start: usize,
len: usize,
annotation_spans: Vec<DataSpan>,
annotation_type: AnnotationType,
rev: RevToken,
) {
let _t = trace_block("Editor::update_annotations", &["core"]);
let mut start = start;
let mut end_offset = start + len;
let mut sb = SpansBuilder::new(len);
for span in annotation_spans {
sb.add_span(Interval::new(span.start, span.end), span.data);
}
let mut spans = sb.build();
if rev != self.engine.get_head_rev_id().token() {
if let Ok(delta) = self.engine.try_delta_rev_head(rev) {
let mut transformer = Transformer::new(&delta);
let new_start = transformer.transform(start, false);
if !transformer.interval_untouched(Interval::new(start, end_offset)) {
spans = spans.transform(start, end_offset, &mut transformer);
}
start = new_start;
end_offset = transformer.transform(end_offset, true);
} else {
error!("Revision {} not found", rev);
}
}
let iv = Interval::new(start, end_offset);
view.update_annotations(plugin, iv, Annotations { items: spans, annotation_type });
}
pub(crate) fn get_rev(&self, rev: RevToken) -> Option<Cow<Rope>> {
let text_cow = if rev == self.engine.get_head_rev_id().token() {
Cow::Borrowed(&self.text)
} else {
match self.engine.get_rev(rev) {
None => return None,
Some(text) => Cow::Owned(text),
}
};
Some(text_cow)
}
pub fn plugin_get_data(
&self,
start: usize,
unit: TextUnit,
max_size: usize,
rev: RevToken,
) -> Option<GetDataResponse> {
let _t = trace_block("Editor::plugin_get_data", &["core"]);
let text_cow = self.get_rev(rev)?;
let text = &text_cow;
let offset = unit.resolve_offset(text.borrow(), start)?;
let max_size = min(max_size, MAX_SIZE_LIMIT);
let mut end_off = offset.saturating_add(max_size);
if end_off >= text.len() {
end_off = text.len();
} else {
end_off = text.prev_codepoint_offset(end_off + 1).unwrap();
}
let chunk = text.slice_to_cow(offset..end_off).into_owned();
let first_line = text.line_of_offset(offset);
let first_line_offset = offset - text.offset_of_line(first_line);
Some(GetDataResponse { chunk, offset, first_line, first_line_offset })
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EditType {
Other,
#[serde(rename = "insert")]
InsertChars,
#[serde(rename = "newline")]
InsertNewline,
Indent,
Delete,
Undo,
Redo,
Transpose,
Surround,
}
impl EditType {
fn breaks_undo_group(self, previous: EditType) -> bool {
self == EditType::Other || self == EditType::Transpose || self != previous
}
}
fn last_selection_region(regions: &[SelRegion]) -> Option<&SelRegion> {
for region in regions.iter().rev() {
if !region.is_caret() {
return Some(region);
}
}
None
}
fn n_spaces(n: usize) -> &'static str {
let spaces = " ";
assert!(n <= spaces.len());
&spaces[..n]
}
fn count_lines(s: &str) -> usize {
let mut newlines = count_newlines(s);
if s.as_bytes().last() == Some(&0xa) {
newlines -= 1;
}
1 + newlines
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plugin_edit() {
let base_text = "hello";
let mut editor = Editor::with_text(base_text);
let mut builder = DeltaBuilder::new(base_text.len());
builder.replace(0..0, "s".into());
let delta = builder.build();
let rev = editor.get_head_rev_token();
let edit_one = PluginEdit {
rev,
delta,
priority: 55,
after_cursor: false,
undo_group: None,
author: "plugin_one".into(),
};
editor.apply_plugin_edit(edit_one.clone());
editor.apply_plugin_edit(edit_one);
assert_eq!(editor.get_buffer().to_string(), "sshello");
}
}