ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
129 lines
4.5 KiB
Rust
129 lines
4.5 KiB
Rust
//! Formula entry: reference marks and function hints (TABLE-03).
|
|
|
|
use ely_gpui_component::{
|
|
editor::{CompletionItem, CompletionMenu, Signature, SignatureHelp, SymbolKind, completions},
|
|
primitives::raise,
|
|
};
|
|
use gpui::{
|
|
AnyElement, Bounds, Context, Hsla, IntoElement, Keystroke, Modifiers, ParentElement, Pixels,
|
|
anchored, rgb,
|
|
};
|
|
|
|
use super::{LokView, input::Input};
|
|
|
|
impl LokView {
|
|
/// The sheet shown, which reference marks follow.
|
|
pub fn set_part(&mut self, part: usize) {
|
|
self.part = part;
|
|
}
|
|
|
|
/// The shown sheet's reference marks, in window pixels.
|
|
pub(super) fn marks(&self) -> Vec<(Bounds<Pixels>, Hsla)> {
|
|
self.cell
|
|
.marks
|
|
.iter()
|
|
.filter(|mark| mark.part == self.part)
|
|
.map(|mark| (self.screen_rect(mark.rect), rgb(mark.color).into()))
|
|
.collect()
|
|
}
|
|
|
|
/// The offered functions, in the order the menu shows.
|
|
fn offered(&self) -> Vec<CompletionItem> {
|
|
let items: Vec<CompletionItem> = self
|
|
.cell
|
|
.functions
|
|
.iter()
|
|
.map(|function| {
|
|
CompletionItem::new(function.name.clone(), SymbolKind::Function)
|
|
.detail(function.signature.clone())
|
|
.docs(function.description.clone())
|
|
})
|
|
.collect();
|
|
completions(&items, "")
|
|
.into_iter()
|
|
.map(|(item, _)| item)
|
|
.collect()
|
|
}
|
|
|
|
/// Up and Down walk the offered functions.
|
|
pub(super) fn walk(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) -> bool {
|
|
let count = self.cell.functions.len();
|
|
if count == 0 || keystroke.modifiers != Modifiers::none() {
|
|
return false;
|
|
}
|
|
self.picked = match keystroke.key.as_str() {
|
|
"down" => (self.picked + 1) % count,
|
|
"up" => (self.picked + count - 1) % count,
|
|
_ => return false,
|
|
};
|
|
cx.notify();
|
|
true
|
|
}
|
|
|
|
/// Takes the chosen function, in order with typing.
|
|
pub(super) fn complete(&mut self) -> bool {
|
|
let Some(item) = self.offered().into_iter().nth(self.picked) else {
|
|
return false;
|
|
};
|
|
log::info!("lok: completes {}", item.label);
|
|
self.send(Input::Complete(item.label.to_string()));
|
|
true
|
|
}
|
|
|
|
/// The offered functions, else the call's usage, under the cell.
|
|
pub(super) fn hints(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
|
|
let cell = self.screen_rect(self.cell.cursor?);
|
|
let hint = if self.cell.functions.is_empty() {
|
|
SignatureHelp::new("lok-view-usage", [usage(&self.cell.usage)?]).into_any_element()
|
|
} else {
|
|
let this = cx.entity();
|
|
let offered = self.offered();
|
|
let labels: Vec<_> = offered.iter().map(|item| item.label.clone()).collect();
|
|
CompletionMenu::new("lok-view-functions", offered)
|
|
.selected(self.picked)
|
|
.on_pick(move |item, _, cx| {
|
|
let at = labels.iter().position(|label| *label == item.label);
|
|
this.update(cx, |view, cx| {
|
|
view.picked = at.expect("a picked item is offered");
|
|
view.complete();
|
|
cx.notify();
|
|
})
|
|
})
|
|
.into_any_element()
|
|
};
|
|
let place = anchored()
|
|
.position(cell.bottom_left())
|
|
.snap_to_window()
|
|
.child(hint);
|
|
Some(raise("lok-view-hints", place).into_any_element())
|
|
}
|
|
}
|
|
|
|
/// The kernel's usage text as a signature.
|
|
fn usage(text: &str) -> Option<Signature> {
|
|
let (call, docs) = text.split_once(" : ").unwrap_or((text, ""));
|
|
let at = call.find('►')?;
|
|
let label = call.replacen('►', "", 1);
|
|
let rest = &label[at..];
|
|
let end = at + rest.find([',', ';', ')']).unwrap_or(rest.len());
|
|
Some(Signature {
|
|
label: label.clone().into(),
|
|
params: std::iter::once(at..end).collect(),
|
|
docs: (!docs.is_empty()).then(|| docs.to_string().into()),
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::usage;
|
|
|
|
#[test]
|
|
fn the_kernel_usage_reads_as_a_signature() {
|
|
let signature = usage("SUM(► Number 1, Number 2) : The numbers.").expect("a usage");
|
|
assert_eq!(signature.label.as_ref(), "SUM( Number 1, Number 2)");
|
|
assert_eq!(&signature.label[signature.params[0].clone()], " Number 1");
|
|
assert_eq!(signature.docs.as_deref(), Some("The numbers."));
|
|
assert!(usage("").is_none());
|
|
}
|
|
}
|