pub(crate) mod translate;
use {
crate::{
io::{Dimensions, DocEdit, File, Input, Output},
orient,
},
log::trace,
lsp_types::{ShowMessageRequestParams, TextDocumentIdentifier, TextDocumentItem},
std::mem,
translate::{Command, Interpreter, Operation},
url::Url,
};
#[derive(Debug, Default)]
pub(crate) struct Processor {
pane: Pane,
input: String,
command: Option<Command>,
interpreter: Interpreter,
}
impl Processor {
pub(crate) fn new() -> Self {
Self::default()
}
pub(crate) fn process(&mut self, input: Input) -> Vec<Output> {
self.interpreter
.translate(input)
.map_or_else(Vec::new, |operation| self.operate(operation))
}
pub(crate) fn operate(&mut self, operation: Operation) -> Vec<Output> {
let mut outputs = Vec::new();
match operation {
Operation::Resize { dimensions } => {
self.pane.update_size(dimensions, &mut outputs);
}
Operation::Confirm(action) => {
outputs.push(Output::Question {
request: ShowMessageRequestParams::from(action),
});
}
Operation::Reset => {
self.input.clear();
self.pane.update(&mut outputs);
}
Operation::StartCommand(command) => {
let prompt = command.to_string();
self.command = Some(command);
outputs.push(Output::Command { command: prompt });
}
Operation::Collect(ch) => {
self.input.push(ch);
outputs.push(Output::Command {
command: self.input.clone(),
});
}
Operation::Execute => {
if self.command.is_some() {
let mut path = String::new();
mem::swap(&mut path, &mut self.input);
outputs.push(Output::OpenFile { path });
}
}
Operation::Quit => {
if let Some(output) = self.pane.close_doc() {
outputs.push(output);
}
outputs.push(Output::Quit);
}
Operation::CreateDoc(file) => {
outputs.append(&mut self.pane.create_doc(file));
}
Operation::Scroll(direction) => {
if let Some(output) = self.pane.scroll(direction) {
outputs.push(output);
}
}
};
outputs.push(Output::UpdateHeader);
trace!("outputs: {:?}", outputs);
outputs
}
}
#[derive(Debug, Default)]
struct Pane {
doc: Option<Document>,
size: Dimensions,
}
impl Pane {
fn update(&mut self, outputs: &mut Vec<Output>) {
if let Some(doc) = self.doc.as_mut() {
outputs.push(doc.change_output());
}
}
fn update_size(&mut self, dimensions: Dimensions, outputs: &mut Vec<Output>) {
self.size = dimensions;
if let Some(doc) = self.doc.as_mut() {
doc.dimensions = dimensions;
outputs.push(doc.change_output());
}
}
fn create_doc(&mut self, file: File) -> Vec<Output> {
let mut outputs = Vec::new();
let doc = Document::new(file, self.size);
let output = doc.open_output();
if let Some(old_doc) = self.doc.replace(doc) {
outputs.push(old_doc.close());
}
outputs.push(output);
outputs
}
fn close_doc(&mut self) -> Option<Output> {
self.doc.take().map(Document::close)
}
fn scroll(&mut self, direction: orient::ScreenDirection) -> Option<Output> {
self.doc.as_mut().map(|doc| {
doc.scroll(direction);
Output::UpdateView { rows: doc.rows() }
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct Document {
file: File,
row_indices: Vec<(usize, usize)>,
dimensions: Dimensions,
first_visible_row: usize,
max_visible_row: usize,
version: i64,
}
impl Document {
fn new(file: File, dimensions: Dimensions) -> Self {
let max_length = usize::from(dimensions.width);
let mut prev_index = 0;
let mut row_end_index = max_length;
let mut row_indices = Vec::new();
let text = file.text();
for (index, _) in text.match_indices('\n') {
let mut end_index = index;
let before_end_index = end_index.saturating_sub(1);
if text.get(before_end_index..end_index) == Some("\r") {
end_index = before_end_index;
}
while row_end_index < end_index {
row_indices.push((prev_index, row_end_index));
prev_index = row_end_index;
row_end_index = row_end_index.saturating_add(max_length);
}
row_indices.push((prev_index, end_index));
prev_index = index.saturating_add(1);
row_end_index = prev_index.saturating_add(max_length);
}
let text_length = text.len();
while row_end_index < text_length {
row_indices.push((prev_index, row_end_index));
prev_index = row_end_index;
row_end_index = row_end_index.saturating_add(max_length);
}
row_indices.push((prev_index, text_length));
Self {
max_visible_row: row_indices.len().saturating_sub(dimensions.height.into()),
file,
row_indices,
dimensions,
first_visible_row: 0,
version: 0,
}
}
fn scroll(&mut self, direction: orient::ScreenDirection) {
if let Some(vertical_direction) = direction.vertical_direction() {
self.first_visible_row = match vertical_direction {
orient::AxialDirection::Positive => self.first_visible_row.saturating_add(5),
orient::AxialDirection::Negative => self.first_visible_row.saturating_sub(5),
};
if self.first_visible_row > self.max_visible_row {
self.first_visible_row = self.max_visible_row;
}
}
}
fn open_output(&self) -> Output {
Output::EditDoc {
doc: self.clone(),
edit: DocEdit::Open {
version: self.version,
},
}
}
fn change_output(&mut self) -> Output {
Output::EditDoc {
doc: self.clone(),
edit: DocEdit::Update,
}
}
pub(crate) const fn url(&self) -> &Url {
self.file.url()
}
pub(crate) fn rows(&self) -> Vec<String> {
self.row_indices
.iter()
.skip(self.first_visible_row)
.take((*self.dimensions.height).into())
.map(|&(start, end)| {
self.file
.text()
.get(start..end)
.map(ToString::to_string)
.unwrap_or_default()
})
.collect()
}
const fn close(self) -> Output {
Output::EditDoc {
doc: self,
edit: DocEdit::Close,
}
}
}
impl From<Document> for TextDocumentItem {
#[inline]
fn from(value: Document) -> Self {
Self::new(
value.url().clone(),
value.file.language().to_string(),
value.version,
value.file.text().to_string(),
)
}
}
impl From<Document> for TextDocumentIdentifier {
#[inline]
fn from(value: Document) -> Self {
Self::new(value.url().clone())
}
}