Documents: cells keep their row's pipes; long reads finish cell moves
An emptied bare edge cell writes a pipe so its column stays. The caret reads the escaped write-back. A right press beside an edited cell keeps its place. A move that waits on a long read opens once the read lands. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
2b6bd1d369
commit
f1771b3880
8 files changed
+224
-44
No files matched your search
@@ -132,10 +132,10 @@ impl MarkdownDocument {
|
|||||||
// A cell reads as it writes back.
|
// A cell reads as it writes back.
|
||||||
let typed = match open.cell {
|
let typed = match open.cell {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let closed = cells::closed(text, open.range.end);
|
let edges = cells::edges(text, open.range.clone());
|
||||||
let shift = |at: usize| cells::kept_at(typed, at, closed);
|
let shift = |at: usize| cells::kept_at(typed, at, edges);
|
||||||
picked = shift(picked.start)..shift(picked.end);
|
picked = shift(picked.start)..shift(picked.end);
|
||||||
cells::kept(typed, closed)
|
cells::kept(typed, edges)
|
||||||
}
|
}
|
||||||
None => typed.to_string(),
|
None => typed.to_string(),
|
||||||
};
|
};
|
||||||
@@ -157,8 +157,8 @@ impl MarkdownDocument {
|
|||||||
if let Some(open) = &self.open {
|
if let Some(open) = &self.open {
|
||||||
let typed = open.input.read(cx).text();
|
let typed = open.input.read(cx).text();
|
||||||
// Offsets read the escaped cell; its field holds it raw.
|
// Offsets read the escaped cell; its field holds it raw.
|
||||||
let closed = cells::closed(self.editor.read(cx).text(), open.range.end);
|
let edges = cells::edges(self.editor.read(cx).text(), open.range.clone());
|
||||||
let raw = open.cell.is_some() && cells::kept(typed, closed) != typed;
|
let raw = open.cell.is_some() && cells::kept(typed, edges) != typed;
|
||||||
let end = offset + typed.len();
|
let end = offset + typed.len();
|
||||||
let grown = (edits.iter().map(|(_, with)| with.len()).sum::<usize>() + end)
|
let grown = (edits.iter().map(|(_, with)| with.len()).sum::<usize>() + end)
|
||||||
.checked_sub(edits.iter().map(|(range, _)| range.len()).sum::<usize>());
|
.checked_sub(edits.iter().map(|(range, _)| range.len()).sum::<usize>());
|
||||||
|
|||||||
@@ -157,32 +157,55 @@ fn guarded(typed: &str) -> String {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a pipe follows the cell source ending at `end`.
|
/// What borders a cell's source in its row.
|
||||||
pub(super) fn closed(text: &str, end: usize) -> bool {
|
#[derive(Clone, Copy, Debug)]
|
||||||
text[end..].starts_with('|')
|
pub(super) struct Edges {
|
||||||
|
/// A pipe follows at once.
|
||||||
|
closed: bool,
|
||||||
|
/// No pipe before it on its line.
|
||||||
|
first: bool,
|
||||||
|
/// No pipe after it on its line.
|
||||||
|
last: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `typed` as a cell keeps it, a `closed` pipe intact.
|
/// The borders of the cell source at `range` of `text`.
|
||||||
pub(super) fn kept(typed: &str, closed: bool) -> String {
|
pub(super) fn edges(text: &str, range: Range<usize>) -> Edges {
|
||||||
|
let line = text[..range.start].rfind('\n').map_or(0, |at| at + 1);
|
||||||
|
let end = (text[range.end..].find('\n')).map_or(text.len(), |at| range.end + at);
|
||||||
|
let (before, after) = (&text[line..range.start], &text[range.end..end]);
|
||||||
|
Edges {
|
||||||
|
closed: after.starts_with('|'),
|
||||||
|
first: !before.contains('|'),
|
||||||
|
last: !after.contains('|'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `typed` as a cell keeps it, its row's pipes intact.
|
||||||
|
pub(super) fn kept(typed: &str, edges: Edges) -> String {
|
||||||
let mut out = guarded(typed);
|
let mut out = guarded(typed);
|
||||||
let slashes = out.len() - out.trim_end_matches('\\').len();
|
let slashes = out.len() - out.trim_end_matches('\\').len();
|
||||||
if closed && slashes % 2 == 1 {
|
if edges.closed && slashes % 2 == 1 {
|
||||||
out.push('\\');
|
out.push('\\');
|
||||||
}
|
}
|
||||||
|
// An emptied bare edge cell keeps its column.
|
||||||
|
if out.is_empty() && (edges.first || edges.last) {
|
||||||
|
out.push('|');
|
||||||
|
}
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Byte `at` of `typed` within `kept(typed, closed)`.
|
/// Byte `at` of `typed` within `kept(typed, edges)`.
|
||||||
pub(super) fn kept_at(typed: &str, at: usize, closed: bool) -> usize {
|
pub(super) fn kept_at(typed: &str, at: usize, edges: Edges) -> usize {
|
||||||
match at == typed.len() {
|
match at == typed.len() {
|
||||||
true => kept(typed, closed).len(),
|
true if typed.is_empty() && !edges.first => 0,
|
||||||
|
true => kept(typed, edges).len(),
|
||||||
false => guarded(&typed[..at]).len(),
|
false => guarded(&typed[..at]).len(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Aim, CellAt, Move, beside, closed, find, guarded, kept, kept_at};
|
use super::{Aim, CellAt, Edges, Move, beside, edges, find, guarded, kept, kept_at};
|
||||||
|
|
||||||
const TABLE: &str = "intro\n\n| a | **b** |\n| --- | --- |\n| c | |\n| d | e |\n";
|
const TABLE: &str = "intro\n\n| a | **b** |\n| --- | --- |\n| c | |\n| d | e |\n";
|
||||||
|
|
||||||
@@ -242,36 +265,74 @@ mod tests {
|
|||||||
assert_eq!(guarded("a\\\\|b"), "a\\\\\\|b");
|
assert_eq!(guarded("a\\\\|b"), "a\\\\\\|b");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cell `(row, column)` of `text`'s one table, written as `typed`.
|
||||||
|
fn write(text: &str, row: usize, column: usize, typed: &str) -> String {
|
||||||
|
let block = 0..text.len() - 1;
|
||||||
|
let cell = find(text, block, Aim::At(at(row, column))).expect("a cell");
|
||||||
|
let kept = kept(typed, edges(text, cell.range.clone()));
|
||||||
|
[&text[..cell.range.start], &kept, &text[cell.range.end..]].concat()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The source of each cell of `text`'s one table.
|
||||||
|
fn grid(text: &str) -> Vec<Vec<String>> {
|
||||||
|
let block = 0..text.len() - 1;
|
||||||
|
(0..)
|
||||||
|
.map_while(|row| {
|
||||||
|
let cells: Vec<String> = (0..)
|
||||||
|
.map_while(|column| {
|
||||||
|
let cell = find(text, block.clone(), Aim::At(at(row, column)))?;
|
||||||
|
Some(text[cell.range].to_string())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(!cells.is_empty()).then_some(cells)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_closing_backslash_keeps_the_next_pipe() {
|
fn a_closing_backslash_keeps_the_next_pipe() {
|
||||||
assert_eq!(kept("a\\", true), "a\\\\");
|
let shut = Edges {
|
||||||
assert_eq!(kept("a\\\\", true), "a\\\\");
|
closed: true,
|
||||||
assert_eq!(kept_at("a\\", 2, true), 3);
|
first: false,
|
||||||
assert_eq!(kept_at("a\\b", 2, true), 2);
|
last: false,
|
||||||
let text = "|a|b|\n|---|---|\n|c|d|\n";
|
};
|
||||||
let cell = find(text, 0..text.len() - 1, Aim::First).expect("a cell");
|
assert_eq!(kept("a\\", shut), "a\\\\");
|
||||||
let shut = closed(text, cell.range.end);
|
assert_eq!(kept("a\\\\", shut), "a\\\\");
|
||||||
let written = [
|
assert_eq!(kept_at("a\\", 2, shut), 3);
|
||||||
&text[..cell.range.start],
|
assert_eq!(kept_at("a\\b", 2, shut), 2);
|
||||||
&kept("a\\", shut),
|
let written = write("|a|b|\n|---|---|\n|c|d|\n", 0, 0, "a\\");
|
||||||
&text[cell.range.end..],
|
assert_eq!(grid(&written)[0], ["a\\\\", "b"]);
|
||||||
]
|
|
||||||
.concat();
|
|
||||||
let b = find(&written, 0..written.len() - 1, Aim::At(at(0, 1))).expect("still a table");
|
|
||||||
assert_eq!(&written[b.range], "b");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_backslash_before_padding_stays_single() {
|
fn a_backslash_before_padding_stays_single() {
|
||||||
let text = "| a\\ | b |\n| --- | --- |\n";
|
let text = "| a\\ | b |\n| --- | --- |\n";
|
||||||
let cell = find(text, 0..text.len() - 1, Aim::First).expect("a cell");
|
assert_eq!(write(text, 0, 0, "a\\"), text);
|
||||||
assert_eq!(&text[cell.range.clone()], "a\\");
|
}
|
||||||
assert!(!closed(text, cell.range.end));
|
|
||||||
assert_eq!(kept("a\\", false), "a\\");
|
#[test]
|
||||||
|
fn an_emptied_bare_edge_cell_keeps_its_column() {
|
||||||
|
let text = "alpha | beta\n--- | ---\nc | d\n";
|
||||||
|
let rows = |written: &str| grid(written).iter().map(Vec::len).collect::<Vec<_>>();
|
||||||
|
for (row, column) in [(0, 0), (0, 1), (1, 0), (1, 1)] {
|
||||||
|
let written = write(text, row, column, "");
|
||||||
|
assert_eq!(rows(&written), [2, 2], "{written:?}");
|
||||||
|
assert_eq!(grid(&written)[row][column], "", "{written:?}");
|
||||||
|
}
|
||||||
|
assert_eq!(grid(&write(text, 1, 0, ""))[1], ["", "d"]);
|
||||||
|
let empty = edges(text, 0..5);
|
||||||
|
assert_eq!(kept_at("", 0, empty), 1, "past its new pipe");
|
||||||
|
let last = edges(text, 8..12);
|
||||||
|
assert_eq!(kept_at("", 0, last), 0, "before its new pipe");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn typed_pipes_shift_later_bytes() {
|
fn typed_pipes_shift_later_bytes() {
|
||||||
assert_eq!(kept_at("||x", 2, false), 4);
|
let open = Edges {
|
||||||
|
closed: false,
|
||||||
|
first: false,
|
||||||
|
last: false,
|
||||||
|
};
|
||||||
|
assert_eq!(kept_at("||x", 2, open), 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,17 @@ pub(super) enum Landing {
|
|||||||
Cell(Aim),
|
Cell(Aim),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Landing {
|
||||||
|
/// The same landing after `edit`, if it survives.
|
||||||
|
fn moved(self, edit: &Option<(Range<usize>, usize)>) -> Option<Self> {
|
||||||
|
Some(match self {
|
||||||
|
Self::At(at) => Self::At(moved(at, edit)?),
|
||||||
|
Self::Cell(Aim::Byte(at)) => Self::Cell(Aim::Byte(moved(at, edit)?)),
|
||||||
|
other => other,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Where byte `at` lies after `edit`, if it survives.
|
/// Where byte `at` lies after `edit`, if it survives.
|
||||||
pub(super) fn moved(at: usize, edit: &Option<(Range<usize>, usize)>) -> Option<usize> {
|
pub(super) fn moved(at: usize, edit: &Option<(Range<usize>, usize)>) -> Option<usize> {
|
||||||
match edit {
|
match edit {
|
||||||
@@ -69,6 +80,7 @@ impl MarkdownDocument {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
self.waiting = None;
|
||||||
let pressed = match ix.map(|ix| self.parsed.blocks.get(ix)) {
|
let pressed = match ix.map(|ix| self.parsed.blocks.get(ix)) {
|
||||||
Some(None) => return log::warn!("markdown document: a press on a block read away"),
|
Some(None) => return log::warn!("markdown document: a press on a block read away"),
|
||||||
pressed => pressed.flatten().map(|block| block.range.start),
|
pressed => pressed.flatten().map(|block| block.range.start),
|
||||||
@@ -77,6 +89,19 @@ impl MarkdownDocument {
|
|||||||
if written.is_some() {
|
if written.is_some() {
|
||||||
self.reparse(true, cx);
|
self.reparse(true, cx);
|
||||||
}
|
}
|
||||||
|
if !self.read_now(cx) && written.is_some() {
|
||||||
|
// A long write-back reads first; it opens after.
|
||||||
|
let start = match pressed {
|
||||||
|
None => Some(None),
|
||||||
|
Some(start) => moved(start, &written).map(Some),
|
||||||
|
};
|
||||||
|
let (Some(start), Some(landing)) = (start, landing.moved(&written)) else {
|
||||||
|
return log::warn!("markdown document: the pressed block was written over");
|
||||||
|
};
|
||||||
|
log::info!("markdown document: a block opens once its text is read");
|
||||||
|
self.waiting = Some((start, landing, self.edits + self.writes as u64));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if !self.read_now(cx) {
|
if !self.read_now(cx) {
|
||||||
return log::warn!("markdown document: pressed while the text is read again");
|
return log::warn!("markdown document: pressed while the text is read again");
|
||||||
}
|
}
|
||||||
@@ -188,6 +213,25 @@ impl MarkdownDocument {
|
|||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A block asked open before its text was read, now opened.
|
||||||
|
pub(super) fn open_waiting(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
|
if self.waiting.is_none() || !self.read_now(cx) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (start, landing, expected) = self.waiting.take().expect("checked above");
|
||||||
|
if self.edits != expected {
|
||||||
|
return log::info!("markdown document: a waiting block lapsed with its text");
|
||||||
|
}
|
||||||
|
let ix = match start {
|
||||||
|
None => None,
|
||||||
|
Some(start) => match (self.parsed.blocks.iter()).position(|b| b.range.start == start) {
|
||||||
|
Some(ix) => Some(ix),
|
||||||
|
None => return log::warn!("markdown document: the waiting block was read away"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
self.open(ix, landing, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
/// Takes the focus a dropped block still holds.
|
/// Takes the focus a dropped block still holds.
|
||||||
pub(super) fn take_orphan(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn take_orphan(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
let Some(orphan) = self.orphan.take() else {
|
let Some(orphan) = self.orphan.take() else {
|
||||||
@@ -231,15 +275,15 @@ impl MarkdownDocument {
|
|||||||
let typed = open.input.read(cx).text().to_string();
|
let typed = open.input.read(cx).text().to_string();
|
||||||
let caret = open.input.read(cx).cursor();
|
let caret = open.input.read(cx).cursor();
|
||||||
let text = self.editor.read(cx).text();
|
let text = self.editor.read(cx).text();
|
||||||
let closed = cells::closed(text, open.range.end);
|
let edges = cells::edges(text, open.range.clone());
|
||||||
let (lead, written) = match open.append {
|
let (lead, written) = match open.append {
|
||||||
true if typed.is_empty() => return None,
|
true if typed.is_empty() => return None,
|
||||||
true => (joint(text).len(), format!("{}{typed}", joint(text))),
|
true => (joint(text).len(), format!("{}{typed}", joint(text))),
|
||||||
false if open.cell.is_some() => (0, cells::kept(&typed, closed)),
|
false if open.cell.is_some() => (0, cells::kept(&typed, edges)),
|
||||||
false => (0, typed.clone()),
|
false => (0, typed.clone()),
|
||||||
};
|
};
|
||||||
let caret = match open.cell {
|
let caret = match open.cell {
|
||||||
Some(_) => cells::kept_at(&typed, caret, closed),
|
Some(_) => cells::kept_at(&typed, caret, edges),
|
||||||
None => caret,
|
None => caret,
|
||||||
};
|
};
|
||||||
let at = open.range.start + lead + caret;
|
let at = open.range.start + lead + caret;
|
||||||
|
|||||||
@@ -109,6 +109,8 @@ pub struct MarkdownDocument {
|
|||||||
writes: usize,
|
writes: usize,
|
||||||
/// A match asked before its text was read; its version.
|
/// A match asked before its text was read; its version.
|
||||||
showing: Option<(Range<usize>, u64)>,
|
showing: Option<(Range<usize>, u64)>,
|
||||||
|
/// A block asked open before its text was read.
|
||||||
|
waiting: Option<(Option<usize>, edit::Landing, u64)>,
|
||||||
look: DocumentLook,
|
look: DocumentLook,
|
||||||
/// Typing in the open block, counted.
|
/// Typing in the open block, counted.
|
||||||
typed: u64,
|
typed: u64,
|
||||||
@@ -181,6 +183,7 @@ impl MarkdownDocument {
|
|||||||
asks: 0,
|
asks: 0,
|
||||||
writes: 0,
|
writes: 0,
|
||||||
showing: None,
|
showing: None,
|
||||||
|
waiting: None,
|
||||||
look: DocumentLook::default(),
|
look: DocumentLook::default(),
|
||||||
typed: 0,
|
typed: 0,
|
||||||
centered: None,
|
centered: None,
|
||||||
@@ -246,7 +249,9 @@ impl MarkdownDocument {
|
|||||||
let written = match open.append {
|
let written = match open.append {
|
||||||
true if typed.is_empty() => String::new(),
|
true if typed.is_empty() => String::new(),
|
||||||
true => format!("{}{typed}", joint(text)),
|
true => format!("{}{typed}", joint(text)),
|
||||||
false if open.cell.is_some() => cells::kept(typed, cells::closed(text, open.range.end)),
|
false if open.cell.is_some() => {
|
||||||
|
cells::kept(typed, cells::edges(text, open.range.clone()))
|
||||||
|
}
|
||||||
false => typed.to_string(),
|
false => typed.to_string(),
|
||||||
};
|
};
|
||||||
[&text[..open.range.start], &written, &text[open.range.end..]].concat()
|
[&text[..open.range.start], &written, &text[open.range.end..]].concat()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use gpui::{App, Context, Focusable as _, ListOffset, ScrollStrategy, Window, px};
|
use gpui::{App, Context, Focusable as _, ListOffset, ScrollStrategy, Window, px};
|
||||||
|
|
||||||
use super::{MarkdownDocument, Placement};
|
use super::{MarkdownDocument, Placement, cells};
|
||||||
use crate::documents::{MarkdownMode, modes::joint};
|
use crate::documents::{MarkdownMode, modes::joint};
|
||||||
|
|
||||||
/// Line and column, from zero, of byte `at` in `text`.
|
/// Line and column, from zero, of byte `at` in `text`.
|
||||||
@@ -25,7 +25,15 @@ impl MarkdownDocument {
|
|||||||
true => joint(editor.text()).len(),
|
true => joint(editor.text()).len(),
|
||||||
false => 0,
|
false => 0,
|
||||||
};
|
};
|
||||||
point(&self.text(cx), open.range.start + joined + input.cursor())
|
// A cell's caret reads its escaped write-back.
|
||||||
|
let cursor = match open.cell {
|
||||||
|
Some(_) => {
|
||||||
|
let edges = cells::edges(editor.text(), open.range.clone());
|
||||||
|
cells::kept_at(input.text(), input.cursor(), edges)
|
||||||
|
}
|
||||||
|
None => input.cursor(),
|
||||||
|
};
|
||||||
|
point(&self.text(cx), open.range.start + joined + cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The selected Markdown: the source's, or the open block's.
|
/// The selected Markdown: the source's, or the open block's.
|
||||||
|
|||||||
@@ -187,7 +187,9 @@ impl MarkdownDocument {
|
|||||||
let now = (self.parsed.blocks.iter())
|
let now = (self.parsed.blocks.iter())
|
||||||
.find(|block| block.range.start <= at && at <= block.range.end)
|
.find(|block| block.range.start <= at && at <= block.range.end)
|
||||||
.map(|block| self.drawn(block));
|
.map(|block| self.drawn(block));
|
||||||
if written.is_some() && pressed.is_some() && now != pressed {
|
// A cell beside the written one keeps its place.
|
||||||
|
let beside = pointed.table && now.as_ref().is_some_and(|now| now.0 == TargetBlock::Table);
|
||||||
|
if written.is_some() && pressed.is_some() && now != pressed && !beside {
|
||||||
return log::warn!("markdown document: the write-back changed the pressed block");
|
return log::warn!("markdown document: the write-back changed the pressed block");
|
||||||
}
|
}
|
||||||
self.editor
|
self.editor
|
||||||
@@ -220,8 +222,8 @@ impl MarkdownDocument {
|
|||||||
// A cell's typed pipes shift its bytes.
|
// A cell's typed pipes shift its bytes.
|
||||||
let pressed = match open.cell {
|
let pressed = match open.cell {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let closed = cells::closed(self.editor.read(cx).text(), open.range.end);
|
let edges = cells::edges(self.editor.read(cx).text(), open.range.clone());
|
||||||
cells::kept_at(input.text(), pressed, closed)
|
cells::kept_at(input.text(), pressed, edges)
|
||||||
}
|
}
|
||||||
None => pressed,
|
None => pressed,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -241,3 +241,62 @@ fn a_right_press_on_the_open_cells_padding_points_at_its_end(cx: &mut TestAppCon
|
|||||||
);
|
);
|
||||||
assert_eq!(opened(&document, cx), "alpha||", "the cell stays open");
|
assert_eq!(opened(&document, cx), "alpha||", "the cell stays open");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn the_caret_after_a_typed_pipe_reads_the_escaped_cell(cx: &mut TestAppContext) {
|
||||||
|
for (typed, column) in [("|中", 5), ("|x", 5)] {
|
||||||
|
let (document, cx) = open(WORDS, MarkdownMode::Visual, cx);
|
||||||
|
press_cell(0, 0, cx);
|
||||||
|
cx.simulate_keystrokes("backspace backspace backspace backspace backspace");
|
||||||
|
cx.simulate_input(typed);
|
||||||
|
let caret = document.read_with(cx, |document, cx| document.caret(cx));
|
||||||
|
assert_eq!(caret, (0, column), "{typed}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn emptied_bare_edge_cells_keep_the_table(cx: &mut TestAppContext) {
|
||||||
|
let cases = [
|
||||||
|
(0, 0, 5, "| | beta\n--- | ---\nc | d\n"),
|
||||||
|
(0, 1, 4, "alpha | |\n--- | ---\nc | d\n"),
|
||||||
|
(1, 0, 1, "alpha | beta\n--- | ---\n| | d\n"),
|
||||||
|
];
|
||||||
|
for (row, column, chars, written) in cases {
|
||||||
|
let (document, cx) = open(BARE, MarkdownMode::Visual, cx);
|
||||||
|
press_cell(row, column, cx);
|
||||||
|
for _ in 0..chars {
|
||||||
|
cx.simulate_keystrokes("backspace");
|
||||||
|
}
|
||||||
|
cx.simulate_keystrokes("escape");
|
||||||
|
assert_eq!(text(&document, cx), written);
|
||||||
|
press_cell(1, 1, cx);
|
||||||
|
assert_eq!(opened(&document, cx), "d", "{written:?} is still a table");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_right_press_beside_an_edited_cell_points(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(WORDS, MarkdownMode::Visual, cx);
|
||||||
|
let heard = heard(&document, cx);
|
||||||
|
press_cell(0, 0, cx);
|
||||||
|
cx.simulate_input("X");
|
||||||
|
press_cell_with(0, 1, MouseButton::Right, cx);
|
||||||
|
let at = heard.borrow().last().expect("heard").at;
|
||||||
|
let written = "| alphaX | beta |\n| --- | --- |\n| c | d |\n";
|
||||||
|
assert_eq!(text(&document, cx), written);
|
||||||
|
assert_eq!(at, written.find("beta").expect("the cell") + 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn tab_in_a_long_document_opens_the_next_cell_once_read(cx: &mut TestAppContext) {
|
||||||
|
let long = format!("{WORDS}\n```\n{}\n```\n", "x".repeat(super::super::AT_ONCE));
|
||||||
|
let (document, cx) = open(&long, MarkdownMode::Visual, cx);
|
||||||
|
document.update_in(cx, |document, window, cx| document.reveal(0, window, cx));
|
||||||
|
super::frames(cx);
|
||||||
|
press_cell(0, 0, cx);
|
||||||
|
cx.simulate_input("X");
|
||||||
|
cx.simulate_keystrokes("tab");
|
||||||
|
cx.run_until_parked();
|
||||||
|
assert_eq!(opened(&document, cx), "beta");
|
||||||
|
assert!(text(&document, cx).starts_with("| alphaX | beta |"));
|
||||||
|
}
|
||||||
@@ -230,6 +230,7 @@ impl Render for MarkdownDocument {
|
|||||||
// A top line asked before its text was read.
|
// A top line asked before its text was read.
|
||||||
self.place_top(window, cx);
|
self.place_top(window, cx);
|
||||||
self.show_waiting(window, cx);
|
self.show_waiting(window, cx);
|
||||||
|
self.open_waiting(window, cx);
|
||||||
let colors = cx.theme().colors.clone();
|
let colors = cx.theme().colors.clone();
|
||||||
let root = div()
|
let root = div()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|||||||
Reference in new issue
Block a user