merge rc2 (gutter) and rc3 (syntax + auto-indent) into features branch
This commit is contained in:
commit
a61752dad8
|
|
@ -11,7 +11,7 @@ acord-core = { path = "../core" }
|
|||
iced_wgpu = "0.14"
|
||||
iced_graphics = "0.14"
|
||||
iced_runtime = "0.14"
|
||||
iced_widget = { version = "0.14", features = ["wgpu", "markdown"] }
|
||||
iced_widget = { version = "0.14", features = ["wgpu", "markdown", "canvas"] }
|
||||
wgpu = "27"
|
||||
raw-window-handle = "0.6"
|
||||
pollster = "0.4"
|
||||
|
|
|
|||
|
|
@ -2,13 +2,15 @@ use std::sync::Arc;
|
|||
|
||||
use iced_wgpu::core::keyboard;
|
||||
use iced_wgpu::core::keyboard::key;
|
||||
use iced_wgpu::core::text::{Highlight, Wrapping};
|
||||
use iced_wgpu::core::text::{Highlight, LineHeight, Wrapping};
|
||||
use iced_wgpu::core::{
|
||||
border, padding, Background, Border, Color, Element, Font, Length, Padding, Shadow, Theme,
|
||||
border, padding, alignment, Background, Border, Color, Element, Font, Length,
|
||||
Padding, Pixels, Point, Rectangle, Shadow, Theme,
|
||||
};
|
||||
use iced_widget::canvas;
|
||||
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, Action, Binding, KeyPress, Motion, Status, Style};
|
||||
use iced_wgpu::core::text::highlighter::Format;
|
||||
use crate::syntax::{self, SyntaxHighlighter, SyntaxSettings};
|
||||
|
||||
|
|
@ -36,6 +38,7 @@ pub struct EditorState {
|
|||
pub eval_results: Vec<(usize, String)>,
|
||||
pub eval_errors: Vec<(usize, String)>,
|
||||
pub lang: Option<String>,
|
||||
scroll_offset: f32,
|
||||
}
|
||||
|
||||
fn md_style() -> markdown::Style {
|
||||
|
|
@ -88,11 +91,17 @@ impl EditorState {
|
|||
eval_results: Vec::new(),
|
||||
eval_errors: Vec::new(),
|
||||
lang: Some("rust".into()),
|
||||
scroll_offset: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
fn line_height(&self) -> f32 {
|
||||
self.font_size * 1.3
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
self.content = text_editor::Content::with_text(text);
|
||||
self.scroll_offset = 0.0;
|
||||
self.reparse();
|
||||
}
|
||||
|
||||
|
|
@ -145,6 +154,14 @@ impl EditorState {
|
|||
Message::EditorAction(action) => {
|
||||
let is_edit = action.is_edit();
|
||||
|
||||
if let Action::Scroll { lines } = &action {
|
||||
let lh = self.line_height();
|
||||
self.scroll_offset += *lines as f32 * lh;
|
||||
self.scroll_offset = self.scroll_offset.max(0.0);
|
||||
let max = (self.content.line_count() as f32 - 1.0) * lh;
|
||||
self.scroll_offset = self.scroll_offset.min(max.max(0.0));
|
||||
}
|
||||
|
||||
let auto_indent = if let text_editor::Action::Edit(text_editor::Edit::Enter) = &action {
|
||||
let cursor = self.content.cursor();
|
||||
let line_text = self.content.line(cursor.position.line)
|
||||
|
|
@ -178,7 +195,6 @@ impl EditorState {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.content.perform(action);
|
||||
|
||||
if let Some(indent) = auto_indent {
|
||||
|
|
@ -286,12 +302,13 @@ impl EditorState {
|
|||
})
|
||||
.into()
|
||||
} else {
|
||||
let top_pad = 38.0_f32;
|
||||
let editor = iced_widget::text_editor(&self.content)
|
||||
.on_action(Message::EditorAction)
|
||||
.font(Font::MONOSPACE)
|
||||
.size(self.font_size)
|
||||
.height(Length::Fill)
|
||||
.padding(Padding { top: 38.0, right: 8.0, bottom: 8.0, left: 8.0 })
|
||||
.padding(Padding { top: top_pad, right: 8.0, bottom: 8.0, left: 8.0 })
|
||||
.wrapping(Wrapping::Word)
|
||||
.key_binding(macos_key_binding)
|
||||
.style(|_theme, _status| Style {
|
||||
|
|
@ -302,23 +319,43 @@ 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(),
|
||||
let editor_el: Element<'_, Message, Theme, iced_wgpu::Renderer> =
|
||||
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()
|
||||
};
|
||||
editor
|
||||
.highlight_with::<SyntaxHighlighter>(
|
||||
settings,
|
||||
|highlight, _theme| Format {
|
||||
color: Some(syntax::highlight_color(highlight.kind)),
|
||||
font: None,
|
||||
},
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
editor.into()
|
||||
}
|
||||
|
||||
let gutter = Gutter {
|
||||
line_count: self.content.line_count(),
|
||||
font_size: self.font_size,
|
||||
scroll_offset: self.scroll_offset,
|
||||
cursor_line: self.content.cursor().position.line,
|
||||
top_pad,
|
||||
};
|
||||
let gw = gutter.gutter_width();
|
||||
|
||||
let gutter_canvas: Element<'_, Message, Theme, iced_wgpu::Renderer> =
|
||||
canvas::Canvas::new(gutter)
|
||||
.width(Length::Fixed(gw))
|
||||
.height(Length::Fill)
|
||||
.into();
|
||||
|
||||
iced_widget::row![gutter_canvas, editor_el]
|
||||
.height(Length::Fill)
|
||||
.into()
|
||||
};
|
||||
|
||||
let mode_label = if self.preview { "Preview" } else { "Edit" };
|
||||
|
|
@ -394,6 +431,84 @@ impl EditorState {
|
|||
}
|
||||
}
|
||||
|
||||
struct Gutter {
|
||||
line_count: usize,
|
||||
font_size: f32,
|
||||
scroll_offset: f32,
|
||||
cursor_line: usize,
|
||||
top_pad: f32,
|
||||
}
|
||||
|
||||
impl Gutter {
|
||||
fn gutter_width(&self) -> f32 {
|
||||
let digits = if self.line_count == 0 {
|
||||
1
|
||||
} else {
|
||||
(self.line_count as f32).log10().floor() as usize + 1
|
||||
};
|
||||
let char_width = self.font_size * 0.6;
|
||||
(digits.max(2) as f32 * char_width + 16.0).ceil()
|
||||
}
|
||||
}
|
||||
|
||||
impl canvas::Program<Message, Theme, iced_wgpu::Renderer> for Gutter {
|
||||
type State = ();
|
||||
|
||||
fn draw(
|
||||
&self,
|
||||
_state: &(),
|
||||
renderer: &iced_wgpu::Renderer,
|
||||
_theme: &Theme,
|
||||
bounds: Rectangle,
|
||||
_cursor: iced_wgpu::core::mouse::Cursor,
|
||||
) -> Vec<canvas::Geometry<iced_wgpu::Renderer>> {
|
||||
let mut frame = canvas::Frame::new(renderer, bounds.size());
|
||||
let lh = self.font_size * 1.3;
|
||||
|
||||
frame.fill_rectangle(
|
||||
Point::ORIGIN,
|
||||
bounds.size(),
|
||||
Color::from_rgb(0.06, 0.06, 0.08),
|
||||
);
|
||||
|
||||
let first_visible = (self.scroll_offset / lh).floor() as usize;
|
||||
let sub_pixel = self.scroll_offset - first_visible as f32 * lh;
|
||||
let visible_count = (bounds.height / lh).ceil() as usize + 1;
|
||||
|
||||
let gw = self.gutter_width();
|
||||
|
||||
for i in 0..visible_count {
|
||||
let line_idx = first_visible + i;
|
||||
if line_idx >= self.line_count {
|
||||
break;
|
||||
}
|
||||
let y = self.top_pad + i as f32 * lh - sub_pixel;
|
||||
if y + lh < 0.0 || y > bounds.height {
|
||||
continue;
|
||||
}
|
||||
let color = if line_idx == self.cursor_line {
|
||||
Color::from_rgb(0.55, 0.55, 0.62)
|
||||
} else {
|
||||
Color::from_rgb(0.35, 0.35, 0.42)
|
||||
};
|
||||
frame.fill_text(canvas::Text {
|
||||
content: format!("{}", line_idx + 1),
|
||||
position: Point::new(gw - 8.0, y),
|
||||
max_width: gw,
|
||||
color,
|
||||
size: Pixels(self.font_size),
|
||||
line_height: LineHeight::Relative(1.3),
|
||||
font: Font::MONOSPACE,
|
||||
align_x: iced_wgpu::core::text::Alignment::Right,
|
||||
align_y: alignment::Vertical::Top,
|
||||
shaping: iced_wgpu::core::text::Shaping::Basic,
|
||||
});
|
||||
}
|
||||
|
||||
vec![frame.into_geometry()]
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_let_binding(line: &str) -> Option<String> {
|
||||
let rest = line.strip_prefix("let ")?;
|
||||
let eq_pos = rest.find('=')?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue