use turbo_vision::app::Application;
use turbo_vision::core::command::{CM_CLOSE, CM_HELP_INDEX, CM_QUIT, CM_TOGGLE_BLOCK_MODE};
use turbo_vision::core::event::KB_F10;
use turbo_vision::core::geometry::Rect;
use turbo_vision::core::menu_data::{Menu, MenuItem};
use turbo_vision::views::edit_window::EditWindow;
use turbo_vision::views::help_file::HelpFile;
use turbo_vision::views::menu_bar::{MenuBar, SubMenu};
use turbo_vision::views::msgbox::message_box_ok;
use turbo_vision::views::status_line::{StatusItem, StatusLine};
use turbo_vision::views::syntax::{SyntaxHighlighter, Token, TokenType};
const HC_FILE_MENU: u16 = 1;
const HC_EDITOR: u16 = 2;
struct PascalHighlighter {
in_block_comment_brace: bool,
in_block_comment_paren: bool,
}
impl PascalHighlighter {
fn new() -> Self {
Self {
in_block_comment_brace: false,
in_block_comment_paren: false,
}
}
fn is_keyword(word: &str) -> bool {
matches!(
word,
"program"
| "var"
| "begin"
| "end"
| "if"
| "then"
| "else"
| "while"
| "do"
| "for"
| "to"
| "downto"
| "repeat"
| "until"
| "write"
| "writeln"
| "read"
| "readln"
| "div"
| "mod"
| "and"
| "or"
| "not"
| "true"
| "false"
| "const"
| "type"
| "procedure"
| "function"
| "array"
| "of"
| "record"
| "nil"
| "case"
| "with"
| "new"
| "dispose"
| "forward"
)
}
fn is_type_name(word: &str) -> bool {
matches!(
word,
"integer"
| "string"
| "boolean"
| "real"
| "char"
| "byte"
| "word"
| "longint"
| "text"
)
}
}
impl SyntaxHighlighter for PascalHighlighter {
fn language(&self) -> &str {
"pascal"
}
fn highlight_line(&self, line: &str, _line_number: usize) -> Vec<Token> {
let mut tokens = Vec::new();
let chars: Vec<char> = line.chars().collect();
let len = chars.len();
let mut i = 0;
let mut in_brace = self.in_block_comment_brace;
let mut in_paren = self.in_block_comment_paren;
while i < len {
if in_brace {
let start = i;
while i < len && chars[i] != '}' {
i += 1;
}
if i < len {
i += 1;
in_brace = false;
}
tokens.push(Token::new(start, i, TokenType::Comment));
continue;
}
if in_paren {
let start = i;
while i < len {
if i + 1 < len && chars[i] == '*' && chars[i + 1] == ')' {
i += 2;
in_paren = false;
break;
}
i += 1;
}
tokens.push(Token::new(start, i, TokenType::Comment));
continue;
}
let ch = chars[i];
if ch.is_whitespace() {
let start = i;
while i < len && chars[i].is_whitespace() {
i += 1;
}
tokens.push(Token::new(start, i, TokenType::Normal));
continue;
}
if ch == '/' && i + 1 < len && chars[i + 1] == '/' {
tokens.push(Token::new(i, len, TokenType::Comment));
break;
}
if ch == '{' {
let start = i;
i += 1;
while i < len && chars[i] != '}' {
i += 1;
}
if i < len {
i += 1;
} else {
in_brace = true;
}
tokens.push(Token::new(start, i, TokenType::Comment));
continue;
}
if ch == '(' && i + 1 < len && chars[i + 1] == '*' {
let start = i;
i += 2;
while i < len {
if i + 1 < len && chars[i] == '*' && chars[i + 1] == ')' {
i += 2;
break;
}
i += 1;
}
if i >= len && !(i >= 2 && chars[i - 2] == '*' && chars[i - 1] == ')') {
in_paren = true;
}
tokens.push(Token::new(start, i, TokenType::Comment));
continue;
}
if ch == '\'' {
let start = i;
i += 1;
while i < len {
if chars[i] == '\'' {
if i + 1 < len && chars[i + 1] == '\'' {
i += 2;
} else {
i += 1;
break;
}
} else {
i += 1;
}
}
tokens.push(Token::new(start, i, TokenType::String));
continue;
}
if ch.is_ascii_digit() || (ch == '$' && i + 1 < len && chars[i + 1].is_ascii_hexdigit())
{
let start = i;
if ch == '$' {
i += 1;
while i < len && chars[i].is_ascii_hexdigit() {
i += 1;
}
} else {
while i < len && chars[i].is_ascii_digit() {
i += 1;
}
}
tokens.push(Token::new(start, i, TokenType::Number));
continue;
}
if ch.is_ascii_alphabetic() || ch == '_' {
let start = i;
while i < len && (chars[i].is_ascii_alphanumeric() || chars[i] == '_') {
i += 1;
}
let word: String = chars[start..i].iter().collect();
let lower = word.to_lowercase();
let token_type = if Self::is_keyword(&lower) {
TokenType::Keyword
} else if Self::is_type_name(&lower) {
TokenType::Type
} else {
TokenType::Identifier
};
tokens.push(Token::new(start, i, token_type));
continue;
}
if i + 1 < len {
let two: String = chars[i..i + 2].iter().collect();
if matches!(two.as_str(), ":=" | "<>" | "<=" | ">=") {
tokens.push(Token::new(i, i + 2, TokenType::Operator));
i += 2;
continue;
}
}
if matches!(ch, '+' | '-' | '*' | '=' | '<' | '>' | '/') {
tokens.push(Token::new(i, i + 1, TokenType::Operator));
i += 1;
continue;
}
if matches!(ch, ';' | ':' | '.' | ',' | '(' | ')' | '[' | ']') {
tokens.push(Token::new(i, i + 1, TokenType::Special));
i += 1;
continue;
}
tokens.push(Token::new(i, i + 1, TokenType::Normal));
i += 1;
}
tokens
}
fn is_multiline_context(&self, _line_number: usize) -> bool {
self.in_block_comment_brace || self.in_block_comment_paren
}
fn update_multiline_state(&mut self, line: &str, _line_number: usize) {
let chars: Vec<char> = line.chars().collect();
let len = chars.len();
let mut i = 0;
while i < len {
if self.in_block_comment_brace {
if chars[i] == '}' {
self.in_block_comment_brace = false;
}
i += 1;
continue;
}
if self.in_block_comment_paren {
if i + 1 < len && chars[i] == '*' && chars[i + 1] == ')' {
self.in_block_comment_paren = false;
i += 2;
continue;
}
i += 1;
continue;
}
if chars[i] == '\'' {
i += 1;
while i < len && chars[i] != '\'' {
i += 1;
}
if i < len {
i += 1;
}
continue;
}
if chars[i] == '/' && i + 1 < len && chars[i + 1] == '/' {
break;
}
if chars[i] == '{' {
self.in_block_comment_brace = true;
i += 1;
continue;
}
if chars[i] == '(' && i + 1 < len && chars[i + 1] == '*' {
self.in_block_comment_paren = true;
i += 2;
continue;
}
i += 1;
}
}
}
const SAMPLE_PROGRAM: &str = r#"program Demo;
const
Size = 5;
type
Vector = array[1..5] of integer;
Point = record
x, y: real;
end;
var
nums: Vector;
pt: Point;
total, i: integer;
avg: real;
msg: string;
p: ^integer;
procedure Fill(var a: Vector; n: integer);
var
k: integer;
begin
for k := 1 to n do
a[k] := k * k
end;
function Sum(var a: Vector; n: integer): integer;
var
s, j: integer;
begin
s := 0;
for j := 1 to n do
s := s + a[j];
Sum := s
end;
begin
{ Arrays and procedures }
Fill(nums, Size);
total := Sum(nums, Size);
avg := total / Size;
writeln('Squares: 1..', Size);
for i := 1 to Size do
write(nums[i], ' ');
writeln;
{ Real arithmetic }
writeln('Sum = ', total);
writeln('Avg = ', avg);
{ Records }
pt.x := avg;
pt.y := 3.14;
writeln('Point = (', pt.x, ', ', pt.y, ')');
{ Strings }
msg := 'Hello' + ' ' + 'Pascal!';
writeln(msg, ' len=', length(msg));
{ Heap pointers }
new(p);
p^ := 42;
writeln('Heap value: ', p^);
dispose(p);
{ Repeat-until }
i := 1;
repeat
i := i * 2
until i > 100;
writeln('First power of 2 > 100: ', i);
{ ord / chr }
writeln('ord(A) = ', ord('A'));
write('chr(90) = ');
writeln(chr(90));
if total = 55 then
writeln('All correct!')
else
writeln('Something is wrong!')
end.
"#;
const HELP_CONTENT: &str = r#"# Bruto Pascal IDE {#welcome}
Welcome to the **Bruto Pascal IDE**, a Mini-Pascal
development environment built with Turbo Vision for Rust.
Press **F1** at any time for context-sensitive help.
[File Menu](#file-menu)
[Editor](#editor)
[Keyboard Shortcuts](#shortcuts)
[About Pascal](#pascal-lang)
# File Menu {#file-menu}
The **File** menu provides these commands:
**Close** `Alt+F3` Close the current editor
**Exit** `Alt+X` Quit the IDE
# Editor {#editor}
The editor supports **Pascal syntax highlighting**
with the following token categories:
**Keywords** `program`, `begin`, `end`, `if`...
**Types** `integer`, `string`, `boolean`...
**Strings** Single-quoted: `'Hello'`
**Numbers** Decimal and hex: `42`, `$FF`
**Comments** `{ ... }`, `(* ... *)`, `// ...`
**Operators** `:=`, `+`, `-`, `*`, `<>`, ...
Use the mouse or keyboard to scroll and edit text.
[Keyboard Shortcuts](#shortcuts)
# Keyboard Shortcuts {#shortcuts}
**F1** Open help
**F10** Activate the menu bar
**Alt+F3** Close current window
**Alt+X** Exit the IDE
**Arrow keys** Move the cursor
**Home/End** Go to line start / end
**PgUp/PgDn** Scroll page up / down
[Back to Welcome](#welcome)
# About Pascal {#pascal-lang}
Pascal is a structured programming language designed
by Niklaus Wirth in 1970. It emphasizes clear syntax,
strong typing, and structured control flow.
Key features of the language:
- Block structure with `begin` / `end`
- Procedures and functions
- Records, arrays, and pointer types
- `repeat..until` and `while..do` loops
The sample program demonstrates arrays, records,
pointer allocation, and formatted output.
[Back to Welcome](#welcome)
"#;
fn main() -> turbo_vision::core::error::Result<()> {
let mut app = Application::new()?;
let (width, height) = app.terminal.size();
let w = width as i16;
let h = height as i16;
let help = HelpFile::from_content(HELP_CONTENT);
app.set_help(help);
app.register_help_context(HC_FILE_MENU, "file-menu");
app.register_help_context(HC_EDITOR, "editor");
let mut menu_bar = MenuBar::new(Rect::new(0, 0, w, 1));
menu_bar.add_submenu(SubMenu::new(
"~F~ile",
Menu::from_items(vec![
MenuItem::with_shortcut("~C~lose", CM_CLOSE, 0, "Alt+F3", HC_FILE_MENU),
MenuItem::separator(),
MenuItem::with_shortcut("E~x~it", CM_QUIT, 0, "Alt+X", HC_FILE_MENU),
]),
));
menu_bar.add_submenu(SubMenu::new(
"~E~dit",
Menu::from_items(vec![MenuItem::flag(
"Bloc~k~ mode",
CM_TOGGLE_BLOCK_MODE,
0,
HC_EDITOR,
turbo_vision::core::state::block_edit_mode,
)]),
));
menu_bar.add_submenu(SubMenu::new(
"~H~elp",
Menu::from_items(vec![MenuItem::with_shortcut(
"~C~ontents",
CM_HELP_INDEX,
0,
"F1",
0,
)]),
));
app.set_menu_bar(menu_bar);
let mut status_line = StatusLine::new(
Rect::new(0, h - 1, w, h),
vec![
StatusItem::new("~F1~ Help", 0, CM_HELP_INDEX),
StatusItem::new("~F10~ Menu", KB_F10, 0),
StatusItem::new("~Alt+X~ Exit", 0x012D, CM_QUIT),
],
);
status_line.set_right_indicator(|| {
turbo_vision::core::state::block_edit_mode().then(|| "▭ Block".to_string())
});
app.set_status_line(status_line);
let desk = app.get_tile_rect();
let edit_window = EditWindow::new(Rect::new(0, 0, desk.width(), desk.height()), "Untitled.pas");
edit_window
.editor_rc()
.borrow_mut()
.set_highlighter(Box::new(PascalHighlighter::new()));
edit_window
.editor_rc()
.borrow_mut()
.set_text(SAMPLE_PROGRAM);
app.desktop.add(Box::new(edit_window));
message_box_ok(
&mut app,
"\x03Bruto Pascal IDE\n\n\
Version 0.1.0\n\n\
A Mini-Pascal IDE built with\n\
Turbo Vision for Rust\n\n\
\x03(c) 2026 Enzo Lombardi",
);
app.run();
Ok(())
}