datafusion_tui/app/editor/
mod.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use log::debug;
19use std::cmp;
20use std::fs::File;
21use std::io::{self, BufRead, BufReader};
22
23use unicode_width::UnicodeWidthStr;
24
25use crate::app::core::AppReturn;
26use crate::app::datafusion::context::QueryResultsMeta;
27use crate::app::error::Result;
28
29const MAX_EDITOR_LINES: u16 = 17;
30
31/// Single line of text in SQL Editor and cursor over it
32#[derive(Debug)]
33pub struct Line {
34    text: io::Cursor<String>,
35}
36
37impl Default for Line {
38    fn default() -> Line {
39        Line {
40            text: io::Cursor::new(String::new()),
41        }
42    }
43}
44
45impl Line {
46    pub fn new(text: String) -> Self {
47        Line {
48            text: io::Cursor::new(text),
49        }
50    }
51}
52
53/// All lines in SQL Editor and cursor location
54#[derive(Debug, Default)]
55pub struct Input {
56    pub lines: Vec<Line>,
57    /// Current line in editor
58    pub current_row: u16,
59    /// Current column in editor
60    pub cursor_column: u16,
61}
62
63impl Input {
64    pub fn combine_lines(&self) -> String {
65        let text: Vec<&str> = self
66            .lines
67            .iter()
68            // Replace tabs with spaces
69            .map(|line| line.text.get_ref().as_str())
70            .collect();
71        text.join("")
72    }
73
74    pub fn combine_visible_lines(&self) -> String {
75        let start = if self.current_row < MAX_EDITOR_LINES {
76            0
77        } else {
78            self.current_row - MAX_EDITOR_LINES
79        } as usize;
80
81        let end = (start + (MAX_EDITOR_LINES as usize) + 1) as usize;
82
83        let text: Vec<&str> = if start == 0 {
84            // debug!("Combining all lines");
85            self.lines
86                .iter()
87                .map(|line| line.text.get_ref().as_str())
88                .collect()
89        } else {
90            debug!("Combining visible lines: start({}) to end({})", start, end);
91            self.lines[start..end]
92                .iter()
93                .map(|line| line.text.get_ref().as_str())
94                .collect()
95        };
96
97        text.join("")
98    }
99
100    pub fn append_char(&mut self, c: char) -> Result<AppReturn> {
101        if self.lines.is_empty() {
102            let line = Line::default();
103            self.lines.push(line)
104        }
105        match c {
106            '\n' => self.new_line(),
107            '\t' => {
108                self.lines[self.current_row as usize]
109                    .text
110                    .get_mut()
111                    .push_str("    ");
112                self.cursor_column += 4
113            }
114            _ => {
115                self.lines[self.current_row as usize]
116                    .text
117                    .get_mut()
118                    .insert(self.cursor_column as usize, c);
119                debug!(
120                    "Line after appending {:?} : {:?}",
121                    c,
122                    self.lines[self.current_row as usize].text.get_ref()
123                );
124                self.cursor_column += 1;
125            }
126        }
127        Ok(AppReturn::Continue)
128    }
129
130    /// Remove last character from the current line.
131    pub fn pop(&mut self) -> Option<char> {
132        self.lines[self.current_row as usize].text.get_mut().pop()
133    }
134
135    /// Moves the cursor one line up.
136    pub fn up_row(&mut self) -> Result<AppReturn> {
137        if self.current_row > 0 {
138            match self.lines[self.current_row as usize]
139                .text
140                .get_ref()
141                .is_empty()
142            {
143                true => {
144                    self.current_row -= 1;
145                    let new_row_width =
146                        self.lines[self.current_row as usize].text.get_ref().width() as u16;
147                    self.cursor_column = new_row_width;
148                }
149                false => {
150                    let previous_col = self.cursor_column;
151                    self.current_row -= 1;
152                    let new_row_width =
153                        self.lines[self.current_row as usize].text.get_ref().width() as u16;
154                    let new_col = cmp::min(previous_col, new_row_width);
155                    self.cursor_column = new_col;
156                }
157            }
158        }
159        Ok(AppReturn::Continue)
160    }
161
162    /// Moves the cursor one line down.
163    pub fn down_row(&mut self) -> Result<AppReturn> {
164        if self.lines.is_empty() {
165            return Ok(AppReturn::Continue);
166        } else if self.current_row + 1 < self.lines.len() as u16 {
167            let previous_col = self.cursor_column;
168            self.current_row += 1;
169            let new_row_width = self.lines[self.current_row as usize].text.get_ref().width() as u16;
170            let new_col = cmp::min(previous_col, new_row_width);
171            self.cursor_column = new_col;
172        }
173        Ok(AppReturn::Continue)
174    }
175
176    /// Moves the cursor to the next character.
177    pub fn next_char(&mut self) -> Result<AppReturn> {
178        if self.lines.is_empty()
179            || self.cursor_column
180                == self.lines[self.current_row as usize].text.get_ref().width() as u16
181        {
182            return Ok(AppReturn::Continue);
183        } else if (self.cursor_column + 1
184            == self.lines[self.current_row as usize].text.get_ref().width() as u16)
185            && (self.current_row as usize != self.lines.len() - 1)
186        {
187            self.current_row += 1;
188            self.cursor_column = 0
189        } else {
190            self.cursor_column += 1
191        }
192        Ok(AppReturn::Continue)
193    }
194
195    /// Moves the cursor to the previous character.
196    pub fn previous_char(&mut self) -> Result<AppReturn> {
197        if (self.cursor_column == 0) && (self.current_row > 0) {
198            self.current_row -= 1;
199            self.cursor_column = self.lines[self.current_row as usize].text.get_ref().width() as u16
200        } else if self.cursor_column > 0 {
201            self.cursor_column -= 1
202        }
203        Ok(AppReturn::Continue)
204    }
205
206    #[allow(dead_code)]
207    /// Returns the number of UTF8 characters in the line where the cursor is located.
208    /// This function is only required, if the editor needs to support non-ascii characters
209    /// which have more then 1 byte. <br>
210    /// Example: <br>
211    /// "abcd" has 4 characters and 4 bytes <br>
212    /// "äbcd" has 4 characters but 5 bytes (first character requires 2 bytes)
213    fn number_chars_in_current_line(&self) -> usize {
214        self.lines[self.current_row as usize]
215            .text
216            .get_ref()
217            .chars()
218            .count()
219    }
220
221    pub fn backspace(&mut self) -> Result<AppReturn> {
222        debug!("Backspace entered. Input Before: {:?}", self);
223        match self.lines[self.current_row as usize]
224            .text
225            .get_ref()
226            .is_empty()
227        {
228            true => {
229                self.up_row()?;
230                // Pop newline character
231                self.pop();
232            }
233            false => {
234                if self.cursor_is_at_line_beginning() {
235                    if self.on_first_line() {
236                        debug!("On first column of first line.  Unable to backspace")
237                    } else {
238                        let prior_row_text =
239                            self.lines[self.current_row as usize].text.get_ref().clone();
240                        self.up_row()?;
241                        self.pop();
242                        self.lines[self.current_row as usize]
243                            .text
244                            .get_mut()
245                            .push_str(&prior_row_text);
246                        self.lines.remove((self.current_row + 1) as usize);
247                    }
248                } else if self.cursor_is_at_line_end() || self.cursor_is_in_line_middle() {
249                    self.lines[self.current_row as usize]
250                        .text
251                        .get_mut()
252                        .remove((self.cursor_column - 1) as usize);
253                    self.cursor_column -= 1
254                }
255            }
256        };
257        debug!("Input After: {:?}", self);
258        Ok(AppReturn::Continue)
259    }
260
261    pub fn clear(&mut self) -> Result<AppReturn> {
262        let lines = Vec::<Line>::new();
263        self.lines = lines;
264        self.current_row = 0;
265        self.cursor_column = 0;
266        Ok(AppReturn::Continue)
267    }
268
269    pub fn tab(&mut self) -> Result<AppReturn> {
270        self.append_char('\t')
271    }
272
273    fn new_line(&mut self) {
274        if self.cursor_is_at_line_beginning() {
275            debug!("Cursor at line beginning");
276            let line_empty = self.lines[self.current_row as usize]
277                .text
278                .get_ref()
279                .is_empty();
280
281            let line = if line_empty {
282                Line::default()
283            } else {
284                let text = self.lines[self.current_row as usize].text.get_ref().clone();
285                Line::new(text)
286            };
287
288            self.lines[self.current_row as usize] = Line::new(String::from('\n'));
289            if self.on_last_line() {
290                self.lines.push(line);
291            } else {
292                self.lines.insert((self.current_row + 1) as usize, line)
293            }
294            self.current_row += 1;
295            self.cursor_column = 0;
296            debug!("Lines: {:?}", self.lines);
297        } else if self.cursor_is_at_line_end() {
298            debug!("Cursor at line end");
299            self.lines[self.current_row as usize]
300                .text
301                .get_mut()
302                .push('\n');
303            let line = Line::default();
304            if self.on_last_line() {
305                self.lines.push(line);
306            } else {
307                self.lines.insert((self.current_row + 1) as usize, line)
308            }
309            self.current_row += 1;
310            self.cursor_column = 0;
311        } else if self.cursor_is_in_line_middle() {
312            debug!("Cursor in middle of line");
313            let new_line: String = self.lines[self.current_row as usize]
314                .text
315                .get_mut()
316                .drain((self.cursor_column as usize)..)
317                .collect();
318            self.lines[self.current_row as usize]
319                .text
320                .get_mut()
321                .push('\n');
322
323            debug!("New line: {}", new_line);
324            let line = Line::new(new_line);
325            if self.on_last_line() {
326                self.lines.push(line);
327            } else {
328                self.lines.insert((self.current_row + 1) as usize, line)
329            }
330            self.current_row += 1;
331            self.cursor_column = 0;
332        } else {
333            debug!("Unhandled")
334        }
335    }
336
337    fn cursor_is_at_line_end(&self) -> bool {
338        let len = self.lines[self.current_row as usize].text.get_ref().len();
339        self.cursor_column as usize == len
340    }
341
342    fn cursor_is_at_line_beginning(&self) -> bool {
343        self.cursor_column == 0
344    }
345
346    fn cursor_is_in_line_middle(&self) -> bool {
347        let len = self.lines[self.current_row as usize].text.get_ref().len();
348        (self.cursor_column > 0) && ((self.cursor_column as usize) < len)
349    }
350
351    fn on_first_line(&self) -> bool {
352        let res = self.current_row as usize == 0;
353        debug!("On first line: {}", res);
354        res
355    }
356
357    fn on_last_line(&self) -> bool {
358        let res = self.current_row as usize == self.lines.len() - 1;
359        debug!("On last line: {}", res);
360        res
361    }
362}
363
364/// The entire editor and it's state
365pub struct Editor {
366    /// Current value of the input box
367    pub input: Input,
368    /// Flag if SQL statement was terminated with ';'
369    pub sql_terminated: bool,
370    /// History of QueryResultMeta
371    pub history: Vec<QueryResultsMeta>,
372}
373impl Default for Editor {
374    fn default() -> Editor {
375        let input = Input::default();
376        Editor {
377            input,
378            history: Vec::new(),
379            sql_terminated: false,
380        }
381    }
382}
383
384impl Editor {
385    pub fn get_cursor_row(&self) -> u16 {
386        if self.input.current_row < MAX_EDITOR_LINES {
387            self.input.current_row
388        } else {
389            MAX_EDITOR_LINES
390        }
391    }
392
393    pub fn get_cursor_column(&self) -> u16 {
394        self.input.cursor_column
395    }
396
397    pub fn load_file(&mut self, file: File) -> Result<()> {
398        let buf = BufReader::new(file);
399        let mut lines = Vec::new();
400        for line in buf.lines() {
401            let mut line = line?;
402            debug!("Line: {}", line);
403            line.push('\n');
404            let line = line.replace('\t', "    ");
405            lines.push(Line::new(line));
406        }
407        self.input.lines = lines;
408        Ok(())
409    }
410}
411
412#[cfg(test)]
413mod tests {
414    use crate::app::editor::{Input, Line};
415    use std::io::Cursor;
416
417    #[test]
418    #[should_panic]
419    fn can_delete_non_ascii_characters() {
420        //
421        // Due to missing support for non-ascii characters, this test panics.
422        // #[should_panic] should be removed as soon as non-ascii chars are supported.
423        //
424        let mut input: Input = Input {
425            lines: vec![Line {
426                text: Cursor::new(String::from("äää")),
427            }],
428            current_row: 0,
429            cursor_column: 3,
430        };
431
432        input.backspace().expect("Expect that can delete character");
433        assert_eq!(input.current_row, 0);
434        assert_eq!(input.cursor_column, 2);
435
436        input.backspace().expect("Expect that can delete character");
437        assert_eq!(input.current_row, 0);
438        assert_eq!(input.cursor_column, 1);
439    }
440
441    #[test]
442    fn next_character_in_one_line() {
443        let mut input: Input = Input {
444            lines: vec![Line {
445                text: Cursor::new(String::from("aaa")),
446            }],
447            current_row: 0,
448            cursor_column: 0,
449        };
450
451        input.next_char().expect("Could move to next character");
452        assert_eq!(
453            input.cursor_column, 1,
454            "When moving once, cursor should be after first character"
455        );
456
457        input.next_char().expect("Could move to next character");
458        assert_eq!(input.cursor_column, 2);
459
460        input.next_char().expect("Could move to next character");
461        assert_eq!(input.cursor_column, 3);
462
463        input.next_char().expect("Could move to next character");
464        assert_eq!(
465            input.cursor_column, 3,
466            "When line is over and no next line exists, cursor should stop"
467        );
468        assert_eq!(
469            input.current_row, 0,
470            "When line is over and no next line exists, cursor should stop"
471        );
472    }
473
474    #[test]
475    fn previous_character_in_one_line() {
476        let mut input: Input = Input {
477            lines: vec![Line {
478                text: Cursor::new(String::from("aaa")),
479            }],
480            current_row: 0,
481            cursor_column: 3,
482        };
483
484        input
485            .previous_char()
486            .expect("Could move to previous character");
487        assert_eq!(input.cursor_column, 2);
488
489        input
490            .previous_char()
491            .expect("Could move to previous character");
492        assert_eq!(input.cursor_column, 1);
493
494        input
495            .previous_char()
496            .expect("Could move to previous character");
497        assert_eq!(input.cursor_column, 0);
498
499        input
500            .previous_char()
501            .expect("Could move to previous character");
502        assert_eq!(input.cursor_column, 0);
503    }
504
505    #[test]
506    #[ignore]
507    fn jump_to_next_line_on_next_character_at_the_end_of_line() {
508        // This functionality is not implemented but could come in later releases.
509        let mut input: Input = Input {
510            lines: vec![
511                Line {
512                    text: Cursor::new(String::from("aa")),
513                },
514                Line {
515                    text: Cursor::new(String::from("bb")),
516                },
517            ],
518            current_row: 0,
519            cursor_column: 0,
520        };
521
522        input.next_char().expect("Could move to next character");
523        input.next_char().expect("Could move to next character");
524
525        // we expect to jump to the next line here
526        input.next_char().expect("Could move to next character");
527        assert_eq!(
528            input.current_row, 1,
529            "Cursor should have jumped to next line"
530        );
531        assert_eq!(
532            input.cursor_column, 0,
533            "Cursor should be at beginning of the line"
534        );
535
536        input.next_char().expect("Could move to next character");
537        assert_eq!(input.current_row, 1);
538        assert_eq!(
539            input.cursor_column, 1,
540            "Cursor should be at the end of second line"
541        );
542
543        input.next_char().expect("Could move to next character");
544        assert_eq!(input.current_row, 1);
545        assert_eq!(input.cursor_column, 2);
546
547        input.next_char().expect("Could move to next character");
548        assert_eq!(input.current_row, 1);
549        assert_eq!(
550            input.cursor_column, 2,
551            "When there is no next line, cursor should stay unchanged"
552        );
553    }
554
555    #[test]
556    #[ignore]
557    fn jump_to_previous_line_on_previous_character_at_the_beginning_of_line() {
558        // This functionality is not implemented but could come in later releases.
559        let mut input: Input = Input {
560            lines: vec![
561                Line {
562                    text: Cursor::new(String::from("aa")),
563                },
564                Line {
565                    text: Cursor::new(String::from("bb")),
566                },
567            ],
568            current_row: 1,
569            cursor_column: 0,
570        };
571
572        input.previous_char().expect("Could move to next character");
573        assert_eq!(
574            input.current_row, 0,
575            "Cursor should have jumped to previous line"
576        );
577        assert_eq!(
578            input.cursor_column, 1,
579            "Cursor should be at end of the previous line"
580        );
581    }
582
583    #[test]
584    fn non_ascii_character_count() {
585        let input: Input = Input {
586            lines: vec![Line {
587                text: Cursor::new(String::from("äää")),
588            }],
589            current_row: 0,
590            cursor_column: 0,
591        };
592
593        assert_eq!(input.number_chars_in_current_line(), 3);
594
595        let input2: Input = Input {
596            lines: vec![Line {
597                text: Cursor::new(String::from("äääb")),
598            }],
599            current_row: 0,
600            cursor_column: 0,
601        };
602        assert_eq!(input2.number_chars_in_current_line(), 4);
603    }
604
605    #[test]
606    #[should_panic]
607    fn test_append_char() {
608        //
609        // Due to missing support for non-ascii characters, this test panics.
610        // #[should_panic] should be removed as soon as non-ascii chars are supported.
611        //
612        let mut input: Input = Input::default();
613
614        // Input: ""
615        input.append_char('ä').expect("Could append a character");
616        assert_eq!(input.current_row, 0);
617        assert_eq!(input.cursor_column, 1);
618        assert_eq!(input.number_chars_in_current_line(), 1);
619
620        // Input: "ä"
621        input.append_char('b').expect("Could append a character");
622        assert_eq!(input.current_row, 0);
623        assert_eq!(input.cursor_column, 2);
624        assert_eq!(input.number_chars_in_current_line(), 2);
625
626        // Input: "äb"
627        input.append_char('\t').expect("Could append a character");
628        assert_eq!(input.current_row, 0);
629        assert_eq!(input.cursor_column, 6);
630        assert_eq!(input.number_chars_in_current_line(), 6);
631
632        // Input: "äb    "
633        input.append_char('\n').expect("Could append a character");
634        assert_eq!(input.current_row, 1);
635        assert_eq!(input.cursor_column, 0);
636        assert_eq!(input.number_chars_in_current_line(), 0);
637
638        // Input: "äb    \n"
639        //        ""
640        input.append_char('a').expect("Could append a character");
641        assert_eq!(input.current_row, 1);
642        assert_eq!(input.cursor_column, 1);
643        assert_eq!(input.number_chars_in_current_line(), 1);
644
645        // Input: "ä|b    \n" <- cursor |
646        //        "a"
647        input.up_row().expect("Can go up");
648        input.append_char('a').expect("Could append a character");
649        assert_eq!(input.current_row, 0);
650        assert_eq!(input.cursor_column, 2);
651        assert_eq!(
652            input.number_chars_in_current_line(),
653            7,
654            "Line: {}",
655            input.lines[input.current_row as usize].text.get_ref()
656        );
657
658        // Input: "äab    \n"
659        //        "a|"       <- cursor |
660        input.down_row().expect("Can go down");
661        input.previous_char().expect("Can go left");
662        input.append_char('b').expect("Can type a character");
663        // Input: "äab    \n"
664        //        "b|a"  <- cursor |
665        assert_eq!(input.current_row, 1);
666        assert_eq!(input.cursor_column, 1);
667        assert_eq!(input.lines[1].text.get_ref(), "ba");
668    }
669
670    #[test]
671    fn test_up_row_and_down_row() {
672        let mut input: Input = Input {
673            lines: vec![
674                Line {
675                    text: Cursor::new(String::from("aaaa")),
676                },
677                Line {
678                    text: Cursor::new(String::from("bbbb")),
679                },
680                Line {
681                    text: Cursor::new(String::from("cccc")),
682                },
683                Line {
684                    text: Cursor::new(String::from("")),
685                },
686                Line {
687                    text: Cursor::new(String::from("dddd")),
688                },
689            ],
690            current_row: 0,
691            cursor_column: 2,
692        };
693
694        input.up_row().expect("No exception should be thrown.");
695        assert_eq!(input.current_row, 0, "At 0th line, up_row has no effect");
696        assert_eq!(
697            input.cursor_column, 2,
698            "When up_row has no effect, the location inside the line should stay unchanged"
699        );
700
701        input.down_row().expect("No exception should be thrown.");
702        assert_eq!(input.current_row, 1);
703        assert_eq!(input.cursor_column, 2);
704
705        input.down_row().expect("No exception should be thrown.");
706        assert_eq!(input.current_row, 2);
707        assert_eq!(input.cursor_column, 2);
708
709        input.down_row().expect("No exception should be thrown.");
710        assert_eq!(input.current_row, 3);
711        assert_eq!(input.cursor_column, 0);
712
713        input.down_row().expect("No exception should be thrown.");
714        assert_eq!(input.current_row, 4);
715        assert_eq!(input.cursor_column, 0);
716
717        input.down_row().expect("No exception should be thrown.");
718        assert_eq!(input.current_row, 4, "At last line, down_row has no effect");
719        assert_eq!(input.cursor_column, 0);
720
721        input.up_row().expect("No exception should be thrown.");
722        assert_eq!(input.current_row, 3);
723        assert_eq!(
724            input.cursor_column, 0,
725            "When coming from an empty line, the cursor should be at 0th position."
726        );
727
728        let mut input2: Input = Input::default();
729        // this use case caused a bug
730        input2.append_char('a').expect("Can append char");
731        input2.append_char('\n').expect("Can append new line");
732        input2.up_row().expect("Can go up");
733        input2.down_row().expect("Can go down");
734        assert_eq!(input2.current_row, 1);
735        assert_eq!(input2.cursor_column, 0);
736        input2.append_char('b').expect("Can append char");
737        assert_eq!(input2.current_row, 1);
738        assert_eq!(input2.cursor_column, 1);
739        assert_eq!(input2.lines[0].text.get_ref(), "a\n");
740        assert_eq!(input2.lines[1].text.get_ref(), "b");
741    }
742}