Phase 5: Editor und inkrementellen Compiler implementieren und archivieren
This commit is contained in:
@@ -185,6 +185,13 @@ pub enum AfterSave {
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DialogKind {
|
||||
Search {
|
||||
view: ViewId,
|
||||
selection: Option<std::ops::Range<usize>>,
|
||||
},
|
||||
Procedure(bool),
|
||||
Procedures(Vec<usize>),
|
||||
Diagnostics,
|
||||
Browse {
|
||||
return_to: Box<Dialog>,
|
||||
field: usize,
|
||||
@@ -249,6 +256,7 @@ pub enum Hit {
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub editor: crate::editor::Editor,
|
||||
pub project: Project,
|
||||
pub options: Options,
|
||||
pub mode: Mode,
|
||||
@@ -285,6 +293,7 @@ impl App {
|
||||
let (options, errors, config_disk) = Options::load(&config_path);
|
||||
project.include_paths = options.include_paths.clone();
|
||||
let mut app = Self {
|
||||
editor: Default::default(),
|
||||
project,
|
||||
options: options.clone(),
|
||||
saved_options: options,
|
||||
@@ -393,8 +402,13 @@ impl App {
|
||||
.into_owned()
|
||||
});
|
||||
format!(
|
||||
"[{}] {}{}",
|
||||
"[{}] {}{}{}",
|
||||
w.id,
|
||||
if self.editor.expansions.contains_key(&v) {
|
||||
"Included Lines [read-only] · "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
name,
|
||||
if doc.is_dirty() { " *" } else { "" }
|
||||
)
|
||||
@@ -438,12 +452,41 @@ impl App {
|
||||
return Some(format!("Fachfunktion folgt in Phase-5-Change {phase:02}"));
|
||||
}
|
||||
use Command::*;
|
||||
if matches!(command, LoadText | SaveText)
|
||||
&& !matches!(
|
||||
self.active_window().map(|w| w.kind),
|
||||
Some(WindowKind::Code(_))
|
||||
)
|
||||
if matches!(
|
||||
command,
|
||||
Undo | Cut | Paste | Clear | LoadText | NewSub | NewFunction | Replace
|
||||
) && self
|
||||
.editor_view()
|
||||
.is_ok_and(|v| self.editor.expansions.contains_key(&v))
|
||||
{
|
||||
return Some(
|
||||
"Included Lines ist schreibgeschützt; Included File öffnet die Originaldatei"
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if matches!(
|
||||
command,
|
||||
LoadText
|
||||
| SaveText
|
||||
| Cut
|
||||
| Copy
|
||||
| Paste
|
||||
| Clear
|
||||
| NewSub
|
||||
| NewFunction
|
||||
| IncludedFile
|
||||
| IncludedLines
|
||||
| Find
|
||||
| SelectedText
|
||||
| FindNext
|
||||
| Replace
|
||||
| Procedures
|
||||
| PreviousCode
|
||||
| Diagnostics
|
||||
) && !matches!(
|
||||
self.active_window().map(|w| w.kind),
|
||||
Some(WindowKind::Code(_))
|
||||
) {
|
||||
return Some("Kein Codefenster aktiv".into());
|
||||
}
|
||||
if matches!(
|
||||
@@ -489,6 +532,9 @@ impl App {
|
||||
use Command::*;
|
||||
let id = self.active_document();
|
||||
match command {
|
||||
Cut | Copy | Paste | Clear | NewSub | NewFunction | IncludedFile | IncludedLines
|
||||
| Find | SelectedText | FindNext | Replace | Procedures | PreviousCode
|
||||
| Diagnostics => self.editor_command(command)?,
|
||||
NewProject => self.change_project(AfterSave::NewProject)?,
|
||||
OpenProject => self.open_dialog(
|
||||
"Open Project",
|
||||
@@ -531,7 +577,10 @@ impl App {
|
||||
{
|
||||
self.save_dialog(false, vec![id], AfterSave::Stay)?;
|
||||
} else {
|
||||
self.message = "Datei gespeichert".into();
|
||||
self.message = format!(
|
||||
"Datei gespeichert · {}",
|
||||
self.project.save_notices.join("; ")
|
||||
);
|
||||
}
|
||||
}
|
||||
SaveAs => self.save_dialog(false, vec![id.unwrap()], AfterSave::Stay)?,
|
||||
@@ -795,7 +844,12 @@ impl App {
|
||||
.display()
|
||||
.to_string()
|
||||
}
|
||||
fn open_dialog(&mut self, title: impl Into<String>, kind: DialogKind, fields: Vec<Field>) {
|
||||
pub(crate) fn open_dialog(
|
||||
&mut self,
|
||||
title: impl Into<String>,
|
||||
kind: DialogKind,
|
||||
fields: Vec<Field>,
|
||||
) {
|
||||
self.dialog = Some(Dialog::new(title, kind, fields));
|
||||
}
|
||||
fn cascade(&self) -> Rect {
|
||||
@@ -810,7 +864,7 @@ impl App {
|
||||
self.add_window(kind, self.cascade());
|
||||
}
|
||||
}
|
||||
fn show_document(&mut self, id: DocumentId, form: bool) -> Result<()> {
|
||||
pub(crate) fn show_document(&mut self, id: DocumentId, form: bool) -> Result<()> {
|
||||
if form {
|
||||
ensure!(
|
||||
matches!(self.project.document(id)?.content(), Content::Form(_)),
|
||||
@@ -890,6 +944,7 @@ impl App {
|
||||
}
|
||||
}
|
||||
self.base = self.project.directory().to_path_buf();
|
||||
self.editor = Default::default();
|
||||
self.windows.clear();
|
||||
self.active = 0;
|
||||
self.selected_member = 0;
|
||||
@@ -961,6 +1016,10 @@ impl App {
|
||||
}
|
||||
fn submit(&mut self, d: &mut Dialog) -> Result<bool> {
|
||||
match d.kind.clone() {
|
||||
DialogKind::Search { .. }
|
||||
| DialogKind::Procedure(_)
|
||||
| DialogKind::Procedures(_)
|
||||
| DialogKind::Diagnostics => return self.editor_submit(d),
|
||||
DialogKind::Browse {
|
||||
mut return_to,
|
||||
field,
|
||||
@@ -1075,19 +1134,18 @@ impl App {
|
||||
} else {
|
||||
self.project.save_file(ids[0], plan.files.get(&ids[0]))?;
|
||||
}
|
||||
self.message = "Gespeichert".into();
|
||||
self.message = format!("Gespeichert · {}", self.project.save_notices.join("; "));
|
||||
self.finish_change(after)?;
|
||||
}
|
||||
DialogKind::LoadText => {
|
||||
let (id, at) = self.code_cursor()?;
|
||||
self.project
|
||||
.load_text(id, at, &self.base.join(d.fields[0].string()))?;
|
||||
let text = std::fs::read_to_string(self.base.join(d.fields[0].string()))?;
|
||||
self.editor_insert(&text)?;
|
||||
}
|
||||
DialogKind::SaveText => {
|
||||
let (id, _) = self.code_cursor()?;
|
||||
self.project.save_text(
|
||||
id,
|
||||
None,
|
||||
self.project.view(self.editor_view()?)?.selection(),
|
||||
&Destination {
|
||||
path: d.fields[0].string().into(),
|
||||
overwrite: d.fields[1].flag(),
|
||||
@@ -1189,6 +1247,14 @@ impl App {
|
||||
Ok((view.document(), view.cursor))
|
||||
}
|
||||
pub fn handle(&mut self, event: Event) {
|
||||
let old = self.editor_position();
|
||||
if !matches!(event, Event::Key(_) | Event::Resize(_, _)) {
|
||||
self.editor.chord = None;
|
||||
}
|
||||
self.handle_event(event);
|
||||
self.line_leave(old);
|
||||
}
|
||||
fn handle_event(&mut self, event: Event) {
|
||||
if let Event::Resize(w, h) = event {
|
||||
self.size = (w, h);
|
||||
return;
|
||||
@@ -1253,6 +1319,12 @@ impl App {
|
||||
}
|
||||
} else if self.program_focus() {
|
||||
self.basic_events.push(event);
|
||||
} else if let Event::Paste(text) = event {
|
||||
if self.dialog.is_none() && self.menu.is_none() && self.mode == Mode::Environment {
|
||||
if let Err(e) = self.editor_insert(&text) {
|
||||
self.message = e.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fn click(&mut self, hit: Hit) {
|
||||
@@ -1334,6 +1406,16 @@ impl App {
|
||||
)
|
||||
}
|
||||
fn key(&mut self, key: KeyEvent) {
|
||||
if self.editor.chord.is_some()
|
||||
&& self.dialog.is_none()
|
||||
&& self.menu.is_none()
|
||||
&& !self.program_focus()
|
||||
{
|
||||
if let Err(e) = self.editor_key(key) {
|
||||
self.message = e.to_string();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if key.code == K::F(2) && self.dialog.is_some() {
|
||||
self.browse();
|
||||
return;
|
||||
@@ -1606,48 +1688,10 @@ impl App {
|
||||
))
|
||||
}
|
||||
fn code_key(&mut self, key: KeyEvent) -> Result<()> {
|
||||
let Some(Window {
|
||||
kind: WindowKind::Code(view),
|
||||
..
|
||||
}) = self.active_window()
|
||||
else {
|
||||
if self.editor_view().is_err() {
|
||||
return Ok(());
|
||||
};
|
||||
let view = *view;
|
||||
let (id, at) = self.code_cursor()?;
|
||||
let code = self.project.document(id)?.code().to_owned();
|
||||
let mut next = at;
|
||||
match key.code {
|
||||
K::Char(c) if !key.modifiers.intersects(M::CONTROL | M::ALT) => {
|
||||
self.project.replace_text(id, at..at, &c.to_string())?;
|
||||
next += c.len_utf8();
|
||||
}
|
||||
K::Enter => {
|
||||
self.project.replace_text(id, at..at, "\n")?;
|
||||
next += 1;
|
||||
}
|
||||
K::Tab => {
|
||||
self.project.replace_text(id, at..at, "\t")?;
|
||||
next += 1;
|
||||
}
|
||||
K::Backspace if at > 0 => {
|
||||
next = code[..at].char_indices().last().unwrap().0;
|
||||
self.project.replace_text(id, next..at, "")?;
|
||||
}
|
||||
K::Left => {
|
||||
next = code[..at]
|
||||
.char_indices()
|
||||
.last()
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0)
|
||||
}
|
||||
K::Right => next += code[at..].chars().next().map(char::len_utf8).unwrap_or(0),
|
||||
K::Home => next = code[..at].rfind('\n').map(|n| n + 1).unwrap_or(0),
|
||||
K::End => next = code[at..].find('\n').map(|n| at + n).unwrap_or(code.len()),
|
||||
_ => {}
|
||||
}
|
||||
self.project.view_mut(view)?.cursor = next;
|
||||
Ok(())
|
||||
self.editor_key(key)
|
||||
}
|
||||
pub fn menu_entries(&self, index: usize) -> Vec<(String, Option<Command>)> {
|
||||
if self.control_menu {
|
||||
@@ -1732,6 +1776,8 @@ pub fn shortcut(key: KeyEvent) -> Option<Command> {
|
||||
K::F(5) if ctrl => Restore,
|
||||
K::F(1) if shift => UsingHelp,
|
||||
K::F(1) => Topic,
|
||||
K::F(2) if shift => Procedures,
|
||||
K::F(2) if ctrl => PreviousCode,
|
||||
K::F(2) => Code,
|
||||
K::F(3) => FindNext,
|
||||
K::F(4) => OutputScreen,
|
||||
@@ -1746,6 +1792,10 @@ pub fn shortcut(key: KeyEvent) -> Option<Command> {
|
||||
K::F(10) => ProcedureStep,
|
||||
K::F(12) if shift => Form,
|
||||
K::F(12) => Events,
|
||||
K::Insert if ctrl => Copy,
|
||||
K::Insert if shift => Paste,
|
||||
K::Delete if shift => Cut,
|
||||
K::Backspace if key.modifiers.contains(M::ALT) => Undo,
|
||||
K::Char('c') if ctrl => Copy,
|
||||
K::Char('x') if ctrl => Cut,
|
||||
K::Char('v') if ctrl => Paste,
|
||||
|
||||
Reference in New Issue
Block a user