use crate::buffer::{GapBuffer, TextBuffer};
use crate::syntax_highlighter::SyntaxHighlighter;
use gpui::*;
#[derive(Clone)]
pub struct EditorConfig {
pub line_height: Pixels,
pub font_size: Pixels,
pub gutter_width: Pixels,
pub gutter_padding: Pixels,
pub text_color: Rgba,
pub line_number_color: Rgba,
pub gutter_bg_color: Rgba,
pub editor_bg_color: Rgba,
pub active_line_bg_color: Rgba,
pub font_family: SharedString,
}
impl Default for EditorConfig {
fn default() -> Self {
Self {
line_height: px(20.0),
font_size: px(14.0),
gutter_width: px(50.0),
gutter_padding: px(10.0),
text_color: rgb(0xcccccc),
line_number_color: rgb(0x666666),
gutter_bg_color: rgb(0x252525),
editor_bg_color: rgb(0x1e1e1e),
active_line_bg_color: rgb(0x2a2a2a),
font_family: "Monaco".into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CursorPosition {
pub row: usize,
pub col: usize,
}
impl CursorPosition {
pub fn new(row: usize, col: usize) -> Self {
Self { row, col }
}
}
#[derive(Clone)]
pub struct Editor {
id: ElementId,
buffer: GapBuffer,
config: EditorConfig,
cursor_position: CursorPosition,
goal_column: Option<usize>,
selection_anchor: Option<CursorPosition>,
syntax_highlighter: SyntaxHighlighter,
language: String,
current_theme: String,
}
impl Editor {
pub fn new(id: impl Into<ElementId>, lines: Vec<String>) -> Self {
let id = id.into();
let syntax_highlighter = SyntaxHighlighter::new();
let full_text = lines.join("\n");
let language = syntax_highlighter
.detect_language(&full_text, Some("rs"))
.unwrap_or_else(|| "Rust".to_string());
Self {
id,
buffer: GapBuffer::from_lines(lines),
config: EditorConfig::default(),
cursor_position: CursorPosition { row: 0, col: 0 },
goal_column: None,
selection_anchor: None,
syntax_highlighter,
language,
current_theme: String::new(),
}
}
pub fn id(&self) -> &ElementId {
&self.id
}
pub fn config(&self) -> &EditorConfig {
&self.config
}
pub fn config_mut(&mut self) -> &mut EditorConfig {
&mut self.config
}
pub fn set_config(&mut self, config: EditorConfig) {
self.config = config;
}
pub fn cursor_position(&self) -> CursorPosition {
self.cursor_position
}
pub fn set_cursor_position(&mut self, position: CursorPosition) {
self.cursor_position = position;
self.goal_column = None;
}
pub fn get_cursor_position(&self) -> CursorPosition {
self.cursor_position
}
pub fn clear_selection(&mut self) {
self.selection_anchor = None;
self.goal_column = None;
}
pub fn get_buffer(&self) -> &GapBuffer {
&self.buffer
}
pub fn get_buffer_mut(&mut self) -> &mut GapBuffer {
&mut self.buffer
}
pub fn language(&self) -> &str {
&self.language
}
pub fn set_language(&mut self, language: String) {
self.language = language;
}
pub fn current_theme(&self) -> &str {
&self.current_theme
}
pub fn set_theme(&mut self, theme: &str) {
self.current_theme = theme.to_string();
self.syntax_highlighter.set_theme(theme);
self.config.editor_bg_color = self.syntax_highlighter.get_theme_background().into();
self.config.text_color = self.syntax_highlighter.get_theme_foreground().into();
self.config.gutter_bg_color = self.syntax_highlighter.get_theme_gutter_background().into();
self.config.active_line_bg_color =
self.syntax_highlighter.get_theme_line_highlight().into();
}
pub fn update_buffer(&mut self, lines: Vec<String>) {
self.buffer = GapBuffer::from_lines(lines);
self.syntax_highlighter.reset_state();
}
pub fn update_line(&mut self, line_index: usize, new_content: String) {
if line_index < self.buffer.line_count() {
let line_len = self.buffer.line_len(line_index);
let start_pos = self.buffer.cursor_to_position(line_index, 0);
let end_pos = self.buffer.cursor_to_position(line_index, line_len);
self.buffer.delete_range(start_pos, end_pos);
self.buffer.insert_at(line_index, 0, &new_content);
self.syntax_highlighter
.clear_state_from_line(line_index, &self.language);
}
}
pub fn highlight_line(
&mut self,
line: &str,
line_index: usize,
font_family: SharedString,
font_size: f32,
) -> Vec<TextRun> {
self.syntax_highlighter.highlight_line(
line,
&self.language,
line_index,
font_family,
font_size,
)
}
pub fn move_left(&mut self, shift_held: bool) {
if shift_held && self.selection_anchor.is_none() {
self.selection_anchor = Some(self.cursor_position);
} else if !shift_held {
self.selection_anchor = None;
}
self.goal_column = None;
if self.cursor_position.col > 0 {
self.cursor_position.col -= 1;
} else if self.cursor_position.row > 0 {
self.cursor_position.row -= 1;
self.cursor_position.col = self.buffer.line_len(self.cursor_position.row);
}
}
pub fn move_right(&mut self, shift_held: bool) {
if shift_held && self.selection_anchor.is_none() {
self.selection_anchor = Some(self.cursor_position);
} else if !shift_held {
self.selection_anchor = None;
}
self.goal_column = None;
let current_line_len = self.buffer.line_len(self.cursor_position.row);
if self.cursor_position.col < current_line_len {
self.cursor_position.col += 1;
} else if self.cursor_position.row < self.buffer.line_count().saturating_sub(1) {
self.cursor_position.row += 1;
self.cursor_position.col = 0;
}
}
pub fn move_up(&mut self, shift_held: bool) {
if shift_held && self.selection_anchor.is_none() {
self.selection_anchor = Some(self.cursor_position);
} else if !shift_held {
self.selection_anchor = None;
}
if self.cursor_position.row > 0 {
if self.goal_column.is_none() {
self.goal_column = Some(self.cursor_position.col);
}
self.cursor_position.row -= 1;
let line_len = self.buffer.line_len(self.cursor_position.row);
self.cursor_position.col = self
.goal_column
.unwrap_or(self.cursor_position.col)
.min(line_len);
}
}
pub fn move_down(&mut self, shift_held: bool) {
if shift_held && self.selection_anchor.is_none() {
self.selection_anchor = Some(self.cursor_position);
} else if !shift_held {
self.selection_anchor = None;
}
if self.cursor_position.row < self.buffer.line_count().saturating_sub(1) {
if self.goal_column.is_none() {
self.goal_column = Some(self.cursor_position.col);
}
self.cursor_position.row += 1;
let line_len = self.buffer.line_len(self.cursor_position.row);
self.cursor_position.col = self
.goal_column
.unwrap_or(self.cursor_position.col)
.min(line_len);
}
}
pub fn select_all(&mut self) {
self.goal_column = None;
self.selection_anchor = Some(CursorPosition { row: 0, col: 0 });
let last_row = self.buffer.line_count().saturating_sub(1);
let last_col = self.buffer.line_len(last_row);
self.cursor_position = CursorPosition {
row: last_row,
col: last_col,
};
}
pub fn has_selection(&self) -> bool {
self.selection_anchor.is_some()
}
pub fn get_selection_range(&self) -> Option<(CursorPosition, CursorPosition)> {
self.selection_anchor.map(|anchor| {
if anchor.row < self.cursor_position.row
|| (anchor.row == self.cursor_position.row && anchor.col < self.cursor_position.col)
{
(anchor, self.cursor_position)
} else {
(self.cursor_position, anchor)
}
})
}
pub fn delete_selection(&mut self) -> bool {
if let Some((start, end)) = self.get_selection_range() {
let start_pos = self.buffer.cursor_to_position(start.row, start.col);
let end_pos = self.buffer.cursor_to_position(end.row, end.col);
self.buffer.delete_range(start_pos, end_pos);
self.cursor_position = start;
self.selection_anchor = None;
self.goal_column = None;
self.syntax_highlighter
.clear_state_from_line(start.row, &self.language);
true
} else {
false
}
}
pub fn get_selected_text(&self) -> String {
if let Some((start, end)) = self.get_selection_range() {
let start_pos = self.buffer.cursor_to_position(start.row, start.col);
let end_pos = self.buffer.cursor_to_position(end.row, end.col);
let text = self.buffer.to_string();
let chars: Vec<char> = text.chars().collect();
if start_pos <= chars.len() && end_pos <= chars.len() && start_pos <= end_pos {
chars[start_pos..end_pos].iter().collect()
} else {
String::new()
}
} else {
String::new()
}
}
pub fn insert_char(&mut self, ch: char) {
self.delete_selection();
self.buffer.insert_at(
self.cursor_position.row,
self.cursor_position.col,
&ch.to_string(),
);
self.cursor_position.col += 1;
self.goal_column = None;
self.syntax_highlighter
.clear_state_from_line(self.cursor_position.row, &self.language);
}
pub fn insert_newline(&mut self) {
self.delete_selection();
self.buffer
.insert_at(self.cursor_position.row, self.cursor_position.col, "\n");
self.cursor_position.row += 1;
self.cursor_position.col = 0;
self.goal_column = None;
self.syntax_highlighter
.clear_state_from_line(self.cursor_position.row - 1, &self.language);
}
pub fn backspace(&mut self) {
if self.selection_anchor.is_some() {
self.delete_selection();
return;
}
self.buffer
.backspace_at(self.cursor_position.row, self.cursor_position.col);
if self.cursor_position.col > 0 {
self.cursor_position.col -= 1;
self.syntax_highlighter
.clear_state_from_line(self.cursor_position.row, &self.language);
} else if self.cursor_position.row > 0 {
self.cursor_position.row -= 1;
let line_len = self.buffer.line_len(self.cursor_position.row);
self.cursor_position.col = line_len;
self.syntax_highlighter
.clear_state_from_line(self.cursor_position.row, &self.language);
}
self.goal_column = None;
}
pub fn delete(&mut self) {
if self.selection_anchor.is_some() {
self.delete_selection();
return;
}
self.buffer
.delete_at(self.cursor_position.row, self.cursor_position.col);
self.syntax_highlighter
.clear_state_from_line(self.cursor_position.row, &self.language);
self.goal_column = None;
}
}