inline eval results below /= lines, remove status bar panel
This commit is contained in:
parent
1d3c03e23f
commit
f4b2173534
|
|
@ -16,6 +16,7 @@ wgpu = "27"
|
|||
raw-window-handle = "0.6"
|
||||
pollster = "0.4"
|
||||
smol_str = "0.2"
|
||||
serde_json = "1"
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.27"
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define EVAL_RESULT_KIND 24
|
||||
|
||||
#define EVAL_ERROR_KIND 25
|
||||
|
||||
typedef struct ViewportHandle ViewportHandle;
|
||||
|
||||
struct ViewportHandle *viewport_create(void *nsview, float width, float height, float scale);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use iced_wgpu::core::{
|
|||
};
|
||||
use iced_widget::container;
|
||||
use iced_widget::markdown;
|
||||
use iced_widget::text_editor::{self, Binding, KeyPress, Motion, Status, Style};
|
||||
use iced_widget::text_editor::{self, Binding, Cursor, KeyPress, Motion, Position, Status, Style};
|
||||
use iced_wgpu::core::text::highlighter::Format;
|
||||
use crate::syntax::{self, SyntaxHighlighter, SyntaxSettings};
|
||||
|
||||
|
|
@ -28,13 +28,14 @@ pub enum Message {
|
|||
ZoomReset,
|
||||
}
|
||||
|
||||
pub const RESULT_PREFIX: &str = "→ ";
|
||||
pub const ERROR_PREFIX: &str = "⚠ ";
|
||||
|
||||
pub struct EditorState {
|
||||
pub content: text_editor::Content<iced_wgpu::Renderer>,
|
||||
pub font_size: f32,
|
||||
pub preview: bool,
|
||||
pub parsed: Vec<markdown::Item>,
|
||||
pub eval_results: Vec<(usize, String)>,
|
||||
pub eval_errors: Vec<(usize, String)>,
|
||||
pub lang: Option<String>,
|
||||
}
|
||||
|
||||
|
|
@ -85,8 +86,6 @@ impl EditorState {
|
|||
font_size: 14.0,
|
||||
preview: false,
|
||||
parsed: Vec::new(),
|
||||
eval_results: Vec::new(),
|
||||
eval_errors: Vec::new(),
|
||||
lang: Some("rust".into()),
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +100,7 @@ impl EditorState {
|
|||
}
|
||||
|
||||
fn reparse(&mut self) {
|
||||
let text = self.content.text();
|
||||
let text = self.get_clean_text();
|
||||
self.parsed = markdown::parse(&text).collect();
|
||||
}
|
||||
|
||||
|
|
@ -133,11 +132,49 @@ impl EditorState {
|
|||
self.reparse();
|
||||
}
|
||||
|
||||
pub fn get_clean_text(&self) -> String {
|
||||
strip_result_lines(&self.content.text())
|
||||
}
|
||||
|
||||
fn run_eval(&mut self) {
|
||||
let text = self.content.text();
|
||||
let doc = crate::eval::evaluate_document(&text);
|
||||
self.eval_results = doc.results.into_iter().map(|r| (r.line, r.result)).collect();
|
||||
self.eval_errors = doc.errors.into_iter().map(|e| (e.line, e.error)).collect();
|
||||
let old_cursor = self.content.cursor();
|
||||
let old_text = self.content.text();
|
||||
let clean_line = to_clean_line(&old_text, old_cursor.position.line);
|
||||
let clean_col = old_cursor.position.column;
|
||||
|
||||
let clean = strip_result_lines(&old_text);
|
||||
let doc = crate::eval::evaluate_document(&clean);
|
||||
|
||||
let mut insertions: Vec<(usize, Vec<String>)> = Vec::new();
|
||||
for r in &doc.results {
|
||||
let lines = format_result_lines(&r.result, &r.format);
|
||||
if !lines.is_empty() {
|
||||
insertions.push((r.line, lines));
|
||||
}
|
||||
}
|
||||
for e in &doc.errors {
|
||||
insertions.push((e.line, vec![format!("{}{}", ERROR_PREFIX, e.error)]));
|
||||
}
|
||||
insertions.sort_by_key(|(line, _)| *line);
|
||||
|
||||
let mut out_lines: Vec<String> = clean.lines().map(|l| l.to_string()).collect();
|
||||
let mut offset = 0usize;
|
||||
for (line, inject) in &insertions {
|
||||
let insert_at = (*line + 1 + offset).min(out_lines.len());
|
||||
for (i, injected) in inject.iter().enumerate() {
|
||||
out_lines.insert(insert_at + i, injected.clone());
|
||||
}
|
||||
offset += inject.len();
|
||||
}
|
||||
|
||||
let new_text = out_lines.join("\n");
|
||||
let new_line = from_clean_line(&new_text, clean_line);
|
||||
|
||||
self.content = text_editor::Content::with_text(&new_text);
|
||||
self.content.move_to(Cursor {
|
||||
position: Position { line: new_line, column: clean_col },
|
||||
selection: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update(&mut self, message: Message) {
|
||||
|
|
@ -244,28 +281,25 @@ impl EditorState {
|
|||
selection: Color::from_rgba(0.3, 0.5, 0.8, 0.4),
|
||||
});
|
||||
|
||||
if let Some(lang) = &self.lang {
|
||||
let settings = SyntaxSettings {
|
||||
lang: lang.clone(),
|
||||
source: self.content.text(),
|
||||
};
|
||||
editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
settings,
|
||||
|highlight, _theme| Format {
|
||||
color: Some(syntax::highlight_color(highlight.kind)),
|
||||
font: None,
|
||||
},
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
editor.into()
|
||||
}
|
||||
let settings = SyntaxSettings {
|
||||
lang: self.lang.clone().unwrap_or_default(),
|
||||
source: self.content.text(),
|
||||
};
|
||||
editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
settings,
|
||||
|highlight, _theme| Format {
|
||||
color: Some(syntax::highlight_color(highlight.kind)),
|
||||
font: None,
|
||||
},
|
||||
)
|
||||
.into()
|
||||
};
|
||||
|
||||
let mode_label = if self.preview { "Preview" } else { "Edit" };
|
||||
let cursor = self.content.cursor();
|
||||
let line = cursor.position.line + 1;
|
||||
let text = self.content.text();
|
||||
let line = to_clean_line(&text, cursor.position.line) + 1;
|
||||
let col = cursor.position.column + 1;
|
||||
|
||||
let status_bar = iced_widget::container(
|
||||
|
|
@ -290,44 +324,6 @@ impl EditorState {
|
|||
let mut col_items: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> =
|
||||
vec![main_content];
|
||||
|
||||
if !self.eval_results.is_empty() || !self.eval_errors.is_empty() {
|
||||
let mut result_items: Vec<Element<'_, Message, Theme, iced_wgpu::Renderer>> = Vec::new();
|
||||
|
||||
for (ln, val) in &self.eval_results {
|
||||
result_items.push(
|
||||
iced_widget::text(format!("Ln {}: {}", ln + 1, val))
|
||||
.font(Font::MONOSPACE)
|
||||
.size(11.0)
|
||||
.color(Color::from_rgb(0.651, 0.890, 0.631))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
for (ln, err) in &self.eval_errors {
|
||||
result_items.push(
|
||||
iced_widget::text(format!("Ln {}: {}", ln + 1, err))
|
||||
.font(Font::MONOSPACE)
|
||||
.size(11.0)
|
||||
.color(Color::from_rgb(0.890, 0.400, 0.400))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
let eval_panel = iced_widget::container(
|
||||
iced_widget::column(result_items).spacing(2.0),
|
||||
)
|
||||
.width(Length::Fill)
|
||||
.padding(Padding { top: 4.0, right: 10.0, bottom: 4.0, left: 10.0 })
|
||||
.style(|_theme: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color::from_rgb(0.10, 0.10, 0.12))),
|
||||
border: Border::default(),
|
||||
text_color: None,
|
||||
shadow: Shadow::default(),
|
||||
snap: false,
|
||||
});
|
||||
|
||||
col_items.push(eval_panel.into());
|
||||
}
|
||||
|
||||
col_items.push(status_bar.into());
|
||||
|
||||
iced_widget::column(col_items)
|
||||
|
|
@ -336,6 +332,121 @@ impl EditorState {
|
|||
}
|
||||
}
|
||||
|
||||
fn to_clean_line(text: &str, display_line: usize) -> usize {
|
||||
let mut clean = 0;
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
if i == display_line {
|
||||
return clean;
|
||||
}
|
||||
if !is_result_line(line) {
|
||||
clean += 1;
|
||||
}
|
||||
}
|
||||
clean
|
||||
}
|
||||
|
||||
fn from_clean_line(text: &str, clean_target: usize) -> usize {
|
||||
let mut clean = 0;
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
if !is_result_line(line) {
|
||||
if clean == clean_target {
|
||||
return i;
|
||||
}
|
||||
clean += 1;
|
||||
}
|
||||
}
|
||||
text.lines().count().saturating_sub(1)
|
||||
}
|
||||
|
||||
pub fn is_result_line(line: &str) -> bool {
|
||||
let trimmed = line.trim_start();
|
||||
trimmed.starts_with(RESULT_PREFIX) || trimmed.starts_with(ERROR_PREFIX)
|
||||
}
|
||||
|
||||
fn strip_result_lines(text: &str) -> String {
|
||||
let lines: Vec<&str> = text.lines().filter(|l| !is_result_line(l)).collect();
|
||||
let mut result = lines.join("\n");
|
||||
if text.ends_with('\n') {
|
||||
result.push('\n');
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn format_result_lines(result: &str, format: &str) -> Vec<String> {
|
||||
match format {
|
||||
"table" => format_table(result),
|
||||
"tree" => format_tree(result),
|
||||
_ => vec![format!("{}{}", RESULT_PREFIX, result)],
|
||||
}
|
||||
}
|
||||
|
||||
fn format_table(json: &str) -> Vec<String> {
|
||||
let rows: Vec<Vec<String>> = match serde_json::from_str(json) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return vec![format!("{}{}", RESULT_PREFIX, json)],
|
||||
};
|
||||
if rows.is_empty() {
|
||||
return vec![format!("{}(empty table)", RESULT_PREFIX)];
|
||||
}
|
||||
|
||||
let col_count = rows.iter().map(|r| r.len()).max().unwrap_or(0);
|
||||
let mut widths = vec![0usize; col_count];
|
||||
for row in &rows {
|
||||
for (i, cell) in row.iter().enumerate() {
|
||||
if i < col_count {
|
||||
widths[i] = widths[i].max(cell.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut lines = Vec::new();
|
||||
for (ri, row) in rows.iter().enumerate() {
|
||||
let cells: Vec<String> = (0..col_count)
|
||||
.map(|i| {
|
||||
let val = row.get(i).map(|s| s.as_str()).unwrap_or("");
|
||||
format!("{:width$}", val, width = widths[i])
|
||||
})
|
||||
.collect();
|
||||
lines.push(format!("{}│ {} │", RESULT_PREFIX, cells.join(" │ ")));
|
||||
if ri == 0 && rows.len() > 1 {
|
||||
let sep: Vec<String> = widths.iter().map(|w| "─".repeat(*w)).collect();
|
||||
lines.push(format!("{}├─{}─┤", RESULT_PREFIX, sep.join("─┼─")));
|
||||
}
|
||||
}
|
||||
lines
|
||||
}
|
||||
|
||||
fn format_tree(json: &str) -> Vec<String> {
|
||||
let val: serde_json::Value = match serde_json::from_str(json) {
|
||||
Ok(v) => v,
|
||||
Err(_) => return vec![format!("{}{}", RESULT_PREFIX, json)],
|
||||
};
|
||||
let mut lines = Vec::new();
|
||||
render_tree_node(&val, &mut lines, 0);
|
||||
lines
|
||||
}
|
||||
|
||||
fn render_tree_node(val: &serde_json::Value, lines: &mut Vec<String>, depth: usize) {
|
||||
let indent = " ".repeat(depth);
|
||||
match val {
|
||||
serde_json::Value::Array(items) => {
|
||||
for item in items {
|
||||
render_tree_node(item, lines, depth + 1);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
let display = match other {
|
||||
serde_json::Value::String(s) => s.clone(),
|
||||
serde_json::Value::Number(n) => n.to_string(),
|
||||
serde_json::Value::Bool(b) => b.to_string(),
|
||||
serde_json::Value::Null => "null".to_string(),
|
||||
_ => other.to_string(),
|
||||
};
|
||||
lines.push(format!("{}{}{}", RESULT_PREFIX, indent, display));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_let_binding(line: &str) -> Option<String> {
|
||||
let rest = line.strip_prefix("let ")?;
|
||||
let eq_pos = rest.find('=')?;
|
||||
|
|
|
|||
|
|
@ -163,7 +163,7 @@ pub extern "C" fn viewport_get_text(handle: *mut ViewportHandle) -> *mut c_char
|
|||
Some(h) => h,
|
||||
None => return std::ptr::null_mut(),
|
||||
};
|
||||
let text = h.state.content.text();
|
||||
let text = h.state.get_clean_text();
|
||||
CString::new(text).unwrap_or_default().into_raw()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,10 @@ use std::ops::Range;
|
|||
use iced_wgpu::core::text::highlighter;
|
||||
use iced_wgpu::core::Color;
|
||||
use acord_core::highlight::{highlight_source, HighlightSpan};
|
||||
use crate::editor::{RESULT_PREFIX, ERROR_PREFIX};
|
||||
|
||||
pub const EVAL_RESULT_KIND: u8 = 24;
|
||||
pub const EVAL_ERROR_KIND: u8 = 25;
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct SyntaxSettings {
|
||||
|
|
@ -64,6 +68,14 @@ impl highlighter::Highlighter for SyntaxHighlighter {
|
|||
let ln = self.current_line;
|
||||
self.current_line += 1;
|
||||
|
||||
let trimmed = _line.trim_start();
|
||||
if trimmed.starts_with(RESULT_PREFIX) {
|
||||
return vec![(0.._line.len(), SyntaxHighlight { kind: EVAL_RESULT_KIND })].into_iter();
|
||||
}
|
||||
if trimmed.starts_with(ERROR_PREFIX) {
|
||||
return vec![(0.._line.len(), SyntaxHighlight { kind: EVAL_ERROR_KIND })].into_iter();
|
||||
}
|
||||
|
||||
if ln >= self.line_offsets.len() {
|
||||
return Vec::new().into_iter();
|
||||
}
|
||||
|
|
@ -120,6 +132,8 @@ pub fn highlight_color(kind: u8) -> Color {
|
|||
21 => Color::from_rgb(0.569, 0.878, 0.800), // label - teal
|
||||
22 => Color::from_rgb(0.949, 0.604, 0.584), // escape - red
|
||||
23 => Color::from_rgb(0.804, 0.839, 0.957), // embedded - text
|
||||
24 => Color::from_rgb(0.651, 0.890, 0.631), // eval result - green
|
||||
25 => Color::from_rgb(0.890, 0.400, 0.400), // eval error - muted red
|
||||
_ => Color::from_rgb(0.804, 0.839, 0.957), // default text
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue