summaryrefslogtreecommitdiffstats
path: root/src/editor.rs
blob: 1da71a1e4f102b86d35772de9be21da7668b8cf8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
use std::io::{self, Stdout, Write};

use anyhow::Result;

use crossterm::{
    cursor,
    event::{self, Event, KeyCode, KeyModifiers},
    queue, style,
    terminal::{self, disable_raw_mode, enable_raw_mode},
};

use crate::utils::paste;

pub fn edit() -> Result<String> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();

    let ret = edit_inner(&mut stdout);

    // restore terminal
    disable_raw_mode()?;

    ret
}

fn edit_inner(writer: &mut Stdout) -> Result<String> {
    let mut session = Session::new(writer);

    loop {
        let evt = event::read()?;
        if let Event::Key(key) = evt {
            match key.code {
                KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
                    // quit
                    return Ok(String::new());
                }
                KeyCode::Char('d') if key.modifiers == KeyModifiers::CONTROL => {
                    // submit
                    return Ok(session.buffer);
                }
                KeyCode::Char('v') if key.modifiers == KeyModifiers::CONTROL => {
                    let content = paste()?;
                    session.push_str(&content)?;
                }
                KeyCode::Char(c)
                    if matches!(key.modifiers, KeyModifiers::NONE | KeyModifiers::SHIFT) =>
                {
                    session.push(c)?;
                }
                KeyCode::Enter => {
                    session.push('\n')?;
                }
                _ => {}
            }
        }
        session.flush()?;
    }
}

struct Session<'a, T: Write> {
    writer: &'a mut T,
    buffer: String,
    dirty: bool,
}

impl<'a, T: Write> Session<'a, T> {
    fn new<'b: 'a>(writer: &'b mut T) -> Self {
        Self {
            buffer: String::new(),
            writer,
            dirty: false,
        }
    }
    fn push(&mut self, ch: char) -> io::Result<()> {
        if ch == '\n' {
            self.new_line()?;
        } else {
            queue!(self.writer, style::Print(ch))?;
        }
        self.buffer.push(ch);
        self.dirty = true;
        Ok(())
    }
    fn push_str(&mut self, text: &str) -> io::Result<()> {
        for line in text.lines() {
            if !line.is_empty() {
                queue!(self.writer, style::Print(line))?;
            }
            self.new_line()?;
        }

        Ok(())
    }
    fn new_line(&mut self) -> io::Result<()> {
        let (_, y) = cursor::position()?;
        let (_, h) = terminal::size()?;
        if y == h - 1 {
            queue!(self.writer, terminal::ScrollUp(1), cursor::MoveTo(0, y))?;
        } else {
            queue!(self.writer, cursor::MoveToNextLine(1))?;
        }
        Ok(())
    }
    fn flush(&mut self) -> io::Result<()> {
        if self.dirty {
            return self.writer.flush();
        }
        Ok(())
    }
}