diff --git a/Cargo.lock b/Cargo.lock
index 0f7cebae01d4ff5544f0852e211445e9fed3f042..c839e5b346533f1df492fec33b7c8142f9bce85d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -467,11 +467,13 @@ name = "proost"
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "atty",
  "clap",
  "colored",
  "kernel",
  "parser",
  "rustyline",
+ "rustyline-derive",
 ]
 
 [[package]]
@@ -559,6 +561,17 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "rustyline-derive"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
diff --git a/Cargo.toml b/Cargo.toml
index de3a1a7c0339c1a351e9ccc25f79c1b1edbfdfe5..fbfb7250491e87009e6fb3ce2545b2b308e3c968 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,6 +10,10 @@ anyhow = "1.0"
 clap = { version = "4.0.10", features = ["derive"] }
 derive_more = "0.99.17"
 num-bigint = "0.4"
+atty = "0.2"
+colored = "2"
+rustyline = "10.0.0"
+rustyline-derive = "0.7.0"
 
 [workspace.package]
 authors = [
diff --git a/proost/Cargo.toml b/proost/Cargo.toml
index 096b95e3adce1c1d869aa671af0a2c49f0126ce2..20d23c84f28ebf4219f18904ffc8dbc2a5df1f5f 100644
--- a/proost/Cargo.toml
+++ b/proost/Cargo.toml
@@ -12,6 +12,7 @@ parser.path = "../parser"
 
 anyhow.workspace = true
 clap.workspace = true
-
-colored = "2"
-rustyline = "10.0.0"
+atty.workspace = true
+colored.workspace = true
+rustyline.workspace = true
+rustyline-derive.workspace = true
diff --git a/proost/src/main.rs b/proost/src/main.rs
index 8eac4020aee8cc4fb8a4df983bcfd99abd4e44ad..2f07188ee6f64f48d97a9826ac359ef5847186bc 100644
--- a/proost/src/main.rs
+++ b/proost/src/main.rs
@@ -1,29 +1,37 @@
 #![feature(box_syntax)]
+#![feature(let_chains)]
 
 mod process;
+mod rustyline_helper;
 
+use std::fs;
+
+use atty::Stream;
 use clap::Parser;
+use rustyline::error::ReadlineError;
+use rustyline::{Cmd, Editor, EventHandler, KeyCode, KeyEvent, Modifiers, Result};
+use rustyline_helper::*;
+
 use kernel::Environment;
 use process::*;
-use rustyline::error::ReadlineError;
-use rustyline::Editor;
-use std::error::Error;
-use std::fs;
 
-// clap configuration
 #[derive(Parser)]
 #[command(author, version, about, long_about = None)]
 struct Args {
+    /// some .mdln files
     files: Vec<String>,
+    /// remove syntax highlighting
+    #[arg(long)]
+    no_color: bool,
 }
 
-// constants fetching
 const VERSION: &str = env!("CARGO_PKG_VERSION");
 const NAME: &str = env!("CARGO_PKG_NAME");
 
-fn main() -> Result<(), Box<dyn Error>> {
+fn main() -> Result<()> {
     let args = Args::parse();
 
+    // check if files are inputed
     if !args.files.is_empty() {
         for path in args.files.iter() {
             match fs::read_to_string(path) {
@@ -36,8 +44,22 @@ fn main() -> Result<(), Box<dyn Error>> {
         return Ok(());
     }
 
-    let mut rl_err: Option<ReadlineError> = None;
-    let mut rl = Editor::<()>::new()?;
+    // check if we are in a terminal
+    if atty::isnt(Stream::Stdout) || atty::isnt(Stream::Stdin) {
+        return Ok(());
+    }
+
+    let helper = RustyLineHelper::new(!args.no_color);
+    let mut rl = Editor::<RustyLineHelper>::new()?;
+    rl.set_helper(Some(helper));
+    rl.bind_sequence(
+        KeyEvent::from('\t'),
+        EventHandler::Conditional(Box::new(TabEventHandler)),
+    );
+    rl.bind_sequence(
+        KeyEvent(KeyCode::Enter, Modifiers::ALT),
+        EventHandler::Simple(Cmd::Newline),
+    );
     println!("Welcome to {} {}", NAME, VERSION);
 
     let mut env = Environment::new();
@@ -52,14 +74,8 @@ fn main() -> Result<(), Box<dyn Error>> {
             Ok(_) => (),
             Err(ReadlineError::Interrupted) => {}
             Err(ReadlineError::Eof) => break,
-            Err(err) => {
-                rl_err = Some(err);
-                break;
-            }
+            Err(err) => return Err(err),
         }
     }
-    match rl_err {
-        None => Ok(()),
-        Some(err) => Err(box err),
-    }
+    Ok(())
 }
diff --git a/proost/src/rustyline_helper.rs b/proost/src/rustyline_helper.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b6a10e9982bb4e01d33509c3054a6773653c685b
--- /dev/null
+++ b/proost/src/rustyline_helper.rs
@@ -0,0 +1,218 @@
+use colored::*;
+use rustyline::completion::{Completer, FilenameCompleter, Pair};
+use rustyline::highlight::Highlighter;
+use rustyline::hint::HistoryHinter;
+use rustyline::validate::{ValidationContext, ValidationResult, Validator};
+use rustyline::{Cmd, ConditionalEventHandler, Context, Event, EventContext, RepeatCount, Result};
+use rustyline_derive::{Helper, Hinter};
+use std::borrow::Cow::{self, Borrowed, Owned};
+
+/// Language keywords that should be highligted
+const KEYWORDS: [&str; 4] = ["check", "def", "eval", "import"];
+
+/// An Helper for a RustyLine Editor that implements:
+/// - a standard hinter
+/// - customs validator, completer and highlighter
+#[derive(Helper, Hinter)]
+pub struct RustyLineHelper {
+    color: bool,
+    completer: FilenameCompleter,
+    #[rustyline(Hinter)]
+    hinter: HistoryHinter,
+}
+
+impl RustyLineHelper {
+    pub fn new(color: bool) -> Self {
+        Self {
+            color,
+            completer: FilenameCompleter::new(),
+            hinter: HistoryHinter {},
+        }
+    }
+}
+
+/// An Handler for the tab event
+pub struct TabEventHandler;
+impl ConditionalEventHandler for TabEventHandler {
+    fn handle(&self, _: &Event, n: RepeatCount, _: bool, ctx: &EventContext) -> Option<Cmd> {
+        if ctx.line().starts_with("import") {
+            return None;
+        }
+        Some(Cmd::Insert(n, "  ".to_string()))
+    }
+}
+
+/// A variation of FilenameCompleter:
+/// file completion is available only after having typed import
+impl Completer for RustyLineHelper {
+    type Candidate = Pair;
+
+    fn complete(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Result<(usize, Vec<Pair>)> {
+        if line.starts_with("import") {
+            self.completer.complete_path(line, pos)
+        } else {
+            Ok(Default::default())
+        }
+    }
+}
+
+/// A variation of MatchingBracketValidator:
+/// no validation occurs when entering the import command
+impl Validator for RustyLineHelper {
+    fn validate(&self, ctx: &mut ValidationContext) -> Result<ValidationResult> {
+        if ctx.input().starts_with("import") {
+            return Ok(ValidationResult::Valid(None));
+        }
+
+        Ok(validate_arrows(ctx.input())
+            .or_else(|| validate_brackets(ctx.input()))
+            .unwrap_or(ValidationResult::Valid(None)))
+    }
+}
+
+fn validate_arrows(input: &str) -> Option<ValidationResult> {
+    let mut iter = input.as_bytes().iter().rev();
+
+    if let Some(b) = iter.find(|b| **b != b' ') {
+        if *b == b'>' && let Some(b) = iter.next() && (*b == b'-' || *b == b'=') {
+            return Some(ValidationResult::Incomplete)
+        }
+    }
+
+    None
+}
+
+fn validate_brackets(input: &str) -> Option<ValidationResult> {
+    let mut stack = vec![];
+
+    for c in input.chars() {
+        match c {
+            '(' => stack.push(c),
+            ')' => match stack.pop() {
+                Some('(') => {}
+                Some(_) => {
+                    return Some(ValidationResult::Invalid(Some(
+                        "\nMismatched brackets: ) is not properly closed".to_string(),
+                    )))
+                }
+                None => {
+                    return Some(ValidationResult::Invalid(Some(
+                        "\nMismatched brackets: ( is unpaired".to_string(),
+                    )))
+                }
+            },
+            _ => {}
+        }
+    }
+
+    if stack.is_empty() {
+        None
+    } else {
+        Some(ValidationResult::Incomplete)
+    }
+}
+
+/// A variation of MatchingBrackerHighlighter:
+/// no check occurs before cursor
+/// see: https://docs.rs/rustyline/10.0.0/rustyline/highlight/struct.MatchingBracketHighlighter.html
+impl Highlighter for RustyLineHelper {
+    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
+        if !self.color {
+            return Owned(hint.to_owned());
+        }
+        Owned(format!("{}", hint.bold()))
+    }
+
+    fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
+        self.color
+    }
+
+    fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
+        if line.len() <= 1 || !self.color {
+            return Borrowed(line);
+        }
+        let mut copy = line.to_owned();
+
+        if let Some((bracket, pos)) = get_bracket(line, pos)
+            && let Some((matching, pos)) = find_matching_bracket(line, pos, bracket) {
+                let s = String::from(matching as char);
+                copy.replace_range(pos..=pos, &s.blue().bold());
+            }
+        KEYWORDS
+            .iter()
+            .for_each(|keyword| replace_inplace(&mut copy, keyword, &keyword.blue().bold()));
+        Owned(copy)
+    }
+}
+
+/// Variation of the std replace function that only replace full words
+pub fn replace_inplace(input: &mut String, from: &str, to: &str) {
+    let mut offset = 0;
+    while let Some(pos) = input[offset..].find(from) {
+        if (pos == 0 || input.as_bytes()[offset + pos - 1] == b' ')
+            && (offset + pos + from.len() == input.len()
+                || input.as_bytes()[offset + pos + from.len()] == b' ')
+        {
+            input.replace_range(offset + pos..offset + pos + from.len(), to);
+            offset += pos + to.len();
+        } else {
+            offset += pos + from.len()
+        }
+    }
+}
+
+fn find_matching_bracket(line: &str, pos: usize, bracket: u8) -> Option<(u8, usize)> {
+    let matching_bracket = matching_bracket(bracket);
+    let mut to_match = 1;
+
+    let match_bracket = |b: u8| {
+        if b == matching_bracket {
+            to_match -= 1;
+        } else if b == bracket {
+            to_match += 1;
+        };
+        to_match == 0
+    };
+
+    if is_open_bracket(bracket) {
+        // forward search
+        line[pos + 1..]
+            .bytes()
+            .position(match_bracket)
+            .map(|pos2| (matching_bracket, pos2 + pos + 1))
+    } else {
+        // backward search
+        line[..pos]
+            .bytes()
+            .rev()
+            .position(match_bracket)
+            .map(|pos2| (matching_bracket, pos - pos2 - 1))
+    }
+}
+
+/// Check if the cursor is on a bracket
+const fn get_bracket(line: &str, pos: usize) -> Option<(u8, usize)> {
+    if !line.is_empty() && pos < line.len() {
+        let b = line.as_bytes()[pos];
+        if is_bracket(b) {
+            return Some((b, pos));
+        }
+    }
+    None
+}
+
+const fn matching_bracket(bracket: u8) -> u8 {
+    match bracket {
+        b'(' => b')',
+        b')' => b'(',
+        _ => unreachable!(),
+    }
+}
+
+const fn is_bracket(bracket: u8) -> bool {
+    matches!(bracket, b'(' | b')')
+}
+
+const fn is_open_bracket(bracket: u8) -> bool {
+    matches!(bracket, b'(')
+}