use std::cmp::{Reverse, min};
use std::collections::BTreeSet;
use std::fmt::Debug;
use std::iter::repeat_n;
use std::ops::{Deref, Range, RangeBounds};
use std::path::Path;
use std::pin::pin;
use std::sync::LazyLock;
use std::vec::Vec;
use anyhow::anyhow;
use atools::prelude::*;
use dsb::Cell;
use dsb::cell::Style;
use helix_core::Syntax;
use helix_core::syntax::{HighlightEvent, Loader};
use implicit_fn::implicit_fn;
use lsp_types::{
DocumentSymbol, Location, Position, SemanticTokensLegend,
SnippetTextEdit, TextEdit,
};
use ropey::{Rope, RopeSlice};
use serde::{Deserialize, Serialize};
use tree_house::Language;
pub mod cursor;
use cursor::{Cursor, Cursors, ceach};
pub mod inlay;
use inlay::{Inlay, Marking};
pub mod semantic_tokens;
use semantic_tokens::TokenD;
pub mod hist;
pub mod theme_treesitter;
use hist::Changes;
use crate::lsp::Void;
use crate::sni::{Snippet, StopP};
use crate::text::hist::Action;
pub const fn color_(x: &str) -> [u8; 3] {
let Some(x): Option<[u8; 7]> = x.as_bytes().try_into().ok() else {
panic!()
};
color(&x)
}
pub const fn set_a([a, b, c]: [u8; 3], to: f32) -> [u8; 3] {
[
(((a as f32 / 255.0) * to) * 255.0) as u8,
(((b as f32 / 255.0) * to) * 255.0) as u8,
(((c as f32 / 255.0) * to) * 255.0) as u8,
]
}
pub const fn color<const N: usize>(x: &[u8; N]) -> [u8; (N - 1) / 2]
where
[(); N - 1]:,
[(); (N - 1) % 2 + usize::MAX]:,
{
let x = x.tail();
let parse = car::map!(x, |b| (b & 0xF) + 9 * (b >> 6)).chunked::<2>();
car::map!(parse, |[a, b]| a * 16 + b)
}
macro_rules! col {
($x:literal) => {{
const __N: usize = $x.len();
const { crate::text::color($x.as_bytes().as_array::<__N>().unwrap()) }
}};
($($x:literal),+)=> {{
($(crate::text::col!($x),)+)
}};
}
pub fn deserialize_from_string<'de, D: serde::de::Deserializer<'de>>(
de: D,
) -> Result<Rope, D::Error> {
let s = serde::de::Deserialize::deserialize(de)?;
Ok(Rope::from_str(s))
}
#[derive(Clone, Serialize, Deserialize, Default)]
pub struct TextArea {
#[serde(
serialize_with = "crate::serialize_display",
deserialize_with = "deserialize_from_string"
)]
pub rope: Rope,
pub cursor: Cursors,
/// ┌─────────────────┐
/// │#invisible text │
/// │╶╶╶view offset╶╶╶│
/// │visible text │
/// │ │
/// │ │
/// │ EOF │
/// │ │ - up to 1 - r more lines visible
/// └─────────────────┘ default to 5 more lines
///
pub vo: usize,
pub ho: usize,
#[serde(skip)]
pub r: usize,
#[serde(skip)]
pub c: usize,
#[serde(skip)]
pub tabstops: Option<Snippet>,
pub inlays: BTreeSet<Inlay>,
pub tokens: Vec<TokenD>,
pub changes: Changes,
}
#[derive(Serialize, Deserialize)]
pub struct CellBuffer {
pub c: usize,
pub vo: usize,
pub cells: Box<[Cell]>,
}
impl Debug for CellBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CellBuffer")
.field("c", &self.c)
.field("vo", &self.vo)
.field("cells", &self.cells.len())
.finish()
}
}
impl Deref for CellBuffer {
type Target = [Cell];
fn deref(&self) -> &Self::Target {
&self.cells
}
}
impl CellBuffer {
pub fn displayable(&self, r: usize) -> &[Cell] {
&self[self.vo * self.c..((self.vo + r) * self.c).min(self.len())]
}
pub fn l(&self) -> usize {
self.len() / self.c
}
}
impl Debug for TextArea {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextArea")
.field("rope", &self.rope)
.field("cursor", &self.cursor)
// .field("column", &self.column)
.field("vo", &self.vo)
.field("r", &self.r)
.field("c", &self.c)
.finish()
}
}
impl Deref for TextArea {
type Target = Rope;
fn deref(&self) -> &Self::Target {
&self.rope
}
}
pub trait RopeExt {
fn position(&self, r: Range<usize>) -> Option<[(usize, usize); 2]>;
/// number of lines
fn l(&self) -> usize;
// input: char, output: utf8
fn x_bytes(&self, c: usize) -> Option<usize>;
fn y(&self, c: usize) -> Option<usize>;
fn x(&self, c: usize) -> Option<usize>;
fn xy(&self, c: usize) -> Option<(usize, usize)>;
fn beginning_of_line(&self, c: usize) -> Option<usize>;
fn indentation_of(&self, n: usize) -> usize;
/// or eof
fn eol(&self, li: usize) -> usize;
fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)>;
fn l_position(&self, p: Position) -> Option<usize>;
fn to_l_position(&self, l: usize) -> Option<lsp_types::Position>;
fn l_range(&self, r: lsp_types::Range) -> Option<Range<usize>>;
fn to_l_range(&self, r: Range<usize>) -> Option<lsp_types::Range>;
}
impl RopeExt for Rope {
fn position(
&self,
Range { start, end }: Range<usize>,
) -> Option<[(usize, usize); 2]> {
let y1 = self.try_char_to_line(start).ok()?;
let y2 = self.try_char_to_line(end).ok()?;
let x1 = start - self.try_line_to_char(y1).ok()?;
let x2 = end - self.try_line_to_char(y2).ok()?;
[(x1, y1), (x2, y2)].into()
}
/// number of lines
fn l(&self) -> usize {
self.len_lines()
}
// input: char, output: utf8
fn x_bytes(&self, c: usize) -> Option<usize> {
let y = self.try_char_to_line(c).ok()?;
let x = self
.try_char_to_byte(c)
.ok()?
.checked_sub(self.try_line_to_byte(y).ok()?)?;
Some(x)
}
fn y(&self, c: usize) -> Option<usize> {
self.try_char_to_line(c).ok()
}
fn xy(&self, c: usize) -> Option<(usize, usize)> {
let y = self.try_char_to_line(c).ok()?;
let x = c.checked_sub(self.try_line_to_char(y).ok()?)?;
Some((x, y))
}
fn x(&self, c: usize) -> Option<usize> {
self.xy(c).map(|x| x.0)
}
fn beginning_of_line(&self, c: usize) -> Option<usize> {
self.y(c).and_then(|x| self.try_line_to_char(x).ok())
}
#[implicit_fn]
fn indentation_of(&self, n: usize) -> usize {
let Some(l) = self.get_line(n) else { return 0 };
l.chars().filter(*_ != '\n').take_while(_.is_whitespace()).count()
}
/// or eof
#[lower::apply(saturating)]
fn eol(&self, li: usize) -> usize {
self.try_line_to_char(li)
.map(|l| {
l + self
.get_line(li)
.map(|x| x.len_chars() - 1)
.unwrap_or_default()
})
.unwrap_or(usize::MAX)
.min(self.len_chars())
}
fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)> {
self.l_position(p).and_then(|x| self.xy(x))
}
fn l_position(&self, p: Position) -> Option<usize> {
self.try_byte_to_char(
self.try_line_to_byte(p.line as _).ok()?
+ (p.character as usize)
.min(self.get_line(p.line as _)?.len_bytes()),
)
.ok()
}
fn to_l_position(&self, l: usize) -> Option<lsp_types::Position> {
Some(Position {
line: self.y(l)? as _,
character: self.x_bytes(l)? as _,
})
}
fn l_range(&self, r: lsp_types::Range) -> Option<Range<usize>> {
Some(self.l_position(r.start)?..self.l_position(r.end)?)
}
fn to_l_range(&self, r: Range<usize>) -> Option<lsp_types::Range> {
Some(lsp_types::Range {
start: self.to_l_position(r.start)?,
end: self.to_l_position(r.end)?,
})
}
}
impl TextArea {
pub fn visual_position(
&self,
r: Range<usize>,
) -> Option<[(usize, usize); 2]> {
self.position(r).map(|x| x.map(|x| self.map_to_visual(x)))
}
pub fn visual_xy(&self, x: usize) -> Option<(usize, usize)> {
self.xy(x).map(|p| self.map_to_visual(p))
}
pub fn map_to_visual(&self, (x, y): (usize, usize)) -> (usize, usize) {
(
self.reverse_source_map(y)
.and_then(|mut v| v.nth(x))
.unwrap_or(x),
y,
)
}
#[inline(always)]
pub fn source_map(
&'_ self,
l: usize,
) -> Option<impl Iterator<Item = Mapping<'_>>> {
let s = self.rope.try_line_to_char(l).ok()?;
let lin = self.rope.get_line(l)?;
Some(
#[inline(always)]
gen move {
for (char, i) in lin.chars().zip(s..) {
if let Some(x) = self.inlays.get(&Marking::idx(i as _))
{
for (i, (c, _)) in x.data.iter().enumerate() {
yield Mapping::Fake(
x, i as u32, x.position, *c,
);
}
}
yield Mapping::Char(char, i - s, i);
}
},
)
}
pub fn reverse_source_map_w(
&'_ self,
w: impl Iterator<Item = Mapping<'_>>,
) -> Option<impl Iterator<Item = usize>> {
w.scan(0, |off, x| match x {
Mapping::Fake(..) => {
*off += 1;
Some(None)
}
Mapping::Char(_, i, _) => Some(Some(i + *off)),
})
.flatten()
.into()
}
pub fn reverse_source_map(
&'_ self,
l: usize,
) -> Option<impl Iterator<Item = usize>> {
self.reverse_source_map_w(self.source_map(l)?)
}
pub fn visual_eol(&self, li: usize) -> Option<usize> {
Some(self.source_map(li)?.count())
}
#[implicit_fn::implicit_fn]
#[allow(dead_code)]
pub fn raw_index_at(&self, (x, y): (usize, usize)) -> Option<usize> {
let x = x.checked_sub(self.line_number_offset() + 1)? + self.ho;
Some(self.vo + y)
.filter(|&l| {
self.rope.get_line(l).is_some_and(_.len_chars() > x)
})
.and_then(|l| Some(self.rope.try_line_to_char(l).ok()? + x))
}
pub fn visual_index_at(
&'_ self,
(x, y): (usize, usize),
) -> Option<Mapping<'_>> {
self.source_map(self.vo + y).and_then(|mut i| {
i.nth(x.checked_sub(self.line_number_offset() + 1)? + self.ho)
})
}
pub fn mapped_index_at(&'_ self, (x, y): (usize, usize)) -> usize {
match self.visual_index_at((x, y)) {
Some(Mapping::Char(_, _, index)) => index,
Some(Mapping::Fake(_, _, real, ..)) => real as _,
None => self.eol(self.vo + y),
}
}
pub fn remove(&mut self, r: Range<usize>) -> Result<(), ropey::Error> {
let removed = self
.rope
.get_slice(r.clone())
.ok_or(ropey::Error::CharIndexOutOfBounds(4, 4))?
.to_string();
self.changes.inner.push(Action::Removed {
removed: Some(removed),
range: r.clone(),
});
self.rope.try_remove(r.clone())?;
let manip = |x| {
// if your region gets removed, what happens to your tabstop? big question.
if r.contains(&x) {
// for now, simply move it.
r.start
} else {
if x >= r.end { x - r.len() } else { x }
}
};
self.tabstops.as_mut().map(|x| x.manipulate(manip));
self.cursor.manipulate(manip);
for pos in self
.inlays
.range(Marking::idx(r.end as _)..)
.map(|x| x.position)
.collect::<Vec<_>>()
{
let mut m = match self.inlays.entry(Marking::idx(pos)) {
std::collections::btree_set::Entry::Occupied(x) =>
x.remove(),
std::collections::btree_set::Entry::Vacant(_) =>
unreachable!(),
};
m.position -= r.len() as u32;
self.inlays.insert(m);
}
self.tokens.iter_mut().for_each(|d| d.manip(manip));
Ok(())
}
pub fn insert_at(
&mut self,
c: usize,
with: &str,
) -> Result<(), ropey::Error> {
self.rope.try_insert(c, with)?;
self.changes
.inner
.push(Action::Inserted { at: c, insert: with.to_string() });
let manip = |x| {
if x < c { x } else { x + with.chars().count() }
};
self.tabstops.as_mut().map(|x| x.manipulate(manip));
self.cursor.manipulate(manip);
for m in self
.inlays
.range(Marking::idx(c as _)..)
.map(|x| x.position)
.collect::<Vec<_>>()
{
let mut m = match self.inlays.entry(Marking::idx(m)) {
std::collections::btree_set::Entry::Occupied(x) =>
x.remove(),
std::collections::btree_set::Entry::Vacant(_) =>
unreachable!(),
};
m.position += with.chars().count() as u32;
self.inlays.insert(m);
}
self.tokens.iter_mut().for_each(|d| d.manip(manip));
Ok(())
}
pub fn insert(&mut self, c: &str) {
for i in 0..self.cursor.inner.len() {
let cursor = *self.cursor.inner.get(i).expect("aw dangit");
self.insert_at(cursor.position, c).expect("");
}
self.set_ho();
}
pub fn apply(&mut self, x: &TextEdit) -> Result<(usize, usize), ()> {
let begin = self.l_position(x.range.start).ok_or(())?;
let end = self.l_position(x.range.end).ok_or(())?;
self.remove(begin..end).void()?;
self.insert_at(begin, &x.new_text).void()?;
Ok((begin, end))
}
pub fn apply_adjusting(&mut self, x: &TextEdit) -> Result<(), ()> {
let (_b, e) = self.apply(&x)?;
if e < self.cursor.first().position {
if !self.visible(e) {
// line added behind, not visible
self.vo +=
x.new_text.chars().filter(|&x| x == '\n').count();
}
// let removed = e - b;
// self.cursor += x.new_text.chars().count();
// self.cursor -= removed; // compensate
// text.cursor += additional.new_text.chars().count(); // compensate
}
Ok(())
}
pub fn apply_snippet_tedit(
&mut self,
SnippetTextEdit { text_edit, insert_text_format, .. }: &SnippetTextEdit,
) -> anyhow::Result<()> {
match insert_text_format {
Some(lsp_types::InsertTextFormat::SNIPPET) =>
self.apply_snippet(&text_edit).unwrap(),
_ => {
self.apply_adjusting(text_edit).unwrap();
}
}
Ok(())
}
pub fn apply_snippet(&mut self, x: &TextEdit) -> anyhow::Result<()> {
let begin = self
.l_position(x.range.start)
.ok_or(anyhow!("couldnt get start"))?;
let end = self
.l_position(x.range.end)
.ok_or(anyhow!("couldnt get end"))?;
self.remove(begin..end)?;
let (mut sni, tex) =
crate::sni::Snippet::parse(&x.new_text, begin)
.ok_or(anyhow!("failed to parse snippet"))?;
self.insert_at(begin, &tex)?;
self.cursor.one(match sni.next() {
Some(x) => {
self.tabstops = Some(sni);
Cursor::new(x.r().end, &self.rope)
}
None => {
self.tabstops = None;
Cursor::new(
sni.last.map(|x| x.r().end).unwrap_or_else(|| {
begin + x.new_text.chars().count()
}),
&self.rope,
)
}
});
Ok(())
}
pub fn primary_cursor(&self) -> (usize, usize) {
self.cursor.first().cursor(&self.rope)
}
pub fn primary_cursor_visual(&self) -> (usize, usize) {
let (x, y) = self.primary_cursor();
let mut z = self.reverse_source_map(y).unwrap();
(z.nth(x).unwrap_or(x), y)
}
#[allow(dead_code)]
pub fn visible_(&self) -> Range<usize> {
self.rope.line_to_char(self.vo)
..self.rope.line_to_char(self.vo + self.r)
}
pub fn visible(&self, x: usize) -> bool {
(self.vo..self.vo + self.r).contains(&self.rope.char_to_line(x))
}
pub fn page_down(&mut self) {
let l = self.l();
self.cursor.each(|x| {
x.position = self.rope.line_to_char(min(
self.rope.char_to_line(**x) + self.r,
l,
));
});
// for c in &mut self.cursor {
// *c = self.rope.line_to_char(min(
// self.rope.char_to_line(*c) + self.r,
// self.l(),
// ));
// }
self.scroll_to_cursor();
}
#[lower::apply(saturating)]
pub fn page_up(&mut self) {
self.cursor.each(|x| {
x.position = self
.rope
.line_to_char(self.rope.char_to_line(**x) - self.r);
});
// self.cursor = self
// .rope
// .line_to_char(self.rope.char_to_line(self.cursor) - self.r);
self.scroll_to_cursor();
}
#[lower::apply(saturating)]
pub fn left(&mut self) {
self.cursor.left(&self.rope);
// self.cursor -= 1;
// self.setc();
// self.set_ho();
}
// #[implicit_fn]
// fn indentation(&self) -> usize {
// self.indentation_of(self.cursor().1)
// }
#[implicit_fn]
pub fn home(&mut self) {
self.cursor.home(&self.rope);
}
pub fn end(&mut self) {
self.cursor.end(&self.rope);
}
pub fn set_ho(&mut self) {
// let x = self.cursor_visual().0;
// if x < self.ho + 4 {
// self.ho = x.saturating_sub(4);
// } else if x + 4 > (self.ho + self.c) {
// self.ho = (x.saturating_sub(self.c)) + 4;
// }
}
#[lower::apply(saturating)]
pub fn right(&mut self) {
self.cursor.right(&self.rope);
}
#[implicit_fn]
pub fn word_right(&mut self) {
self.cursor.word_right(&self.rope);
}
// from μ
pub fn word_left(&mut self) {
self.cursor.word_left(&self.rope);
}
pub fn tab(&mut self) {
match &mut self.tabstops {
None => self.insert(" "),
Some(x) => match x.next() {
Some(x) => {
self.cursor.one(Cursor::new(x.r().end, &self.rope));
}
None => {
self.cursor.one(Cursor::new(
x.last.clone().unwrap().r().end,
&self.rope,
));
self.tabstops = None;
}
},
}
}
pub fn enter(&mut self) {
// let oc = self.cursor;
ceach!(self.cursor, |cursor| {
let n = cursor.indentation(&self.rope);
self.insert_at(cursor.position, "\n").unwrap();
// assert_eq!(*cursor, oc + c.chars().count());
// self.cursor = oc + c.chars().count();
// cursor.set_ho();
self.insert(&repeat_n(" ", n).collect::<String>());
});
}
pub fn down(&mut self) {
self.cursor.down(&self.rope, &mut self.vo, self.r);
}
pub fn up(&mut self) {
self.cursor.up(&self.rope, &mut self.vo);
}
pub fn backspace_word(&mut self) {
ceach!(self.cursor, |cursor| {
let c = cursor.word_left_p(&self.rope);
_ = self.remove(c..*cursor);
// FIXME maybe?
// cursor.position = c;
// cursor.setc(&self.rope);
// cursor.set_ho();
});
self.cursor.each(|cursor| {
cursor.setc(&self.rope);
cursor.set_ho();
});
}
#[lower::apply(saturating)]
pub fn backspace(&mut self) {
if let Some(tabstops) = &mut self.tabstops
&& let Some((_, StopP::Range(find))) =
tabstops.stops.get_mut(tabstops.index - 1)
&& find.end == self.cursor.first().position
{
self.cursor.one(Cursor::new(find.start, &self.rope));
let f = find.clone();
*find = find.end..find.end;
_ = self.remove(f);
} else {
ceach!(self.cursor, |cursor| {
_ = self.remove(cursor.saturating_sub(1)..*cursor);
// FIXME: maybe?
});
self.set_ho();
}
}
#[lower::apply(saturating)]
pub fn scroll_to_cursor(&mut self) {
let (_, y) = self.primary_cursor();
if !(self.vo..self.vo + self.r).contains(&y) {
if self.vo > y {
// cursor is above current view
// thus we want to keep it at the top of the view
self.vo = y - 5;
} else {
// otherwise, keep it at the bottom
self.vo = y - self.r + 5;
}
}
}
#[lower::apply(saturating)]
pub fn scroll_to_cursor_centering(&mut self) {
let (_, y) = self.primary_cursor();
if !(self.vo..self.vo + self.r).contains(&y) {
self.vo = y - (self.r / 2);
}
}
#[cold]
pub fn tree_sit<'c>(&self, path: Option<&Path>, cell: &mut Output) {
let language = path
.and_then(|x| LOADER.language_for_filename(x))
.unwrap_or_else(|| LOADER.language_for_name("rust").unwrap());
let s = self.rope.line_to_char(self.vo);
let e = self
.rope
.try_line_to_char(self.vo + self.r * self.c)
.unwrap_or(self.rope.len_chars());
for ((x1, y1), (x2, y2), s, _) in std::iter::from_coroutine(pin!(
hl(language, &self.rope, s as u32..e as u32, self.c)
)) {
cell.get_range((x1, y1), (x2, y2)).for_each(|x| x.style |= s);
}
// let mut highlight_stack = Vec::with_capacity(8);
// loop {
// let (e, new_highlights) = h.advance();
// if e == HighlightEvent::Refresh {
// highlight_stack.clear();
// }
// highlight_stack.extend(new_highlights);
// let end = h.next_event_offset() as _;
// if end == 4294967295 {
// break;
// }
// for &h in &highlight_stack {
// let y1 = self.rope.byte_to_line(at);
// let y2 = self.rope.byte_to_line(end);
// let x1 = min(
// self.rope.byte_to_char(at)
// - self.rope.line_to_char(y1),
// self.c,
// );
// let x2 = min(
// self.rope.byte_to_char(end)
// - self.rope.line_to_char(y2),
// self.c,
// );
// cell.get_mut(y1 * self.c + x1..y2 * self.c + x2).map(
// |x| {
// x.iter_mut().for_each(|x| {
// x.style.flags |= STYLES[h.idx()];
// x.style.color = COLORS[h.idx()];
// })
// },
// );
// }
// at = end;
// }
}
#[allow(dead_code)]
pub fn slice<'c>(
&self,
(c, _r): (usize, usize),
cell: &'c mut [Cell],
range: Range<usize>,
) -> Option<&'c mut [Cell]> {
self.position(range).map(|[(x1, y1), (x2, y2)]| {
&mut cell[y1 * c + x1..y2 * c + x2]
})
}
pub gen fn colored_lines(
&self,
slice: impl Iterator<Item = usize>,
leg: Option<&SemanticTokensLegend>,
) -> (usize, usize, Cell) {
let mut tokens = self.tokens.iter();
let mut curr: Option<&TokenD> = tokens.next();
// for ln in self.rope.slice(slice) {}
// let s = self.rope.char_to_line(slice.start);
for l in slice {
// let c = 0;
// let l = self.char_to_line(c);
// let relative = c - self.rope.line_to_char(l);
for (e, i) in self.source_map(l).coerce().zip(0..) {
if e.c() == '\n' {
continue;
}
let mut c = Cell::default();
c.letter = Some(e.c());
c.style = match e {
Mapping::Char(_, _, abspos) if let Some(leg) = leg =>
if let Some(curr) = curr
&& (curr.range.0..curr.range.1)
.contains(&(abspos as _))
{
curr.style(leg)
} else {
while let Some(c) = curr
&& c.range.0 < abspos as _
{
curr = tokens.next();
}
if let Some(curr) = curr
&& (curr.range.0..curr.range.1)
.contains(&(abspos as _))
{
curr.style(leg)
} else {
Style::new(crate::FG, crate::BG)
}
},
Mapping::Char(..) => Style::new(crate::FG, crate::BG),
Mapping::Fake(Marking { .. }, ..) =>
Style::new(const { color_("#536172") }, crate::BG),
};
yield (l, i, c)
}
}
}
#[implicit_fn]
pub fn write_to<'lsp>(
&self,
(into, into_s): (&mut [Cell], (usize, usize)),
(ox, oy): (usize, usize),
selection: Option<Vec<Range<usize>>>,
apply: impl FnOnce((usize, usize), &Self, Output),
path: Option<&Path>,
leg: Option<&SemanticTokensLegend>,
) {
let (c, r) = (self.c, self.r);
let mut cells = Output {
into,
output: Mapper {
into_s,
ox,
oy,
from_c: c,
from_r: r,
vo: self.vo,
ho: self.ho,
},
};
// let mut cells = vec![
// Cell {
// style: Style { color, bg, flags: 0 },
// letter: None,
// };
// (self.l().max(r) + r - 1) * c
// ];
let lns = self.vo..self.vo + r;
let mut tokens = self.tokens.iter();
let mut curr: Option<&TokenD> = tokens.next();
for (l, y) in lns.clone().map(self.source_map(_)).zip(lns) {
for (e, x) in l
.coerce()
.skip(self.ho)
// .flat_map(|x| x.chars().skip(self.ho))
.take(c)
.zip(0..)
{
if e.c() != '\n' {
cells.get((x + self.ho, y)).unwrap().letter =
Some(e.c());
cells.get((x + self.ho, y)).unwrap().style = match e {
Mapping::Char(_, _, abspos)
if let Some(leg) = leg =>
{
if let Some(curr) = curr
&& (curr.range.0..curr.range.1)
.contains(&(abspos as _))
{
curr.style(leg)
} else {
while let Some(c) = curr
&& c.range.0 < abspos as _
{
curr = tokens.next();
}
if let Some(curr) = curr
&& (curr.range.0..curr.range.1)
.contains(&(abspos as _))
{
curr.style(leg)
} else {
Style::new(crate::FG, crate::BG)
}
}
}
Mapping::Char(..) =>
Style::new(crate::FG, crate::BG),
Mapping::Fake(Marking { .. }, ..) => Style::new(
const { color_("#536172") },
crate::BG,
),
};
}
}
}
self.cursor.each_ref(|c| {
cells
.get_range(
(self.ho, self.y(*c).unwrap()),
(self.ho + *c, self.y(*c).unwrap()),
)
.for_each(|x| {
x.style.bg = const { color(b"#1a1f29") };
});
});
// let tokens = None::<(
// arc_swap::Guard<Arc<Box<[SemanticToken]>>>,
// &SemanticTokensLegend,
// )>;
if leg.is_none() {
self.tree_sit(path, &mut cells);
}
if let Some(tabstops) = &self.tabstops {
for [a, b] in
tabstops.stops.iter().skip(tabstops.index - 1).flat_map(
|(_, tabstop)| self.visual_position(tabstop.r()),
)
{
for char in cells.get_range(a, b) {
char.style.bg = [55, 86, 81];
}
}
}
selection.map(|x| {
for x in x {
let [a, b] = self.position(x).unwrap();
let a = self.map_to_visual(a);
let b = self.map_to_visual(b);
cells
.get_range_enumerated(a, b)
.filter(|(c, (x, y))| {
c.letter.is_some()
|| *x == 0
|| (self
.rope
.get_line(*y)
.map(_.len_chars())
.unwrap_or_default()
.saturating_sub(1)
== *x)
})
.for_each(|(x, _)| {
if x.letter == Some(' ') {
x.letter = Some('·'); // tabs? what are those
x.style.fg = [0x4e, 0x62, 0x79];
}
x.style.bg = [0x27, 0x43, 0x64];
// 0x23, 0x34, 0x4B
})
}
});
// for (y, inlay) in inlay
// .into_iter()
// .flatten()
// .chunk_by(|x| x.position.line)
// .into_iter()
// .filter(|&(y, _)| {
// (self.vo..self.vo + r).contains(&(y as usize))
// })
// {
// // self.l_position(inlay.position) {}
// let mut off = self.rope.line(y as _).len_chars();
// for inlay in inlay {
// let label = match &inlay.label {
// InlayHintLabel::String(x) => x.clone(),
// InlayHintLabel::LabelParts(v) =>
// v.iter().map(_.value.clone()).collect::<String>(),
// };
// cells
// .get_range((off, y as _), (!0, y as _))
// .zip(label.chars())
// .for_each(|(x, y)| {
// x.letter = Some(y);
// x.style.color = color_("#536172")
// });
// off += label.chars().count();
// }
// }
apply((c, r), self, cells);
}
pub fn line_number_offset(&self) -> usize {
self.l().ilog10() as usize + 2
}
pub fn line_numbers(
&self,
(_, r): (usize, usize),
color: [u8; 3],
bg: [u8; 3],
(into, (w, _)): (&mut [Cell], (usize, usize)),
(ox, oy): (usize, usize),
) {
for y in 0..r {
if (self.vo + y) < self.l() {
(self.vo + y + 1)
.to_string()
.chars()
.zip(into[(y + oy) * w..].iter_mut().skip(ox))
.for_each(|(a, b)| {
*b = Cell {
style: Style::new(color, bg),
letter: Some(a),
}
});
}
}
}
pub fn comment(&mut self, selection: std::ops::Range<usize>) {
let a = self.rope.char_to_line(selection.start);
let b = self.rope.char_to_line(selection.end);
let lns = (a..=b)
.filter(|l| {
!self.rope.line(*l).chars().all(char::is_whitespace)
})
.collect::<Vec<_>>();
let at =
lns.iter().map(|l| self.indentation_of(*l)).min().unwrap_or(0);
for l in lns {
let c = self.rope.line_to_char(l);
if let Some(n) = self.rope.line(l).to_string().find("//") {
if self.rope.char(n + c + 2).is_whitespace() {
_ = self.remove(c + n..c + n + 3);
} else {
_ = self.remove(c + n..c + n + 2);
}
} else {
_ = self.insert_at(c + at, "// ");
}
}
}
pub fn sticky_context<'local, 'further>(
&'local self,
syms: &'further [DocumentSymbol],
at: usize,
) -> Option<(
&'further DocumentSymbol,
std::ops::Range<usize>,
Vec<&'further DocumentSymbol>,
)> {
/// for the shortest range
fn search<'local, 'further>(
x: &'further DocumentSymbol,
best: &'local mut Option<(
&'further DocumentSymbol,
std::ops::Range<usize>,
Vec<&'further DocumentSymbol>,
)>,
look: usize,
r: &'_ Rope,
mut path: Vec<&'further DocumentSymbol>,
) {
path.push(x);
if let Some(y) = r.l_range(x.range)
&& y.contains(&look)
{
if best.as_ref().is_none_or(|(_, r, _)| r.len() > y.len())
{
*best = Some((x, y, path.clone()))
}
for lem in x.children.as_ref().coerce() {
search(lem, best, look, r, path.clone())
}
}
}
let mut best = None;
for sym in syms {
search(sym, &mut best, at, &self.rope, vec![]);
}
best
}
}
pub fn is_word(r: char) -> bool {
matches!(r, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_')
}
pub static LOADER: LazyLock<Loader> = LazyLock::new(|| {
let x = helix_core::config::default_lang_loader();
x.set_scopes(theme_treesitter::NAMES.map(|x| x.to_string()).to_vec());
// x.languages().for_each(|(_, x)| {
// x.syntax_config(&LOADER).map(|x| {
// x.configure(|x| {
// // let x = set.entry(x.to_string()).or_insert_with(|| {
// // n += 1;
// // n
// // });
// // dbg!(x);
// NAMES
// .iter()
// .position(|&y| y == x)
// .map(|x| x as u32)
// .map(helix_core::syntax::Highlight::new)
// // Some(helix_core::syntax::Highlight::new(*x))
// })
// });
// });
x
});
#[test]
pub fn man() {
let _query_str = r#"
(line_comment)+ @quantified_nodes
((line_comment)+) @quantified_nodes_grouped
((line_comment) (line_comment)) @multiple_nodes_grouped
"#;
let source = Rope::from_str(r#"assert_eq!(0, Some(0));"#);
// dbg!(source.slice(70..));
// let mut set = std::collections::HashMap::new();
let _n = 0;
let loader = &*LOADER;
// loader.set_scopes(nam.map(|x| x.to_string()).to_vec());
let language = loader.language_for_name("rust").unwrap();
// for lang in [
// "rust-format-args",
// "rust-format-args-macro",
// "rust",
// "markdown-rustdoc",
// "comment",
// "regex",
// ] {
// let c = LOADER
// .language(LOADER.language_for_name(lang).unwrap())
// .syntax_config(&LOADER)
// .unwrap();
// reconfigure_highlights(c, &NAMES.map(|x| x.to_string()));
// // c.configure(|x| {
// // // NAMES
// // // .iter()
// // // .position(|&y| y == x)
// // // .map(|x| x as u32)
// // // .map(helix_core::syntax::Highlight::new)
// // let x = set.entry(x.to_string()).or_insert_with(|| {
// // n += 1;
// // n
// // });
// // dbg!(*x);
// // Some(helix_core::syntax::Highlight::new(*x))
// // })
// }
// let mut set = std::collections::HashMap::new();
// LOADER.languages().for_each(|(_, x)| {
// x.syntax_config(&LOADER).map(|x| {
// x.configure(|x| {
// // let x = set.entry(x.to_string()).or_insert_with(|| {
// // n += 1;
// // n
// // });
// // dbg!(x);
// NAMES
// .iter()
// .position(|&y| y == x)
// .map(|x| x as u32)
// .map(helix_core::syntax::Highlight::new)
// // Some(helix_core::syntax::Highlight::new(*x))
// })
// });
// });
// let c = LOADER.languages().next().unwrap().1;
// let grammar = LOADER.get_config(language).unwrap().grammar;
// let query = Query::new(grammar, query_str, |_, _| Ok(())).unwrap();
// let textobject = TextObjectQuery::new(query);
// reconfigure_highlights(
// LOADER.get_config(language).unwrap(),
// &NAMES.map(|x| x.to_string()),
// );
let syntax = Syntax::new(source.slice(..), language, &loader).unwrap();
let mut h = syntax.highlighter(
source.slice(..),
&loader,
0..source.len_chars() as u32,
);
println!(
"{}",
tree_house::fixtures::highlighter_fixture(
"hmm",
&loader,
// |y| set
// .iter()
// .find(|x| x.1 == &y.get())
// .unwrap()
// .0
// .to_string(),
|y| theme_treesitter::NAMES[y.idx()].to_string(),
&syntax.inner,
source.slice(..),
..,
)
);
for _n in 0..40 {
dbg!(h.next_event_offset());
let (e, _hl) = h.advance();
dbg!(e);
// dbg!(hl.map(|x| NAMES[x.idx()]).collect::<Vec<_>>(), e);
dbg!(
h.active_highlights()
// .map(|y| set
// .iter()
// .find(|x| x.1 == &y.get())
// .unwrap()
// .0
// .to_string())
.map(|x| theme_treesitter::NAMES[x.idx()])
.collect::<Vec<_>>()
);
// panic!()
}
// panic!();
// let root = syntax.tree().root_node();
// let test = |capture, range| {
// let matches: Vec<_> = textobject
// .capture_nodes(capture, &root, source.slice(..))
// .unwrap()
// .collect();
// assert_eq!(
// matches[0].byte_range(),
// range,
// "@{} expected {:?}",
// capture,
// range
// )
// };
// test("quantified_nodes", 1..37);
// panic!()
}
pub fn hl(
lang: Language,
text: &'_ Rope,
r: impl RangeBounds<u32>,
c: usize,
) -> impl std::ops::Coroutine<
Yield = ((usize, usize), (usize, usize), (u8, [u8; 3]), RopeSlice<'_>),
Return = (),
> {
// println!(
// "{}",
// tree_house::fixtures::highlighter_fixture(
// "hmm",
// &*LOADER,
// |y| NAMES[y.idx()].to_string(),
// &syntax.inner,
// self.rope.slice(..),
// ..,
// )
// );
#[coroutine]
static move || {
let Ok(syntax) = Syntax::new(text.slice(..), lang, &LOADER) else {
return;
};
let mut h = syntax.highlighter(text.slice(..), &LOADER, r);
let mut at = 0;
let mut highlight_stack = Vec::with_capacity(8);
loop {
let (e, new_highlights) = h.advance();
if e == HighlightEvent::Refresh {
highlight_stack.clear();
}
highlight_stack.extend(new_highlights);
let end = h.next_event_offset() as _;
if end == 4294967295 {
break;
}
if end < at {
at = end;
continue;
}
for &h in &highlight_stack {
let y1 = text.byte_to_line(at);
let y2 = text.byte_to_line(end);
let x1 =
min(text.byte_to_char(at) - text.line_to_char(y1), c);
let x2 =
min(text.byte_to_char(end) - text.line_to_char(y2), c);
yield (
(x1, y1),
(x2, y2),
(
theme_treesitter::STYLES[h.idx()],
theme_treesitter::COLORS[h.idx()],
),
(text.byte_slice(at..end)),
)
}
at = end;
}
}
// };
// std::iter::from_fn(move || {
// //
// use std::ops::Coroutine;
// Some(Pin::new(&mut x).resume(()))
// })
}
#[derive(Copy, Clone)]
/// this struct is made to mimic a simple 2d vec
/// over the entire text area
/// without requiring that entire allocation, and the subsequent copy into the output area.
/// ```text
/// text above view offset (global text area)²
/// ╶╶╶╶╶├╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶ view offset
/// ┏━━━━┿━━━━━━━━━━━━┓← into_ and into_s³
/// ┃ ╏← offset y ┃
/// ┃ ┏╺╺┿╺╺╺╺╺╺╺╺╺╺╺╺┃
/// ┃ ╏ v╎iewable area┃
/// ┃ ╏ g╎oes here¹ ┃
/// ┃ ╏ ╎ ┃
/// ┃ ╏ ╎ ┃
/// ┃ ╏ ╎ ┃
/// ┗━━━━━━━━━━━━━━━━━┛
/// ═ ══╎
/// ↑ ↑ ╎
/// │ ╰ horiz scroll
/// ╰ horizontal offset
/// ```
#[allow(dead_code)] // ?
pub struct Mapper {
/// c, r
pub into_s: (usize, usize),
pub ox: usize,
pub oy: usize,
pub from_c: usize,
pub from_r: usize,
pub vo: usize,
pub ho: usize,
}
pub struct Output<'a> {
pub into: &'a mut [Cell],
pub output: Mapper,
}
impl Deref for Output<'_> {
type Target = Mapper;
fn deref(&self) -> &Self::Target {
&self.output
}
}
impl Mapper {
/// translate an index into the global text buffer² into an (x, y), of the global text buffer
// fn to_point(&self, rope: &Rope, index: usize) -> (usize, usize) {
// ((index % self.from_c), (index / self.from_c))
// }
// fn from_point(&self, (x, y): (usize, usize)) -> usize {
// y * self.from_c + x
// }
/// translate an (x, y) into the global text buffer²
/// to a point over the viewable area¹ (offset by the given offsets) of the global text buffer,
/// returning none if the given point is outside of the viewable area
fn translate(&self, (x, y): (usize, usize)) -> Option<(usize, usize)> {
let (x, y) = (
x.checked_sub(self.ho)? + self.ox,
y.checked_sub(self.vo)? + self.oy,
);
((x < self.into_s.0) & (y < self.into_s.1)
& (x >= self.ox) // this is kind of forced already but its ok
& (y >= self.oy)) // "
.then_some((x, y))
}
/// converts an (x, y) of the viewable area¹ to an index into [`Output::into`]
fn from_point_global(&self, (x, y): (usize, usize)) -> usize {
y * self.into_s.0 + x
}
// /// translate an index into a (x, y), of the output³
// fn to_point_global(&self, index: usize) -> (usize, usize) {
// (index % self.into_s.0, index / self.into_s.0)
// }
// fn etalsnart_(
// &self,
// (x, y): (usize, usize),
// ) -> Option<(usize, usize)> {
// Some((
// x.checked_sub(self.ox)? + self.ho, //
// y.checked_sub(self.oy)? + self.vo,
// ))
// }
}
impl<'a> Output<'a> {
// /// get an index thats relative over the viewable area¹ of the global text area
// fn get_at_compensated(
// &mut self,
// (x, y): (usize, usize),
// ) -> Option<&mut Cell> {
// Some(&mut self.into[(y + self.oy) * self.into_s.0 + (x + self.ox)])
// }
pub fn get_range(
&mut self,
a: (usize, usize),
b: (usize, usize),
) -> impl Iterator<Item = &mut Cell> {
self.get_range_enumerated(a, b).map(|x| x.0)
}
// needs rope to work properly (see [xy])
// pub fn get_char_range(
// &mut self,
// a: usize,
// b: usize,
// ) -> impl Iterator<Item = &mut Cell> {
// self.get_range_enumerated(self.to_point(a), self.to_point(b))
// .map(|x| x.0)
// }
//// coords reference global text buffer²
pub gen fn get_range_enumerated(
&mut self,
(x1, y1): (usize, usize),
(x2, y2): (usize, usize),
// impl Iterator<Item = (&mut Cell, (usize, usize))> {
) -> (&mut Cell, (usize, usize)) {
let m = self.output;
let c = self.into.as_mut_ptr();
// x1 = x1.checked_sub(m.ho).unwrap_or(m.ho);
// x2 = x2.checked_sub(m.ho).unwrap_or(m.ho);
// y1 = y1.checked_sub(m.vo).unwrap_or(m.vo);
// y2 = y2.checked_sub(m.vo).unwrap_or(m.vo);
// let a = m.from_point((x1, y1));
// let b = m.from_point((x2, y2));
// let a = m.from_point_global(m.translate(m.to_point(a)).unwrap());
// let b = m.from_point_global(m.translate(m.to_point(b)).unwrap());
// dbg!(a, b);
let mut p = (x1, y1);
while p != (x2, y2) {
if let Some(x) = m.translate(p) {
// SAFETY: trust me very disjoint
yield (unsafe { &mut *c.add(m.from_point_global(x)) }, p)
}
p.0 += 1;
if p.0.checked_sub(m.ho) == Some(m.from_c) {
p.0 = 0;
p.1 += 1;
if p.1 > y2 {
break;
}
}
if let Some(x) = p.0.checked_sub(m.ho)
&& x > m.from_c
{
break;
}
}
// (a..=b)
// .filter_map(move |x| {
// // println!("{:?} {x}", m.translate(m.to_point(x)));
// let (x, y) = m.to_point(x);
// m.translate((x, y))
// // m.to_point_global(x)
// })
// .inspect(move |x| {
// assert!(x.0 < self.into_s.0 && x.1 < self.into_s.1);
// assert!(m.from_point_global(*x) < self.into.len());
// })
// .map(move |x| {
// // SAFETY: :)
// (unsafe { &mut *p.add(m.from_point_global(x)) }, x)
// })
// self.get_char_range_enumerated(
// self.output.from_point(a),
// self.output.from_point(b),
// )
}
// oughtnt really be multiline
pub fn get_simple(
&mut self,
s: (usize, usize),
e: (usize, usize),
) -> Option<&mut [Cell]> {
let s = self.from_point_global(self.translate(s)?);
let e = self.from_point_global(self.translate(e)?);
self.into.get_mut(s..e)
}
// impl<'a> IndexMut<(usize, usize)> for Output<'a> {
// fn index_mut(&mut self, p: (usize, usize)) -> &mut Self::Output {
// let x = self.from_point_global(self.translate(p).unwrap());
// &mut self.into[x]
// }
// }
pub fn get(&mut self, p: (usize, usize)) -> Option<&mut Cell> {
let n = self.from_point_global(self.translate(p)?);
self.into.get_mut(n)
}
}
// impl<'a> Index<usize> for Output<'a> {
// type Output = Cell;
// fn index(&self, index: usize) -> &Self::Output {
// &self[self.translate(index).unwrap()]
// }
// }
// impl<'a> IndexMut<usize> for Output<'a> {
// fn index_mut(&mut self, index: usize) -> &mut Self::Output {
// let x = self.translate(index).unwrap();
// &mut self[x]
// }
// }
// impl<'a> Index<(usize, usize)> for Output<'a> {
// type Output = Cell;
// fn index(&self, p: (usize, usize)) -> &Self::Output {
// &self.into[self.from_point_global(self.translate(p).unwrap())]
// }
// }
// impl<'a> IndexMut<(usize, usize)> for Output<'a> {
// fn index_mut(&mut self, p: (usize, usize)) -> &mut Self::Output {
// let x = self.from_point_global(self.translate(p).unwrap());
// &mut self.into[x]
// }
// }
pub trait CoerceOption<T> {
fn coerce(self) -> impl Iterator<Item = T>;
}
impl<I: IntoIterator<Item = T>, T> CoerceOption<T> for Option<I> {
#[allow(refining_impl_trait)]
fn coerce(self) -> std::iter::Flatten<std::option::IntoIter<I>> {
self.into_iter().flatten()
}
}
// #[test]
pub(crate) use col;
#[derive(Debug, PartialEq, Clone)]
pub enum Mapping<'a> {
Fake(
&'a Marking<Box<[(char, Option<Location>)]>>,
/// Label relative
u32,
/// True position
u32,
char,
),
Char(char, usize /* line rel */, usize /* true position */),
}
impl Mapping<'_> {
fn c(&self) -> char {
let (Mapping::Char(x, ..) | Mapping::Fake(.., x)) = self;
*x
}
}
#[test]
fn apply() {
let mut t = TextArea::default();
t.insert(
r#"fn main() {
let x = 4;
}
"#,
);
t.apply_snippet(&TextEdit {
range: lsp_types::Range {
start: Position { line: 0, character: 8 },
end: Position { line: 0, character: 9 },
},
new_text: "$0var_name".into(),
})
.unwrap();
t.apply_adjusting(&TextEdit {
range: lsp_types::Range {
start: Position { line: 1, character: 4 },
end: Position { line: 1, character: 4 },
},
new_text: "let x = var_name;\n ".to_owned(),
})
.unwrap();
assert_eq!(t.cursor.first().position, 8);
}
#[test]
fn apply2() {
let mut t = TextArea::default();
t.insert(
"impl Editor { // 0
pub fn open(f: &Path) { // 1
// 2
let new = std::fs::read_to_string(f) // 3
.map_err(anyhow::Error::from)?; // 4
}
}",
);
use lsp_types::Range;
let mut th = [
TextEdit {
range: Range {
start: Position { line: 1, character: 0 },
end: Position { line: 1, character: 4 },
},
new_text: "".into(),
},
TextEdit {
range: Range {
start: Position { line: 2, character: 0 },
end: Position { line: 3, character: 1 },
},
new_text: "".into(),
},
TextEdit {
range: Range {
start: Position { line: 3, character: 9 },
end: Position { line: 3, character: 9 },
},
new_text: "let new =\n".into(),
},
TextEdit {
range: Range {
start: Position { line: 3, character: 20 },
end: Position { line: 3, character: 29 },
},
new_text: "".into(),
},
TextEdit {
range: Range {
start: Position { line: 3, character: 56 },
end: Position { line: 4, character: 24 },
},
new_text: "".into(),
},
TextEdit {
range: Range {
start: Position { line: 6, character: 1 },
end: Position { line: 6, character: 1 },
},
new_text: "\n".into(),
},
];
th.sort_tedits();
for th in th {
t.apply(&th).unwrap();
println!("=>\n{}", t.rope);
}
assert_eq!(
t.rope.to_string(),
"impl Editor { // 0
pub fn open(f: &Path) { // 1
let new =
std::fs::read_to_string(f).map_err(anyhow::Error::from)?; // 4
}
}
"
);
}
pub trait SortTedits {
fn sort_tedits(&mut self);
}
impl SortTedits for [TextEdit] {
fn sort_tedits(&mut self) {
self.as_mut().sort_by_key(|t| Reverse(t.range.start));
}
}
impl SortTedits for [SnippetTextEdit] {
fn sort_tedits(&mut self) {
self.as_mut().sort_by_key(|t| Reverse(t.text_edit.range.start));
}
}
#[test]
fn inlays() {
use lsp_types::{InlayHint, InlayHintLabel};
let mut t = TextArea::default();
_ = t.insert("let x = 4;");
t.set_inlay(&[InlayHint {
position: Position { line: 0, character: 4 },
label: InlayHintLabel::String("u".into()),
kind: Some(lsp_types::InlayHintKind::TYPE),
text_edits: None,
tooltip: None,
padding_left: None,
padding_right: None,
data: None,
}]);
use Mapping::*;
assert_eq!(
t.source_map(0).unwrap().collect::<Vec<_>>(),
vec![
Char('l', 0, 0),
Char('e', 1, 1),
Char('t', 2, 2),
Char(' ', 3, 3),
Fake(
&Marking { position: 4, data: Box::new([('u', None)]) },
0,
4,
'u'
),
Char('x', 4, 4),
Char(' ', 5, 5),
Char('=', 6, 6),
Char(' ', 7, 7),
Char('4', 8, 8),
Char(';', 9, 9)
]
);
}