Documents: open cells keep presses; finds open their cell
A press inside the open cell stays with its field. Find opens the cell holding a match. A closing backslash no longer escapes the next pipe. A right press after typed pipes reads the right byte. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
5dc4dd1805
commit
6a3f3486fd
9 files changed
+233
-39
No files matched your search
@@ -132,9 +132,9 @@ 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 shift = |at: usize| cells::guarded(&typed[..at]).len();
|
let shift = |at: usize| cells::kept_at(typed, at);
|
||||||
picked = shift(picked.start)..shift(picked.end);
|
picked = shift(picked.start)..shift(picked.end);
|
||||||
cells::guarded(typed)
|
cells::kept(typed)
|
||||||
}
|
}
|
||||||
None => typed.to_string(),
|
None => typed.to_string(),
|
||||||
};
|
};
|
||||||
@@ -156,7 +156,7 @@ 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 raw = open.cell.is_some() && cells::guarded(typed) != typed;
|
let raw = open.cell.is_some() && cells::kept(typed) != 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>());
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use crate::documents::{
|
|||||||
render::CellAt,
|
render::CellAt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A cell of the text: which, its source, the caret in it.
|
/// A cell: which, its source, its caret.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub(super) struct Cell {
|
pub(super) struct Cell {
|
||||||
pub at: CellAt,
|
pub at: CellAt,
|
||||||
@@ -101,7 +101,7 @@ pub(super) fn find(text: &str, block: Range<usize>, aim: Aim) -> Option<Cell> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The cell `by` reaches from `at`; none past the table's edge.
|
/// The cell `by` reaches from `at`; none past the edge.
|
||||||
pub(super) fn beside(text: &str, block: Range<usize>, at: CellAt, by: Move) -> Option<CellAt> {
|
pub(super) fn beside(text: &str, block: Range<usize>, at: CellAt, by: Move) -> Option<CellAt> {
|
||||||
let root = file_tree(&text[block]);
|
let root = file_tree(&text[block]);
|
||||||
let tables = tables(&root);
|
let tables = tables(&root);
|
||||||
@@ -126,7 +126,7 @@ pub(super) fn beside(text: &str, block: Range<usize>, at: CellAt, by: Move) -> O
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A cell's source between its pipes; empty, a place after the opening one.
|
/// A cell's source; empty, just past its opening pipe.
|
||||||
fn written(text: &str, cell: &Node) -> Range<usize> {
|
fn written(text: &str, cell: &Node) -> Range<usize> {
|
||||||
let inside = cell.children().expect("a cell holds inlines");
|
let inside = cell.children().expect("a cell holds inlines");
|
||||||
if let (Some(first), Some(last)) = (inside.first(), inside.last()) {
|
if let (Some(first), Some(last)) = (inside.first(), inside.last()) {
|
||||||
@@ -143,8 +143,8 @@ fn written(text: &str, cell: &Node) -> Range<usize> {
|
|||||||
at..at
|
at..at
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `typed` with each bare `|` escaped, as a cell keeps it.
|
/// `typed` with each bare `|` escaped.
|
||||||
pub(super) fn guarded(typed: &str) -> String {
|
fn guarded(typed: &str) -> String {
|
||||||
let mut out = String::with_capacity(typed.len());
|
let mut out = String::with_capacity(typed.len());
|
||||||
let mut slashes = 0;
|
let mut slashes = 0;
|
||||||
for ch in typed.chars() {
|
for ch in typed.chars() {
|
||||||
@@ -157,9 +157,27 @@ pub(super) fn guarded(typed: &str) -> String {
|
|||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `typed` as a cell keeps it, its closing pipe intact.
|
||||||
|
pub(super) fn kept(typed: &str) -> String {
|
||||||
|
let mut out = guarded(typed);
|
||||||
|
let slashes = out.len() - out.trim_end_matches('\\').len();
|
||||||
|
if slashes % 2 == 1 {
|
||||||
|
out.push('\\');
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Byte `at` of `typed` within `kept(typed)`.
|
||||||
|
pub(super) fn kept_at(typed: &str, at: usize) -> usize {
|
||||||
|
match at == typed.len() {
|
||||||
|
true => kept(typed).len(),
|
||||||
|
false => guarded(&typed[..at]).len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{Aim, CellAt, Move, beside, find, guarded};
|
use super::{Aim, CellAt, Move, beside, 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";
|
||||||
|
|
||||||
@@ -218,4 +236,27 @@ mod tests {
|
|||||||
assert_eq!(guarded("a\\|b"), "a\\|b");
|
assert_eq!(guarded("a\\|b"), "a\\|b");
|
||||||
assert_eq!(guarded("a\\\\|b"), "a\\\\\\|b");
|
assert_eq!(guarded("a\\\\|b"), "a\\\\\\|b");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_closing_backslash_keeps_the_next_pipe() {
|
||||||
|
assert_eq!(kept("a\\"), "a\\\\");
|
||||||
|
assert_eq!(kept("a\\\\"), "a\\\\");
|
||||||
|
assert_eq!(kept_at("a\\", 2), 3);
|
||||||
|
assert_eq!(kept_at("a\\b", 2), 2);
|
||||||
|
let text = "|a|b|\n|---|---|\n|c|d|\n";
|
||||||
|
let cell = find(text, 0..text.len() - 1, Aim::First).expect("a cell");
|
||||||
|
let written = [
|
||||||
|
&text[..cell.range.start],
|
||||||
|
&kept("a\\"),
|
||||||
|
&text[cell.range.end..],
|
||||||
|
]
|
||||||
|
.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]
|
||||||
|
fn typed_pipes_shift_later_bytes() {
|
||||||
|
assert_eq!(kept_at("||x", 2), 4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -234,11 +234,11 @@ impl MarkdownDocument {
|
|||||||
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::guarded(&typed)),
|
false if open.cell.is_some() => (0, cells::kept(&typed)),
|
||||||
false => (0, typed.clone()),
|
false => (0, typed.clone()),
|
||||||
};
|
};
|
||||||
let caret = match open.cell {
|
let caret = match open.cell {
|
||||||
Some(_) => cells::guarded(&typed[..caret]).len(),
|
Some(_) => cells::kept_at(&typed, caret),
|
||||||
None => caret,
|
None => caret,
|
||||||
};
|
};
|
||||||
let at = open.range.start + lead + caret;
|
let at = open.range.start + lead + caret;
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ use std::ops::Range;
|
|||||||
|
|
||||||
use gpui::{Context, Window};
|
use gpui::{Context, Window};
|
||||||
|
|
||||||
use super::{MarkdownDocument, edit::Landing};
|
use super::{
|
||||||
|
MarkdownDocument,
|
||||||
|
cells::{self, Aim},
|
||||||
|
edit::Landing,
|
||||||
|
};
|
||||||
use crate::documents::MarkdownMode;
|
use crate::documents::MarkdownMode;
|
||||||
|
|
||||||
impl MarkdownDocument {
|
impl MarkdownDocument {
|
||||||
@@ -41,12 +45,22 @@ impl MarkdownDocument {
|
|||||||
// Outside one block: its line.
|
// Outside one block: its line.
|
||||||
return self.reveal(line(self, cx), window, cx);
|
return self.reveal(line(self, cx), window, cx);
|
||||||
};
|
};
|
||||||
let start = block.start;
|
// A match in a table opens its cell.
|
||||||
self.open(Some(ix), Landing::End, window, cx);
|
let text = self.editor.read(cx).text();
|
||||||
|
let landing = match cells::find(text, block.clone(), Aim::Byte(range.start)) {
|
||||||
|
Some(cell)
|
||||||
|
if cell.range.start <= range.start && range.end <= cell.range.end =>
|
||||||
|
{
|
||||||
|
Landing::Cell(Aim::At(cell.at))
|
||||||
|
}
|
||||||
|
Some(_) => return self.reveal(line(self, cx), window, cx),
|
||||||
|
None => Landing::End,
|
||||||
|
};
|
||||||
|
self.open(Some(ix), landing, window, cx);
|
||||||
self.list.scroll_to_reveal_item(ix);
|
self.list.scroll_to_reveal_item(ix);
|
||||||
if let Some(open) = &self.open {
|
if let Some(open) = &self.open {
|
||||||
let input = open.input.clone();
|
let input = open.input.clone();
|
||||||
let inside = range.start - start..range.end - start;
|
let inside = range.start - open.range.start..range.end - open.range.start;
|
||||||
input.update(cx, |input, cx| input.select(inside, cx));
|
input.update(cx, |input, cx| input.select(inside, cx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ impl MarkdownDocument {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tab or Shift-Tab in the open block; in a cell, the next or previous.
|
/// Tab or Shift-Tab; in a cell, moves between cells.
|
||||||
pub(super) fn block_shift(
|
pub(super) fn block_shift(
|
||||||
&mut self,
|
&mut self,
|
||||||
deeper: bool,
|
deeper: bool,
|
||||||
|
|||||||
@@ -246,7 +246,7 @@ 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::guarded(typed),
|
false if open.cell.is_some() => cells::kept(typed),
|
||||||
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()
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ use gpui::{AppContext as _, Context, MouseButton, Pixels, Point, Window};
|
|||||||
use markdown::mdast;
|
use markdown::mdast;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
DocumentEvent, MarkdownDocument, cells::Aim, edit::Landing, edit::moved, parse::Block,
|
DocumentEvent, MarkdownDocument,
|
||||||
|
cells::{self, Aim},
|
||||||
|
edit::Landing,
|
||||||
|
edit::moved,
|
||||||
|
parse::Block,
|
||||||
};
|
};
|
||||||
use crate::documents::{
|
use crate::documents::{
|
||||||
markdown::{file_tree, span, walk},
|
markdown::{file_tree, span, walk},
|
||||||
@@ -72,7 +76,8 @@ impl MarkdownDocument {
|
|||||||
/// Presses on block `ix`'s drawing, read as it was drawn.
|
/// Presses on block `ix`'s drawing, read as it was drawn.
|
||||||
pub(super) fn on_point(&self, ix: usize, cx: &mut Context<Self>) -> OnPoint {
|
pub(super) fn on_point(&self, ix: usize, cx: &mut Context<Self>) -> OnPoint {
|
||||||
let (document, parsed) = (cx.entity().downgrade(), self.parsed.clone());
|
let (document, parsed) = (cx.entity().downgrade(), self.parsed.clone());
|
||||||
let start = parsed.blocks[ix].range.start;
|
let block = parsed.blocks[ix].range.clone();
|
||||||
|
let start = block.start;
|
||||||
Rc::new(move |pointed, button, window, cx| {
|
Rc::new(move |pointed, button, window, cx| {
|
||||||
// The drawing counts from its block's start.
|
// The drawing counts from its block's start.
|
||||||
let at = match &pointed.place {
|
let at = match &pointed.place {
|
||||||
@@ -82,7 +87,10 @@ impl MarkdownDocument {
|
|||||||
Some(lines_at(&parsed.text[start..], *from, text, *at).unwrap_or(*from))
|
Some(lines_at(&parsed.text[start..], *from, text, *at).unwrap_or(*from))
|
||||||
}
|
}
|
||||||
Place::At(at) => Some(*at),
|
Place::At(at) => Some(*at),
|
||||||
Place::Cell(_) | Place::Block => None,
|
// A cell's padding lands at its end.
|
||||||
|
Place::Cell(cell) => cells::find(&parsed.text, block.clone(), Aim::At(*cell))
|
||||||
|
.map(|found| found.range.end - start),
|
||||||
|
Place::Block => None,
|
||||||
}
|
}
|
||||||
.map(|at| start + at);
|
.map(|at| start + at);
|
||||||
// A press in a table opens only its cell.
|
// A press in a table opens only its cell.
|
||||||
@@ -175,7 +183,13 @@ impl MarkdownDocument {
|
|||||||
let Some(open) = &self.open else {
|
let Some(open) = &self.open else {
|
||||||
return log::warn!("markdown document: a press on a block closed since");
|
return log::warn!("markdown document: a press on a block closed since");
|
||||||
};
|
};
|
||||||
let pressed = open.input.read(cx).offset_for_point(position);
|
let input = open.input.read(cx);
|
||||||
|
let pressed = input.offset_for_point(position);
|
||||||
|
// A cell's typed pipes shift its bytes.
|
||||||
|
let pressed = match open.cell {
|
||||||
|
Some(_) => cells::kept_at(input.text(), pressed),
|
||||||
|
None => pressed,
|
||||||
|
};
|
||||||
let taken = self.snapshot(cx);
|
let taken = self.snapshot(cx);
|
||||||
let (full, offset, _) = taken.clone();
|
let (full, offset, _) = taken.clone();
|
||||||
self.pressed += 1;
|
self.pressed += 1;
|
||||||
|
|||||||
@@ -1,24 +1,62 @@
|
|||||||
//! A press in a table opens only its cell (R6.1).
|
//! A press in a table opens only its cell (R6.1).
|
||||||
|
|
||||||
use gpui::{Modifiers, MouseButton, TestAppContext, VisualTestContext, point, px};
|
use gpui::{Entity, Modifiers, MouseButton, TestAppContext, VisualTestContext, point, px};
|
||||||
|
|
||||||
use super::{open, opened, text};
|
use super::{open, opened, point::heard, text};
|
||||||
use crate::documents::MarkdownMode;
|
use crate::documents::{MarkdownDocument, MarkdownMode};
|
||||||
|
|
||||||
const TABLE: &str = "| a | **bold** |\n| --- | --- |\n| c | |\n";
|
const TABLE: &str = "| a | **bold** |\n| --- | --- |\n| c | |\n";
|
||||||
|
|
||||||
/// Presses cell `row`, `column` of the first table near its left.
|
/// Presses cell `row`, `column` of the first table.
|
||||||
fn press_cell(row: usize, column: usize, cx: &mut VisualTestContext) {
|
fn press_cell(row: usize, column: usize, cx: &mut VisualTestContext) {
|
||||||
|
press_cell_with(row, column, MouseButton::Left, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn press_cell_with(row: usize, column: usize, button: MouseButton, cx: &mut VisualTestContext) {
|
||||||
let name = format!("markdown-cell-0-{row}-{column}");
|
let name = format!("markdown-cell-0-{row}-{column}");
|
||||||
let bounds = cx
|
let bounds = cx
|
||||||
.debug_bounds(Box::leak(name.into_boxed_str()))
|
.debug_bounds(Box::leak(name.into_boxed_str()))
|
||||||
.expect("the cell is drawn");
|
.expect("the cell is drawn");
|
||||||
let at = point(bounds.left() + px(2.0), bounds.top() + px(2.0));
|
let at = point(bounds.left() + px(2.0), bounds.top() + px(2.0));
|
||||||
cx.simulate_mouse_down(at, MouseButton::Left, Modifiers::none());
|
cx.simulate_mouse_down(at, button, Modifiers::none());
|
||||||
cx.simulate_mouse_up(at, MouseButton::Left, Modifiers::none());
|
cx.simulate_mouse_up(at, button, Modifiers::none());
|
||||||
cx.run_until_parked();
|
cx.run_until_parked();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Presses the open cell's field `x` past its left.
|
||||||
|
fn press_field(x: f32, button: MouseButton, cx: &mut VisualTestContext) {
|
||||||
|
let field = cx
|
||||||
|
.debug_bounds("markdown-open-cell")
|
||||||
|
.expect("a cell is open");
|
||||||
|
let at = point(field.left() + px(x), field.top() + px(10.0));
|
||||||
|
cx.simulate_mouse_down(at, button, Modifiers::none());
|
||||||
|
cx.simulate_mouse_up(at, button, Modifiers::none());
|
||||||
|
cx.run_until_parked();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caret(document: &Entity<MarkdownDocument>, cx: &mut VisualTestContext) -> usize {
|
||||||
|
document.read_with(cx, |document, cx| {
|
||||||
|
document
|
||||||
|
.open
|
||||||
|
.as_ref()
|
||||||
|
.expect("open")
|
||||||
|
.input
|
||||||
|
.read(cx)
|
||||||
|
.cursor()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where byte `k` of the open cell lies.
|
||||||
|
fn x_of(document: &Entity<MarkdownDocument>, k: usize, cx: &mut VisualTestContext) -> f32 {
|
||||||
|
(0..400)
|
||||||
|
.map(|step| step as f32)
|
||||||
|
.find(|&x| {
|
||||||
|
press_field(x, MouseButton::Left, cx);
|
||||||
|
caret(document, cx) == k
|
||||||
|
})
|
||||||
|
.expect("the byte is drawn")
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
fn a_press_opens_its_cell_and_writes_one_undo_step(cx: &mut TestAppContext) {
|
fn a_press_opens_its_cell_and_writes_one_undo_step(cx: &mut TestAppContext) {
|
||||||
let (document, cx) = open(TABLE, MarkdownMode::Visual, cx);
|
let (document, cx) = open(TABLE, MarkdownMode::Visual, cx);
|
||||||
@@ -56,3 +94,80 @@ fn tab_and_arrows_walk_the_cells(cx: &mut TestAppContext) {
|
|||||||
assert!(document.read_with(cx, |document, _| document.open.is_none()));
|
assert!(document.read_with(cx, |document, _| document.open.is_none()));
|
||||||
assert_eq!(text(&document, cx), TABLE);
|
assert_eq!(text(&document, cx), TABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const WORDS: &str = "| alpha | beta |\n| --- | --- |\n| c | d |\n";
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_press_in_the_open_cell_moves_its_caret_and_keeps_one_step(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(WORDS, MarkdownMode::Visual, cx);
|
||||||
|
press_cell(0, 0, cx);
|
||||||
|
assert_eq!(caret(&document, cx), 5);
|
||||||
|
cx.simulate_input("X");
|
||||||
|
let x = x_of(&document, 1, cx);
|
||||||
|
press_field(x, MouseButton::Left, cx);
|
||||||
|
assert_eq!(caret(&document, cx), 1, "the cell stays open");
|
||||||
|
cx.simulate_input("Y");
|
||||||
|
cx.simulate_keystrokes("escape");
|
||||||
|
assert_eq!(
|
||||||
|
text(&document, cx),
|
||||||
|
"| aYlphaX | beta |\n| --- | --- |\n| c | d |\n"
|
||||||
|
);
|
||||||
|
let editor = document.read_with(cx, |document, _| document.editor().clone());
|
||||||
|
editor.update(cx, |editor, cx| editor.undo(cx));
|
||||||
|
assert_eq!(text(&document, cx), WORDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_match_in_a_table_opens_its_cell_selected(cx: &mut TestAppContext) {
|
||||||
|
let table = "| needle | B |\n| --- | --- |\n| C | tail |\n";
|
||||||
|
let (document, cx) = open(table, MarkdownMode::Visual, cx);
|
||||||
|
let at = table.find("needle").expect("the word");
|
||||||
|
document.update_in(cx, |document, window, cx| {
|
||||||
|
document.show_match(at..at + 6, window, cx)
|
||||||
|
});
|
||||||
|
assert_eq!(opened(&document, cx), "needle");
|
||||||
|
let picked = document.read_with(cx, |document, cx| {
|
||||||
|
document
|
||||||
|
.open
|
||||||
|
.as_ref()
|
||||||
|
.expect("open")
|
||||||
|
.input
|
||||||
|
.read(cx)
|
||||||
|
.selection()
|
||||||
|
});
|
||||||
|
assert_eq!(picked, 0..6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_closing_backslash_keeps_the_table(cx: &mut TestAppContext) {
|
||||||
|
let table = "|a|b|\n|---|---|\n|c|d|\n";
|
||||||
|
let (document, cx) = open(table, MarkdownMode::Visual, cx);
|
||||||
|
press_cell(0, 0, cx);
|
||||||
|
cx.simulate_input("\\");
|
||||||
|
cx.simulate_keystrokes("escape");
|
||||||
|
assert_eq!(text(&document, cx), "|a\\\\|b|\n|---|---|\n|c|d|\n");
|
||||||
|
press_cell(0, 1, cx);
|
||||||
|
assert_eq!(opened(&document, cx), "b", "still its own cell");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_right_press_after_typed_pipes_finds_the_link(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(WORDS, MarkdownMode::Visual, cx);
|
||||||
|
let heard = heard(&document, cx);
|
||||||
|
press_cell(1, 1, cx);
|
||||||
|
cx.simulate_keystrokes("backspace");
|
||||||
|
cx.simulate_input("||[go](https://ely.dev)");
|
||||||
|
let x = x_of(&document, 3, cx);
|
||||||
|
press_field(x, MouseButton::Right, cx);
|
||||||
|
let target = heard.borrow().last().cloned().expect("heard");
|
||||||
|
assert_eq!(target.link.as_deref(), Some("https://ely.dev"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
fn a_right_press_on_a_cell_points_at_it(cx: &mut TestAppContext) {
|
||||||
|
let (document, cx) = open(WORDS, MarkdownMode::Visual, cx);
|
||||||
|
let heard = heard(&document, cx);
|
||||||
|
press_cell_with(1, 1, MouseButton::Right, cx);
|
||||||
|
let at = heard.borrow().last().expect("heard").at;
|
||||||
|
assert_eq!(at, WORDS.rfind(" d").expect("the cell") + 2);
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
use gpui::{AnyElement, App, FontWeight, IntoElement, ParentElement, Styled, div, prelude::*};
|
use gpui::{
|
||||||
|
AnyElement, App, FontWeight, IntoElement, MouseButton, ParentElement, Styled, div, prelude::*,
|
||||||
|
};
|
||||||
use markdown::mdast::AlignKind;
|
use markdown::mdast::AlignKind;
|
||||||
|
|
||||||
use super::Draw;
|
use super::Draw;
|
||||||
@@ -9,7 +11,7 @@ use crate::{
|
|||||||
theme::{ActiveTheme, Radius},
|
theme::{ActiveTheme, Radius},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A cell: its table in drawing order, row from the head, column.
|
/// A cell: table in drawing order, row, column.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
pub(crate) struct CellAt {
|
pub(crate) struct CellAt {
|
||||||
pub table: usize,
|
pub table: usize,
|
||||||
@@ -56,17 +58,25 @@ impl Draw<'_> {
|
|||||||
.when(row == 0, |cell| {
|
.when(row == 0, |cell| {
|
||||||
cell.font_weight(FontWeight::MEDIUM)
|
cell.font_weight(FontWeight::MEDIUM)
|
||||||
.text_color(colors.fg_muted)
|
.text_color(colors.fg_muted)
|
||||||
})
|
|
||||||
.child(match open {
|
|
||||||
Some(field) => div().w_full().child(field),
|
|
||||||
None => div()
|
|
||||||
.max_w_full()
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.children(self.nodes(blocks, cx)),
|
|
||||||
});
|
});
|
||||||
self.pressed_cell(drawn, at)
|
match open {
|
||||||
|
// Presses in the open cell stay with its field.
|
||||||
|
Some(field) => drawn
|
||||||
|
.child(div().w_full().child(field))
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
|
||||||
|
.on_mouse_down(MouseButton::Right, |_, _, cx| cx.stop_propagation()),
|
||||||
|
None => self.pressed_cell(
|
||||||
|
drawn.child(
|
||||||
|
div()
|
||||||
|
.max_w_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.gap_1()
|
||||||
|
.children(self.nodes(blocks, cx)),
|
||||||
|
),
|
||||||
|
at,
|
||||||
|
),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
|
|||||||
Reference in new issue
Block a user