A simple CPU rendered GUI IDE experience.
sticky context
bendn 5 days ago
parent 64a2a6e · commit 0882d18
-rw-r--r--src/edi.rs21
-rw-r--r--src/lsp.rs6
-rw-r--r--src/main.rs1
-rw-r--r--src/rnd.rs100
-rw-r--r--src/text.rs151
-rw-r--r--src/text/inlay.rs2
6 files changed, 246 insertions, 35 deletions
diff --git a/src/edi.rs b/src/edi.rs
index 8014348..67a0f3d 100644
--- a/src/edi.rs
+++ b/src/edi.rs
@@ -113,6 +113,13 @@ pub struct Requests {
RequestError<lsp_request!("textDocument/definition")>,
>,
#[serde(skip)]
+ pub document_symbols: Rq<
+ Option<Vec<DocumentSymbol>>,
+ Option<DocumentSymbolResponse>,
+ (),
+ RequestError<lsp_request!("textDocument/documentSymbol")>,
+ >,
+ #[serde(skip)]
pub git_diff: Rq<imara_diff::Diff, imara_diff::Diff, (), ()>,
}
#[derive(
@@ -195,7 +202,12 @@ macro_rules! change {
}
.ok_or(())
});
+ let origin = origin.to_owned();
$self.requests.git_diff.request(t);
+ if $self.requests.document_symbols.result != Some(None) {
+ let h = x.runtime.spawn(async move { x.document_symbols(&origin).await });
+ $self.requests.document_symbols.request(h);
+ }
});
};
}
@@ -493,6 +505,15 @@ impl Editor {
);
self.requests.hovering.poll(|x, _| x.ok().flatten(), &r);
self.requests.git_diff.poll(|x, _| x.ok(), &r);
+ self.requests.document_symbols.poll(
+ |x, _| {
+ x.ok().flatten().map(|x| match x {
+ DocumentSymbolResponse::Flat(_) => None,
+ DocumentSymbolResponse::Nested(x) => Some(x),
+ })
+ },
+ &r,
+ );
}
#[implicit_fn]
pub fn cursor_moved(
diff --git a/src/lsp.rs b/src/lsp.rs
index 40df88a..df96a9b 100644
--- a/src/lsp.rs
+++ b/src/lsp.rs
@@ -32,7 +32,7 @@ use tokio_util::task::AbortOnDropHandle;
use winit::window::Window;
use crate::text::cursor::ceach;
-use crate::text::{SortTedits, TextArea};
+use crate::text::{RopeExt, SortTedits, TextArea};
#[derive(Debug)]
pub struct Client {
pub runtime: tokio::runtime::Runtime,
@@ -479,6 +479,10 @@ impl Client {
t.cursor.first_mut().position = t.l_position(x).unwrap();
}
}
+
+ pub fn legend(&self) -> Option<&SemanticTokensLegend> {
+ match &self.initialized{Some(lsp_types::InitializeResult {capabilities: ServerCapabilities {semantic_tokens_provider:Some(SemanticTokensServerCapabilities::SemanticTokensOptions(SemanticTokensOptions{legend,..})),..}, ..})=> {Some(legend)},_ => None,}
+ }
pub fn inlay(
&'static self,
f: &Path,
diff --git a/src/main.rs b/src/main.rs
index 65be56b..fe94363 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -32,7 +32,6 @@
coroutine_trait,
cell_get_cloned,
import_trait_associated_functions,
- if_let_guard,
deref_patterns,
generic_const_exprs,
const_trait_impl,
diff --git a/src/rnd.rs b/src/rnd.rs
index b22f20e..3a976f8 100644
--- a/src/rnd.rs
+++ b/src/rnd.rs
@@ -1,4 +1,4 @@
-use std::iter::once;
+use std::iter::{chain, once, repeat_n};
use std::os::fd::AsFd;
use std::sync::{Arc, LazyLock};
use std::time::Instant;
@@ -19,7 +19,7 @@ use winit::window::Window;
use crate::edi::st::State;
use crate::edi::{Editor, lsp_m};
use crate::lsp::Rq;
-use crate::text::{CoerceOption, RopeExt, col};
+use crate::text::{CoerceOption, RopeExt, col, color_};
use crate::{
BG, BORDER, CompletionAction, CompletionState, FG, FONT, com, filter,
lsp, sig,
@@ -143,7 +143,6 @@ pub fn render(
diag.iter().flat_map(|diag| {
let sev = diag.severity.unwrap_or(DiagnosticSeverity::ERROR);
let sev_ = match sev {
-
DiagnosticSeverity::ERROR => EType::Error,
DiagnosticSeverity::WARNING => EType::Warning,
DiagnosticSeverity::HINT => EType::Hint,
@@ -357,7 +356,7 @@ pub fn render(
);
let is_above = position.1.checked_sub(h).is_some();
let top = position.1.checked_sub(h).unwrap_or(
- ((((_y + 1) as f32) * (fh + ls * fac)).round() + toy)
+ ((((_y/*+ 1*/) as f32) * (fh + ls * fac)).round() + toy)
as usize,
);
let (_, y) = dsb::fit(
@@ -395,6 +394,98 @@ pub fn render(
};
Ok((is_above, left, top, w, h))
};
+ // dbg!(&ed.requests.document_symbols);
+
+ if let Some(Some(x)) = &ed.requests.document_symbols.result
+ && let Some((_, y, z)) = text.sticky_context(
+ &x,
+ text.line_to_char(text.vo.saturating_sub(1)),
+ )
+ && let Ok(l) = text.try_line_to_char(text.vo + 4)
+ && y.contains(&l)
+ // && text.cursor.iter().any(|x| y.contains(&*x))
+ {
+ let mut cells = vec![];
+ for e in z {
+ let p = text.l_position(e.selection_range.start).unwrap();
+ if text.char_to_line(text.l_position(e.range.end).unwrap())
+ == text.vo
+ {
+ continue;
+ }
+
+ let r = if let Some(r) = e.sticky_range {
+ let r = text.l_range(r).unwrap();
+ text.char_to_line(r.start)..=text.char_to_line(r.end)
+ } else {
+ let x = text.char_to_line(p);
+ x..=x
+ };
+
+ let d = Cell {
+ style: Style {
+ fg: crate::FG,
+ bg: col!("#191d27"),
+ ..Default::default()
+ },
+ letter: None,
+ };
+
+ // let mut cells =
+ // chain([Cell::default()], x.to_string().chars().map(Cell::basic)).chain([Cell::default()]).collect();
+
+ for (x, rel, mut cell) in text
+ .colored_lines(r, lsp_m!(ed).and_then(|x| x.legend()))
+ {
+ if rel == 0 {
+ let rem = cells.len() % c;
+ if rem != 0 {
+ cells.extend(repeat_n(d, c - rem));
+ }
+ cells.extend(
+ chain(
+ [d],
+ x.to_string().chars().map(|x| {
+ Style::new(
+ [67, 76, 87],
+ col!("#191d27"),
+ )
+ .basic(x)
+ }),
+ )
+ .chain([d]),
+ );
+ }
+ cell.style.bg = col!("#191d27");
+ cells.push(cell);
+ }
+
+ let rem = cells.len() % c;
+ if rem != 0 {
+ cells.extend(repeat_n(d, c - rem));
+ }
+ }
+
+ if let Ok((.., w, h)) = place_around(
+ (0, 0),
+ i.copy(),
+ &cells,
+ c,
+ 14.0,
+ -200.,
+ 0.,
+ 0.,
+ 0.,
+ ) {
+ i.filled_box(
+ (w as u32, 0),
+ i.width() - w as u32,
+ h as _,
+ color_("#191d27"),
+ );
+ }
+ // dbg !(x);
+ }
let mut pass = true;
if let Some((lsp, p)) = lsp_m!(ed + p)
&& let Some(diag) = lsp.diagnostics.get(
@@ -627,6 +718,7 @@ pub fn render(
}
_ => None,
};
+
'out: {
if let Rq { result: Some((ref x, vo, ref mut max)), .. } =
ed.requests.sig_help
diff --git a/src/text.rs b/src/text.rs
index bdf9078..00a2e65 100644
--- a/src/text.rs
+++ b/src/text.rs
@@ -16,7 +16,8 @@ use helix_core::Syntax;
use helix_core::syntax::{HighlightEvent, Loader};
use implicit_fn::implicit_fn;
use lsp_types::{
- Location, Position, SemanticTokensLegend, SnippetTextEdit, TextEdit,
+ DocumentSymbol, Location, Position, SemanticTokensLegend,
+ SnippetTextEdit, TextEdit,
};
use ropey::{Rope, RopeSlice};
use serde::{Deserialize, Serialize};
@@ -177,6 +178,12 @@ pub trait RopeExt {
/// 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(
@@ -238,6 +245,33 @@ impl RopeExt for Rope {
.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 {
@@ -739,33 +773,54 @@ impl TextArea {
})
}
- pub fn l_pos_to_char(&self, p: Position) -> Option<(usize, usize)> {
- self.l_position(p).and_then(|x| self.xy(x))
- }
-
- pub fn l_position(&self, p: Position) -> Option<usize> {
- self.rope
- .try_byte_to_char(
- self.rope.try_line_to_byte(p.line as _).ok()?
- + (p.character as usize)
- .min(self.rope.get_line(p.line as _)?.len_bytes()),
- )
- .ok()
- }
- pub fn to_l_position(&self, l: usize) -> Option<lsp_types::Position> {
- Some(Position {
- line: self.y(l)? as _,
- character: self.x_bytes(l)? as _,
- })
- }
- pub fn l_range(&self, r: lsp_types::Range) -> Option<Range<usize>> {
- Some(self.l_position(r.start)?..self.l_position(r.end)?)
- }
- pub 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)?,
- })
+ 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]
@@ -984,6 +1039,46 @@ impl TextArea {
}
}
}
+ 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 {
diff --git a/src/text/inlay.rs b/src/text/inlay.rs
index cacc9f9..37b98cf 100644
--- a/src/text/inlay.rs
+++ b/src/text/inlay.rs
@@ -2,7 +2,7 @@ use Default::default;
use lsp_types::{InlayHint, InlayHintLabel, Location};
use serde_derive::{Deserialize, Serialize};
-use crate::text::TextArea;
+use crate::text::{RopeExt, TextArea};
pub type Inlay = Marking<Box<[(char, Option<Location>)]>>;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]