Ely Office: six offline-first desktop apps in Rust and GPUI

ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git),
ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-08 21:20:08 -04:00
commit b7fca0146d
817 files changed
+143101

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
//! A document's requests: size, pages, tiles, saving.
use std::io;
use super::{Doc, Rect, Tile, Worker};
use crate::ipc::{Pack, Unpack, tag};
/// How a PDF goes out: pages, comments, tracked changes.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Pdf {
/// First and last page, from 1; None is every page.
pub pages: Option<(u32, u32)>,
/// Comments print in the margin.
pub comments: bool,
/// Tracked changes show as marked.
pub changes: bool,
/// Slides per handout page; 0 prints the slides.
pub handouts: u8,
}
/// One sheet: its name and whether it shows.
#[derive(Clone, Debug, PartialEq)]
pub struct Sheet {
pub name: String,
pub visible: bool,
}
/// The sheets of a spreadsheet and the shown one.
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Sheets {
pub current: usize,
pub list: Vec<Sheet>,
}
impl Doc {
/// The sheets, in order, and the one shown.
pub fn sheets(&self, worker: &mut Worker) -> io::Result<Sheets> {
let payload = worker.request(tag::SHEETS, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let mut unpack = Unpack::new(&payload);
let current = usize::try_from(unpack.u64()?).map_err(io::Error::other)?;
let count = unpack.u64()?;
let list = (0..count)
.map(|_| {
let name = unpack.str()?;
let info: serde_json::Value = serde_json::from_str(&unpack.str()?)
.map_err(|error| io::Error::other(format!("bad part info: {error}")))?;
let visible = match info["visible"].as_u64() {
Some(1) => true,
Some(0) => false,
_ => return Err(io::Error::other(format!("no visibility: {info}"))),
};
Ok(Sheet { name, visible })
})
.collect::<io::Result<_>>()?;
Ok(Sheets { current, list })
}
/// Tells the kernel the area the view shows, in twips.
pub fn visible_area(&self, worker: &mut Worker, (x, y, w, h): Rect) -> io::Result<()> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.i64(x.into())
.i64(y.into())
.i64(w.into())
.i64(h.into())
.bytes();
worker.request(tag::VISIBLE_AREA, &request)?;
Ok(())
}
/// The selection as plain text; Calc joins cells by tabs.
pub fn selection_text(&self, worker: &mut Worker) -> io::Result<String> {
let payload = worker.request(tag::SELECTION_TEXT, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
Unpack::new(&payload).str()
}
/// Completes the function name being typed (TABLE-03).
pub fn complete_function(&self, worker: &mut Worker, name: &str) -> io::Result<()> {
log::info!("lok: completes a function");
let request = Pack::new().u64(worker.doc_id(self)?).str(name).bytes();
worker.request(tag::COMPLETE_FUNCTION, &request)?;
Ok(())
}
/// Shows sheet `part`, from 0.
pub fn show_sheet(&self, worker: &mut Worker, part: usize) -> io::Result<()> {
let request = Pack::new().u64(worker.doc_id(self)?).u64(part as u64).bytes();
worker.request(tag::SHOW_SHEET, &request)?;
Ok(())
}
/// Page count or area in LOK twips.
pub fn size(&self, worker: &mut Worker) -> io::Result<(i64, i64)> {
let payload = worker.request(tag::DOC_SIZE, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let mut unpack = Unpack::new(&payload);
Ok((unpack.i64()?, unpack.i64()?))
}
/// Page rectangles in twips; empty without pages.
pub fn pages(&self, worker: &mut Worker) -> io::Result<Vec<Rect>> {
let payload = worker.request(tag::PAGES, &Pack::new().u64(worker.doc_id(self)?).bytes())?;
let text = Unpack::new(&payload).str()?;
text.split(';')
.filter(|part| !part.trim().is_empty())
.map(|part| {
let numbers: Vec<i32> = part
.split(',')
.map(|n| {
n.trim()
.parse()
.map_err(|_| io::Error::other(format!("a bad page rect: {part}")))
})
.collect::<io::Result<_>>()?;
match numbers.as_slice() {
[x, y, w, h] => Ok((*x, *y, *w, *h)),
_ => Err(io::Error::other(format!("a bad page rect: {part}"))),
}
})
.collect()
}
/// Paints one tile: twips rect into a pixel size.
pub fn paint_tile(
&self,
worker: &mut Worker,
twips: (i32, i32, i32, i32),
pixels: (u32, u32),
) -> io::Result<Tile> {
let (x, y, w, h) = twips;
let (pw, ph) = pixels;
let payload = worker.request(
tag::PAINT_TILE,
&Pack::new()
.u64(worker.doc_id(self)?)
.i64(x as i64)
.i64(y as i64)
.i64(w as i64)
.i64(h as i64)
.i64(pw as i64)
.i64(ph as i64)
.bytes(),
)?;
let mut unpack = Unpack::new(&payload);
let generation = unpack.u64()?;
Ok(Tile {
generation,
pixels: unpack.rest().to_vec(),
})
}
/// Slide or master `part` whole, as `pixels` of BGRA.
pub fn paint_part(
&self,
worker: &mut Worker,
(part, master): (usize, bool),
pixels: (u32, u32),
) -> io::Result<Vec<u8>> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.u64(part as u64)
.u64(u64::from(master))
.u64(u64::from(pixels.0))
.u64(u64::from(pixels.1))
.bytes();
worker.request(tag::PAINT_PART, &request)
}
/// Slide `part`'s slideshow layers at about `pixels`.
pub fn slide_layers(
&self,
worker: &mut Worker,
part: usize,
pixels: (u32, u32),
) -> io::Result<crate::show::Layers> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.u64(part as u64)
.u64(u64::from(pixels.0))
.u64(u64::from(pixels.1))
.bytes();
crate::show::layers(&worker.request(tag::SLIDE_LAYERS, &request)?)
}
/// Saves to `url` as `format`; failure surfaces (LOK-09).
pub fn save_as(&self, worker: &mut Worker, url: &str, format: &str) -> io::Result<()> {
self.store(worker, url, format, "")
}
/// Writes the shown sheet as text split by `separator`.
pub fn export_text(&self, worker: &mut Worker, url: &str, separator: char) -> io::Result<()> {
let request = Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.u64(u64::from(separator))
.bytes();
worker.request(tag::EXPORT_TEXT, &request)?;
Ok(())
}
/// Writes a PDF of `pages` (from 1, inclusive) to `url`.
pub fn export_pdf(&self, worker: &mut Worker, url: &str, pdf: &Pdf) -> io::Result<()> {
let (from, to) = pdf.pages.unwrap_or((0, 0));
let request = Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.u64(u64::from(from))
.u64(u64::from(to))
.u64(u64::from(pdf.comments))
.u64(u64::from(pdf.changes))
.u64(u64::from(pdf.handouts))
.bytes();
worker.request(tag::EXPORT_PDF, &request)?;
Ok(())
}
/// Saves and moves the document to `url`; modified clears.
pub fn save_owned(&self, worker: &mut Worker, url: &str, format: &str) -> io::Result<()> {
self.store(worker, url, format, "TakeOwnership")
}
fn store(&self, worker: &mut Worker, url: &str, format: &str, options: &str) -> io::Result<()> {
worker.request(
tag::SAVE_AS,
&Pack::new()
.u64(worker.doc_id(self)?)
.str(url)
.str(format)
.str(options)
.bytes(),
)?;
Ok(())
}
}