1 /*
2     Copyright © 2013 Free Software Foundation, Inc
3     See licensing in LICENSE file
4 
5     File: examples/ex_6.rs
6     Author: Jesse 'Jeaye' Wilkerson
7     Description:
8       Extension of ex_3 that adds naive syntax
9       highlighting to showcase attributes.
10 
11       Usage:
12         ./bin/ex_6 <rust file>
13       Example:
14         ./bin/ex_6 examples/ex_6.rs
15 */
16 
17 extern crate ncurses;
18 
19 use std::{ char, env, fs };
20 use std::path::Path;
21 use std::io::{Read, Bytes};
22 use std::iter::Peekable;
23 use ncurses::*;
24 
25 /* Individual color handles. */
26 static COLOR_BACKGROUND: i16 = 16;
27 static COLOR_FOREGROUND: i16 = 17;
28 static COLOR_KEYWORD: i16 = 18;
29 static COLOR_TYPE: i16 = 19;
30 static COLOR_STORAGE: i16 = 20;
31 static COLOR_COMMENT: i16 = 21;
32 static COLOR_STRING: i16 = 22;
33 static COLOR_CHAR: i16 = 23;
34 static COLOR_NUMBER: i16 = 24;
35 
36 /* Color pairs; foreground && background. */
37 static COLOR_PAIR_DEFAULT: i16 = 1;
38 static COLOR_PAIR_KEYWORD: i16 = 2;
39 static COLOR_PAIR_TYPE: i16 = 3;
40 static COLOR_PAIR_STORAGE: i16 = 4;
41 static COLOR_PAIR_COMMENT: i16 = 5;
42 static COLOR_PAIR_STRING: i16 = 6;
43 static COLOR_PAIR_CHAR: i16 = 7;
44 static COLOR_PAIR_NUMBER: i16 = 8;
45 
46 /* Word delimiters. */
47 static WORD_LIMITS: &'static [u8] = &
48 [
49   ' ' as u8,
50   '(' as u8,
51   ')' as u8,
52   ':' as u8,
53   ';' as u8,
54   '&' as u8,
55   '+' as u8,
56   '-' as u8,
57   ',' as u8,
58   '.' as u8,
59   '@' as u8,
60   '~' as u8,
61   '\\' as u8,
62   '\n' as u8,
63   '\r' as u8,
64   '\0' as u8,
65   !0 as u8,
66 ];
67 
68 struct Pager
69 {
70   file_reader: Peekable<Bytes<fs::File>>,
71 
72   in_comment: bool,
73   in_string: bool,
74   in_char: bool,
75 
76   screen_width: i32,
77   screen_height: i32,
78   curr_x: i32,
79   curr_y: i32,
80 }
81 
82 impl Pager
83 {
new() -> Pager84   pub fn new() -> Pager
85   {
86     Pager
87     {
88       file_reader: open_file().bytes().peekable(),
89 
90       in_comment: false,
91       in_string: false,
92       in_char: false,
93 
94       screen_width: 0,
95       screen_height: 0,
96       curr_x: 0,
97       curr_y: 0,
98     }
99   }
100 
initialize(&mut self)101   pub fn initialize(&mut self)
102   {
103     /* Start ncurses. */
104     initscr();
105     keypad(stdscr(), true);
106     noecho();
107 
108     /* Start colors. */
109     start_color();
110     init_color(COLOR_BACKGROUND, 0, 43 * 4, 54 * 4);
111     init_color(COLOR_FOREGROUND, 142 * 4, 161 * 4, 161 * 4);
112     init_color(COLOR_KEYWORD, 130 * 4, 151 * 4, 0);
113     init_color(COLOR_TYPE, 197 * 4, 73 * 4, 27 * 4);
114     init_color(COLOR_STORAGE, 219 * 4, 51 * 4, 47 * 4);
115     init_color(COLOR_COMMENT, 33 * 4, 138 * 4, 206 * 4);
116     init_color(COLOR_STRING, 34 * 4, 154 * 4, 142 * 4);
117     init_color(COLOR_CHAR, 34 * 4, 154 * 4, 142 * 4);
118     init_color(COLOR_NUMBER, 236 * 4, 107 * 4, 83 * 4);
119 
120     init_pair(COLOR_PAIR_DEFAULT, COLOR_FOREGROUND, COLOR_BACKGROUND);
121     init_pair(COLOR_PAIR_KEYWORD, COLOR_KEYWORD, COLOR_BACKGROUND);
122     init_pair(COLOR_PAIR_TYPE, COLOR_TYPE, COLOR_BACKGROUND);
123     init_pair(COLOR_PAIR_STORAGE, COLOR_STORAGE, COLOR_BACKGROUND);
124     init_pair(COLOR_PAIR_COMMENT, COLOR_COMMENT, COLOR_BACKGROUND);
125     init_pair(COLOR_PAIR_STRING, COLOR_STRING, COLOR_BACKGROUND);
126     init_pair(COLOR_PAIR_CHAR, COLOR_CHAR, COLOR_BACKGROUND);
127     init_pair(COLOR_PAIR_NUMBER, COLOR_NUMBER, COLOR_BACKGROUND);
128 
129     /* Set the window's background color. */
130     bkgd(' ' as chtype | COLOR_PAIR(COLOR_PAIR_DEFAULT) as chtype);
131 
132     /* Get the screen bounds. */
133     getmaxyx(stdscr(), &mut self.screen_height, &mut self.screen_width);
134   }
135 
136   /* Returns the word and delimiter following it. */
read_word(&mut self) -> (String, char)137   pub fn read_word(&mut self) -> (String, char)
138   {
139     let mut s: Vec<u8> = vec![];
140     let mut ch : u8 = self.file_reader.next().unwrap().ok().expect("Unable to read byte");
141 
142     /* Read until we hit a word delimiter. */
143     while !WORD_LIMITS.contains(&ch)
144     {
145       s.push(ch);
146       ch = self.file_reader.next().unwrap().ok().expect("Unable to read byte");
147     }
148 
149     /* Return the word string and the terminating delimiter. */
150     match char::from_u32(ch as u32)
151     {
152       Some(ch) => (String::from_utf8(s).ok().expect("utf-8 conversion failed"), ch),
153       None => (String::from_utf8(s).ok().expect("utf-8 conversion failed"), ' '),
154     }
155   }
156 
157   /* Retuns the attribute the given word requires. */
highlight_word(&mut self, word: &str) -> attr_t158   pub fn highlight_word(&mut self, word: &str) -> attr_t
159   {
160     /* Comments. */
161     if self.in_comment && !word.contains("*/")
162     { return COLOR_PAIR(COLOR_PAIR_COMMENT); }
163     else if self.in_comment && word.contains("*/")
164     {
165       self.in_comment = false;
166       return COLOR_PAIR(COLOR_PAIR_COMMENT);
167     }
168     else if !self.in_comment && word.contains("/*")
169     {
170       self.in_comment = true;
171       return COLOR_PAIR(COLOR_PAIR_COMMENT);
172     }
173 
174     /* Strings. */
175     if !self.in_char
176     {
177       if self.in_string && !word.contains("\"")
178       { return COLOR_PAIR(COLOR_PAIR_STRING); }
179       else if self.in_string && word.contains("\"")
180       {
181         self.in_string = false;
182         return COLOR_PAIR(COLOR_PAIR_STRING);
183       }
184       else if !self.in_string && word.contains("\"")
185       {
186         /* If the same quote is found from either direction
187          * then it's the only quote in the string. */
188         if word.find('\"') == word.rfind('\"')
189         { self.in_string = true; }
190         return COLOR_PAIR(COLOR_PAIR_STRING);
191       }
192     }
193 
194     /* Chars. */
195     if self.in_char && !word.contains("\'")
196     { return COLOR_PAIR(COLOR_PAIR_CHAR); }
197     else if self.in_char && word.contains("\'")
198     {
199       self.in_char = false;
200       return COLOR_PAIR(COLOR_PAIR_CHAR);
201     }
202     else if !self.in_char && word.contains("\'") && !word.contains("static")
203     {
204       /* If the same quote is found from either direction
205        * then it's the only quote in the string. */
206       if word.find('\'') == word.rfind('\'')
207       { self.in_char = true; }
208       return COLOR_PAIR(COLOR_PAIR_CHAR);
209     }
210 
211     /* Trim the word of all delimiters. */
212     let word = word.trim_matches(|ch: char|
213                                  { WORD_LIMITS.contains(&(ch as u8)) });
214 
215     if word.len() == 0
216     { return 0; }
217 
218     /* If it starts with a number, it is a number. */
219     if word.as_bytes()[0] >= '0' as u8 && word.as_bytes()[0] <= '9' as u8
220     { return COLOR_PAIR(COLOR_PAIR_NUMBER); }
221 
222     match word
223     {
224       /* Key words. */
225       "break" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
226       "continue" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
227       "do" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
228       "else" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
229       "extern" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
230       "in" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
231       "if" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
232       "impl" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
233       "let" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
234       "log" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
235       "loop" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
236       "match" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
237       "once" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
238       "priv" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
239       "pub" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
240       "return" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
241       "unsafe" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
242       "while" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
243       "use" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
244       "mod" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
245       "trait" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
246       "struct" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
247       "enum" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
248       "type" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
249       "fn" => { COLOR_PAIR(COLOR_PAIR_KEYWORD) },
250 
251       /* Types. */
252       "int" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
253       "uint" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
254       "char" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
255       "bool" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
256       "u8" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
257       "u16" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
258       "u32" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
259       "u64" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
260       "i16" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
261       "i32" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
262       "i64" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
263       "f32" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
264       "f64" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
265       "str" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
266       "self" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
267       "Self" => { COLOR_PAIR(COLOR_PAIR_TYPE) },
268 
269       /* Storage. */
270       "const" => { COLOR_PAIR(COLOR_PAIR_STORAGE) },
271       "mut" => { COLOR_PAIR(COLOR_PAIR_STORAGE) },
272       "ref" => { COLOR_PAIR(COLOR_PAIR_STORAGE) },
273       "static" => { COLOR_PAIR(COLOR_PAIR_STORAGE) },
274 
275       /* Not something we need to highlight. */
276       _ => 0,
277     }
278   }
279 
280 }
281 
282 impl Drop for Pager
283 {
drop(&mut self)284   fn drop(&mut self)
285   {
286     /* Final prompt before closing. */
287     mv(self.screen_height - 1, 0);
288     prompt();
289     endwin();
290   }
291 }
292 
main()293 fn main()
294 {
295   let mut pager = Pager::new();
296   pager.initialize();
297 
298   /* Read the whole file. */
299   while pager.file_reader.peek().is_some()
300   {
301     /* Read a word at a time. */
302     let (word, leftover) = pager.read_word();
303     let attr = pager.highlight_word(word.as_ref());
304     let leftover_attr = pager.highlight_word(format!("{}", leftover).as_ref());
305 
306     /* Get the current position on the screen. */
307     getyx(stdscr(), &mut pager.curr_y, &mut pager.curr_x);
308 
309     if pager.curr_y == (pager.screen_height - 1)
310     {
311       /* Status bar at the bottom. */
312       prompt();
313 
314       /* Once a key is pressed, clear the screen and continue. */
315       clear();
316       mv(0, 0);
317     }
318     else
319     {
320       attron(attr);
321       printw(word.as_ref());
322       attroff(attr);
323 
324       attron(leftover_attr);
325       addch(leftover as chtype);
326       attroff(leftover_attr);
327     }
328   }
329 }
330 
prompt()331 fn prompt()
332 {
333   attron(A_BOLD());
334   printw("<-Press Space->");
335   while getch() != ' ' as i32
336   { }
337   attroff(A_BOLD());
338 }
339 
open_file() -> fs::File340 fn open_file() -> fs::File
341 {
342   let args : Vec<_> = env::args().collect();
343   if args.len() != 2
344   {
345     println!("Usage:\n\t{} <rust file>", args[0]);
346     println!("Example:\n\t{} examples/ex_5.rs", args[0]);
347     panic!("Exiting");
348   }
349 
350   let reader = fs::File::open(Path::new(&args[1]));
351   reader.ok().expect("Unable to open file")
352 }
353