Phase 5: Editor und inkrementellen Compiler implementieren und archivieren

This commit is contained in:
2026-09-06 17:58:26 +02:00
parent df85a4b7b2
commit 9c85349a4d
36 changed files with 2993 additions and 247 deletions

View File

@@ -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,