Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .forth-lsp.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
# Copy this file to .forth-lsp.toml in your workspace root to customize settings

[format]
# Enable formatting (default: true)
enabled = true

# Number of spaces for indentation (default: 2)
indent_width = 2

Expand Down
5 changes: 5 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub struct Config {
/// Formatter configuration
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct FormatConfig {
/// Control if the formatter should run or not
#[serde(default = "default_true")]
pub enabled: bool,

/// Number of spaces for indentation
#[serde(default = "default_indent_width")]
pub indent_width: usize,
Expand Down Expand Up @@ -72,6 +76,7 @@ pub struct FormatConfig {
impl Default for FormatConfig {
fn default() -> Self {
Self {
enabled: default_true(),
indent_width: default_indent_width(),
use_spaces: default_use_spaces(),
space_after_colon: default_true(),
Expand Down
119 changes: 83 additions & 36 deletions src/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,39 @@ use forth_lexer::{parser::Lexer, token::Token};
use lsp_types::{Position, Range, TextEdit};
use ropey::Rope;

/// Formatter trait for formatting Forth source code
pub trait Formatter {
/// Format the entire document and return a TextEdit to replace all content
fn format_document(&self, rope: &Rope) -> Result<Vec<TextEdit>>;

/// Format the source code string and return formatted string
fn format_source(&self, source: &str) -> Result<String>;
}

/// Creates a formatter based on if FormatConfig.enabled is true
pub fn create_formatter(config: FormatConfig) -> Box<dyn Formatter> {
if config.enabled {
eprintln!("[DEBUG] Using DefaultFormatter");
Box::new(DefaultFormatter::new(config))
} else {
eprintln!("[DEBUG] Using NullFormatter");
Box::new(NullFormatter::new())
}
}

/// Formats Forth source code according to the provided configuration
pub struct Formatter {
pub struct DefaultFormatter {
config: FormatConfig,
}

impl Formatter {
impl DefaultFormatter {
pub fn new(config: FormatConfig) -> Self {
Self { config }
}
}

/// Format the entire document and return a TextEdit to replace all content
pub fn format_document(&self, rope: &Rope) -> Result<Vec<TextEdit>> {
impl Formatter for DefaultFormatter {
fn format_document(&self, rope: &Rope) -> Result<Vec<TextEdit>> {
let source = rope.to_string();
let formatted = self.format_source(&source)?;

Expand All @@ -29,8 +50,7 @@ impl Formatter {
}])
}

/// Format the source code string and return formatted string
pub fn format_source(&self, source: &str) -> Result<String> {
fn format_source(&self, source: &str) -> Result<String> {
let mut lexer = Lexer::new(source);
let tokens = lexer.parse();

Expand All @@ -42,7 +62,9 @@ impl Formatter {
};
Ok(formatted)
}
}

impl DefaultFormatter {
/// Format a colon definition while preserving its internal newlines
fn format_preserved_definition(
&self,
Expand Down Expand Up @@ -459,7 +481,7 @@ mod tests {
indent_control_structures: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -472,7 +494,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -486,7 +508,7 @@ mod tests {
indent_control_structures: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -500,7 +522,7 @@ mod tests {
indent_control_structures: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 1 2 + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -514,7 +536,7 @@ mod tests {
indent_control_structures: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 1 2 + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -527,7 +549,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": abs dup 0 < if negate then ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -538,7 +560,7 @@ mod tests {
#[test]
fn test_multiple_definitions() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ; : cube dup square * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -552,7 +574,7 @@ mod tests {
#[test]
fn test_comments_preserved() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = r"\ This is a comment
: test ( a b -- c ) + ;";
Expand All @@ -568,7 +590,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -582,7 +604,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -595,7 +617,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 0 10 do i 2 mod 0 = if i . then loop ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -607,7 +629,7 @@ mod tests {
#[test]
fn test_format_document_returns_text_edit() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ;";
let rope = Rope::from_str(source);
Expand All @@ -620,7 +642,7 @@ mod tests {
#[test]
fn test_stack_comment_on_declaration_line_default() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add ( a b -- c ) + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -634,7 +656,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add ( a b -- c ) + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -648,7 +670,7 @@ mod tests {
indent_control_structures: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add ( a b -- c ) + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -663,7 +685,7 @@ mod tests {
indent_control_structures: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = r": test \ inline comment
dup ;";
Expand All @@ -675,7 +697,7 @@ dup ;";
#[test]
fn test_blank_line_between_definitions_default() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ; : cube dup square * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -692,7 +714,7 @@ dup ;";
blank_line_between_definitions: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": square dup * ; : cube dup square * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -703,7 +725,7 @@ dup ;";
#[test]
fn test_blank_line_three_definitions() {
let config = FormatConfig::default();
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": a 1 ; : b 2 ; : c 3 ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -716,7 +738,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test\n 1 2 +\n 3 4 *\n + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -730,7 +752,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": a\n 1\n 2 + ;\n: b\n dup * ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -744,7 +766,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test\n \\ comment\n 1 2 + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -757,7 +779,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

// Test that comments, constants, variables outside definitions are preserved
let source = "\\ File header comment\n10 CONSTANT MAX\n: double dup * ;\n\\ Footer comment";
Expand All @@ -776,7 +798,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = "VARIABLE counter\n100 CONSTANT LIMIT\n: increment counter @ 1 + counter ! ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -792,7 +814,7 @@ dup ;";
preserve_definition_newlines: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

// Multiple constants on one line should be split
let source = "10 CONSTANT MAX 42 CONSTANT ANSWER";
Expand All @@ -810,7 +832,7 @@ dup ;";
newline_before_paren_comments: false, // default
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add ( regular comment ) + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -825,7 +847,7 @@ dup ;";
newline_before_line_comments: false, // default
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 1 2 \\ inline comment\n + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -840,7 +862,7 @@ dup ;";
newline_before_paren_comments: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": add ( regular paren comment ) + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -855,7 +877,7 @@ dup ;";
newline_before_line_comments: true,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 1 2 \\ inline comment\n + ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -871,7 +893,7 @@ dup ;";
newline_before_line_comments: false,
..Default::default()
};
let formatter = Formatter::new(config);
let formatter = DefaultFormatter::new(config);

let source = ": test 1 2 ( inline paren ) + \\ inline line\n 3 ;";
let formatted = formatter.format_source(source).unwrap();
Expand All @@ -880,3 +902,28 @@ dup ;";
assert!(formatted.contains("\\ inline line"));
}
}

pub struct NullFormatter;

impl NullFormatter {
pub fn new() -> Self {
Self
}
}

impl Formatter for NullFormatter {
fn format_document(&self, rope: &Rope) -> Result<Vec<TextEdit>> {
let source = rope.to_string();
Ok(vec![TextEdit {
range: Range::new(
Position::new(0, 0),
Position::new(rope.len_lines() as u32, 0),
),
new_text: source,
}])
}

fn format_source(&self, source: &str) -> Result<String> {
Ok(source.to_string())
}
}
Loading