use std::collections::VecDeque;
use crate::keymap::script_to_msgs_for;
use crate::msg::{Effect, Msg};
use crate::state::State;
use crate::state::View;
use crate::trace::{apply_with_views, OnView, TraceLine, ViewOpen};
use crate::update::update;
use crate::view::{view, Frame};
pub const DEFAULT_TRACE_LIMIT: usize = 100_000;
#[derive(Debug, Clone)]
pub struct Session {
state: State,
views: Vec<(u32, View)>,
next_view: u32,
rev: u64,
trace: Vec<TraceLine>,
trace_revs: Vec<u64>,
segment: usize,
dropped: usize,
limit: usize,
}
impl Session {
pub fn new(state: State) -> Session {
let trace = vec![TraceLine::State(Box::new(state.clone()))];
Session {
state,
views: Vec::new(),
next_view: 1,
rev: 0,
trace,
trace_revs: vec![0],
segment: 0,
dropped: 0,
limit: DEFAULT_TRACE_LIMIT,
}
}
pub fn set_trace_limit(&mut self, lines: usize) {
self.limit = lines.max(2);
self.trim();
}
pub fn trace_limit(&self) -> usize {
self.limit
}
pub fn state(&self) -> &State {
&self.state
}
pub fn rev(&self) -> u64 {
self.rev
}
pub fn trace(&self) -> &[TraceLine] {
&self.trace
}
pub fn segment_trace(&self) -> &[TraceLine] {
&self.trace[self.segment..]
}
pub fn segment_rev(&self) -> u64 {
self.trace_revs[self.segment]
}
pub fn trace_start_rev(&self) -> u64 {
self.trace_revs[0]
}
pub fn trace_since(&self, rev: u64) -> Option<&[TraceLine]> {
if rev < self.trace_start_rev() {
return None;
}
let from = self.trace_revs.partition_point(|&r| r <= rev);
Some(&self.trace[from..])
}
pub fn checkpoint(&mut self) -> u64 {
self.push_state_lines();
self.segment = self.trace.len() - 1 - self.views.len();
self.trim();
self.rev
}
pub fn trace_lines_total(&self) -> usize {
self.dropped + self.trace.len()
}
pub fn trace_lines_from(&self, n: usize) -> Option<&[TraceLine]> {
if n < self.dropped {
return None;
}
Some(&self.trace[(n - self.dropped).min(self.trace.len())..])
}
pub fn trace_jsonl(&self) -> String {
let mut out = String::new();
for line in &self.trace {
out.push_str(&line.to_line());
out.push('\n');
}
out
}
pub fn state_lines(&self) -> Vec<TraceLine> {
let mut lines = vec![TraceLine::State(Box::new(self.state.clone()))];
for (id, v) in &self.views {
lines.push(TraceLine::ViewOpen(ViewOpen { id: *id, view: Box::new(v.clone()) }));
}
lines
}
fn push_state_lines(&mut self) {
for line in self.state_lines() {
self.push_line(line);
}
}
fn push_line(&mut self, line: TraceLine) {
self.trace.push(line);
self.trace_revs.push(self.rev);
}
fn trim(&mut self) {
if self.trace.len() <= self.limit {
return;
}
if self.segment > 0 {
self.drop_front(self.segment);
}
if self.trace.len() > self.limit {
self.push_state_lines();
self.drop_front(self.trace.len() - 1 - self.views.len());
}
}
fn drop_front(&mut self, n: usize) {
self.trace.drain(..n);
self.trace_revs.drain(..n);
self.dropped += n;
self.segment = self.segment.saturating_sub(n);
}
pub fn apply(&mut self, msg: Msg) -> Vec<Effect> {
self.apply_on(0, msg)
}
pub fn apply_on(&mut self, id: u32, msg: Msg) -> Vec<Effect> {
if id != 0 && !self.views.iter().any(|(k, _)| *k == id) {
return Vec::new();
}
self.rev += 1;
let line = if id == 0 { TraceLine::Msg(msg.clone()) } else { TraceLine::On(OnView { view: id, msg: msg.clone() }) };
self.push_line(line);
let effects = if self.views.is_empty() { update(&mut self.state, msg) } else { apply_with_views(&mut self.state, &mut self.views, id, msg) };
self.trim();
effects
}
pub fn open_view(&mut self, mut view: View) -> u32 {
view.fit(&self.state.doc);
let id = self.next_view;
self.next_view += 1;
self.rev += 1;
self.push_line(TraceLine::ViewOpen(ViewOpen { id, view: Box::new(view.clone()) }));
self.views.push((id, view));
self.trim();
id
}
pub fn close_view(&mut self, id: u32) -> bool {
let Some(i) = self.views.iter().position(|(k, _)| *k == id) else { return false };
self.views.remove(i);
self.rev += 1;
self.push_line(TraceLine::ViewClose(id));
self.trim();
true
}
pub fn views(&self) -> &[(u32, View)] {
&self.views
}
pub fn view(&self, id: u32) -> Option<&View> {
if id == 0 {
return Some(&self.state.view);
}
self.views.iter().find(|(k, _)| *k == id).map(|(_, v)| v)
}
pub fn state_of(&self, id: u32) -> Option<State> {
self.view(id).map(|v| State::from_parts(self.state.doc.clone(), v.clone()))
}
pub fn apply_all(&mut self, msgs: impl IntoIterator<Item = Msg>) -> Vec<Effect> {
let mut effects = Vec::new();
for msg in msgs {
effects.extend(self.apply(msg));
}
effects
}
pub fn apply_with(
&mut self,
msg: Msg,
exec: &mut dyn FnMut(&Effect) -> Option<Msg>,
) -> (Vec<Effect>, Vec<Msg>) {
self.apply_with_on(0, msg, exec)
}
pub fn apply_with_on(
&mut self,
id: u32,
msg: Msg,
exec: &mut dyn FnMut(&Effect) -> Option<Msg>,
) -> (Vec<Effect>, Vec<Msg>) {
let mut queue = VecDeque::from([msg]);
let mut effects = Vec::new();
let mut applied = Vec::new();
while let Some(msg) = queue.pop_front() {
applied.push(msg.clone());
for effect in self.apply_on(id, msg) {
if let Some(next) = exec(&effect) {
queue.push_back(next);
}
effects.push(effect);
}
}
(effects, applied)
}
pub fn keys(&mut self, script: &str) -> Result<(Vec<Msg>, Vec<Effect>), String> {
self.keys_on(0, script)
}
pub fn keys_on(&mut self, id: u32, script: &str) -> Result<(Vec<Msg>, Vec<Effect>), String> {
let msgs = script_to_msgs_for(script, self.state.doc.now_ms, self.state.doc.outline.is_some())?;
let mut effects = Vec::new();
for msg in msgs.iter().cloned() {
effects.extend(self.apply_on(id, msg));
}
Ok((msgs, effects))
}
pub fn set_text_on(&mut self, id: u32, text: &str) -> Option<Msg> {
self.view(id)?;
let msg = self.text_change(text)?;
self.apply_on(id, msg.clone());
Some(msg)
}
pub fn set_text(&mut self, text: &str) -> Option<Msg> {
self.set_text_on(0, text)
}
pub fn text_change(&self, text: &str) -> Option<Msg> {
use crate::external::ExtChange;
let doc = &self.state.doc;
let text = crate::update::normalize_line_endings(text, doc.config.line_ending.as_str());
let changes = crate::diff::changes(&doc.text.to_string(), &text);
if changes.is_empty() {
return None;
}
let changes = changes.into_iter().rev().map(|(from, to, text)| ExtChange::Replace { from, to, text }).collect();
Some(Msg::External { changes })
}
pub fn set_state(&mut self, mut state: State) -> u64 {
state.sanitize();
if state.doc.host.is_empty() {
state.doc.host = self.state.doc.host.clone();
}
self.rev += 1;
self.state = state;
for (_, v) in &mut self.views {
v.fit(&self.state.doc);
}
self.push_state_lines();
self.segment = self.trace.len() - 1 - self.views.len();
self.trim();
self.rev
}
pub fn push_frame(&mut self, text: &str, highlights: &[(usize, usize)], caret: Option<usize>, status: Option<String>) -> u64 {
use crate::helix::{Range, Selection};
let old = &self.state;
let mut doc = crate::state::Document::new(text, old.doc.path.clone());
doc.config = crate::state::Config { line_ending: doc.config.line_ending, ..old.doc.config.clone() };
doc.now_ms = old.doc.now_ms;
doc.host = old.doc.host.clone();
let mut view = crate::state::View::new(old.view.viewport);
view.config = old.view.config.clone();
view.scroll = old.view.scroll;
view.focused = old.view.focused;
view.read_only = old.view.read_only;
view.frame_clock = old.view.frame_clock;
view.status = status.or_else(|| old.view.status.clone());
let mut ranges: Vec<Range> = Vec::with_capacity(highlights.len() + 1);
if let Some(c) = caret {
ranges.push(Range::point(c));
}
ranges.extend(highlights.iter().map(|&(a, b)| Range::new(a, b)));
if !ranges.is_empty() {
view.selection = Selection::new(ranges.into(), 0);
}
view.fit(&doc);
self.rev += 1;
self.state = State::from_parts(doc, view);
for (_, v) in &mut self.views {
v.fit(&self.state.doc);
}
self.push_state_lines();
self.segment = self.trace.len() - 1 - self.views.len();
self.trim();
self.rev
}
pub fn render_view(&self, id: u32, size: Option<(u16, u16)>) -> Option<Frame> {
let mut s = self.state_of(id)?;
if let Some((w, h)) = size {
if (s.view.viewport.width, s.view.viewport.height) != (w, h) {
update(&mut s, Msg::Resize { width: w, height: h });
}
}
Some(view(&s))
}
pub fn frame(&self) -> Frame {
view(&self.state)
}
pub fn render(&self, width: u16, height: u16) -> Frame {
let v = self.state.view.viewport;
if (v.width, v.height) == (width, height) {
return view(&self.state);
}
let mut sized = self.state.clone();
update(&mut sized, Msg::Resize { width, height });
view(&sized)
}
}