mod bracket;
mod editing;
mod key_handling;
mod modes;
mod navigation;
mod render;
mod selection;
mod types;
use std::path::Path;
use crate::style::Color;
use crate::widget::syntax::{Language, SyntaxHighlighter, SyntaxTheme};
use crate::widget::traits::WidgetProps;
use crate::{impl_props_builders, impl_styled_view};
pub use types::{BracketMatch, BracketPair, EditOp, EditorConfig, IndentStyle};
pub struct CodeEditor {
pub(super) lines: Vec<String>,
pub(super) cursor: (usize, usize),
pub(super) anchor: Option<(usize, usize)>,
pub(super) scroll: (usize, usize),
pub(super) undo_stack: Vec<EditOp>,
pub(super) redo_stack: Vec<EditOp>,
pub(super) language: Language,
pub(super) highlighter: Option<SyntaxHighlighter>,
pub(super) theme: SyntaxTheme,
pub(super) config: EditorConfig,
pub(super) show_line_numbers: bool,
pub(super) read_only: bool,
pub(super) focused: bool,
pub(super) goto_line_mode: bool,
pub(super) goto_line_input: String,
pub(super) find_mode: bool,
pub(super) find_query: String,
pub(super) find_matches: Vec<(usize, usize, usize)>, pub(super) find_index: usize,
pub bg: Option<Color>,
pub fg: Option<Color>,
pub cursor_bg: Color,
pub selection_bg: Color,
pub line_number_fg: Color,
pub current_line_bg: Color,
pub bracket_match_bg: Color,
pub find_match_bg: Color,
pub current_find_bg: Color,
pub minimap_bg: Color,
pub minimap_visible_bg: Color,
pub props: WidgetProps,
}
impl CodeEditor {
pub fn new() -> Self {
Self {
lines: vec![String::new()],
cursor: (0, 0),
anchor: None,
scroll: (0, 0),
undo_stack: Vec::new(),
redo_stack: Vec::new(),
language: Language::None,
highlighter: None,
theme: SyntaxTheme::dark(),
config: EditorConfig::default(),
show_line_numbers: true,
read_only: false,
focused: true,
goto_line_mode: false,
goto_line_input: String::new(),
find_mode: false,
find_query: String::new(),
find_matches: Vec::new(),
find_index: 0,
bg: Some(Color::rgb(30, 30, 46)),
fg: Some(Color::rgb(205, 214, 244)),
cursor_bg: Color::rgb(166, 227, 161),
selection_bg: Color::rgb(69, 71, 90),
line_number_fg: Color::rgb(88, 91, 112),
current_line_bg: Color::rgb(49, 50, 68),
bracket_match_bg: Color::rgb(137, 180, 250),
find_match_bg: Color::rgb(249, 226, 175),
current_find_bg: Color::rgb(250, 179, 135),
minimap_bg: Color::rgb(24, 24, 37),
minimap_visible_bg: Color::rgb(49, 50, 68),
props: WidgetProps::new(),
}
}
pub fn content(mut self, text: impl Into<String>) -> Self {
let text = text.into();
self.lines = text.lines().map(String::from).collect();
if self.lines.is_empty() {
self.lines.push(String::new());
}
self.cursor = (0, 0);
self.scroll = (0, 0);
self
}
pub fn set_content(&mut self, text: &str) {
self.lines = text.lines().map(String::from).collect();
if self.lines.is_empty() {
self.lines.push(String::new());
}
self.cursor = (0, 0);
self.scroll = (0, 0);
self.undo_stack.clear();
self.redo_stack.clear();
}
pub fn get_content(&self) -> String {
self.lines.join("\n")
}
pub fn language(mut self, lang: Language) -> Self {
self.language = lang;
if lang != Language::None {
self.highlighter = Some(SyntaxHighlighter::with_theme(lang, self.theme.clone()));
} else {
self.highlighter = None;
}
self
}
pub fn set_language(&mut self, lang: Language) {
self.language = lang;
if lang != Language::None {
self.highlighter = Some(SyntaxHighlighter::with_theme(lang, self.theme.clone()));
} else {
self.highlighter = None;
}
}
pub fn detect_language(mut self, filename: &str) -> Self {
if let Some(ext) = Path::new(filename).extension().and_then(|e| e.to_str()) {
let lang = Language::from_extension(ext);
self = self.language(lang);
}
self
}
pub fn theme(mut self, theme: SyntaxTheme) -> Self {
self.theme = theme.clone();
if let Some(ref mut hl) = self.highlighter {
*hl = SyntaxHighlighter::with_theme(self.language, theme);
}
self
}
pub fn config(mut self, config: EditorConfig) -> Self {
self.config = config;
self
}
pub fn line_numbers(mut self, show: bool) -> Self {
self.show_line_numbers = show;
self
}
pub fn read_only(mut self, read_only: bool) -> Self {
self.read_only = read_only;
self
}
pub fn focused(mut self, focused: bool) -> Self {
self.focused = focused;
self
}
pub fn indent_size(mut self, size: usize) -> Self {
self.config.indent_size = size.max(1);
self
}
pub fn indent_style(mut self, style: IndentStyle) -> Self {
self.config.indent_style = style;
self
}
pub fn auto_indent(mut self, enable: bool) -> Self {
self.config.auto_indent = enable;
self
}
pub fn bracket_matching(mut self, enable: bool) -> Self {
self.config.bracket_matching = enable;
self
}
pub fn highlight_current_line(mut self, enable: bool) -> Self {
self.config.highlight_current_line = enable;
self
}
pub fn minimap(mut self, show: bool) -> Self {
self.config.show_minimap = show;
self
}
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
#[doc(hidden)]
pub fn get_line(&self, index: usize) -> Option<String> {
self.lines.get(index).cloned()
}
}
impl Default for CodeEditor {
fn default() -> Self {
Self::new()
}
}
impl_styled_view!(CodeEditor);
impl_props_builders!(CodeEditor);
pub fn code_editor() -> CodeEditor {
CodeEditor::new()
}