Skip to content

Commit cf64324

Browse files
committed
extra::term overhaul
1 parent 55c23bc commit cf64324

File tree

9 files changed

+715
-81
lines changed

9 files changed

+715
-81
lines changed

src/libextra/std.rc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ pub mod flate;
118118
#[cfg(unicode)]
119119
mod unicode;
120120

121+
#[path="terminfo/terminfo.rs"]
122+
pub mod terminfo;
121123

122124
// Compiler support modules
123125

src/libextra/term.rs

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
1+
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
22
// file at the top-level directory of this distribution and at
33
// http://rust-lang.org/COPYRIGHT.
44
//
@@ -15,9 +15,13 @@
1515
use core::prelude::*;
1616

1717
use core::io;
18-
use core::option;
1918
use core::os;
2019

20+
use terminfo::*;
21+
use terminfo::searcher::open;
22+
use terminfo::parser::compiled::parse;
23+
use terminfo::parm::{expand, Number};
24+
2125
// FIXME (#2807): Windows support.
2226

2327
pub static color_black: u8 = 0u8;
@@ -41,41 +45,42 @@ pub static color_bright_white: u8 = 15u8;
4145

4246
pub fn esc(writer: @io::Writer) { writer.write([0x1bu8, '[' as u8]); }
4347

44-
/// Reset the foreground and background colors to default
45-
pub fn reset(writer: @io::Writer) {
46-
esc(writer);
47-
writer.write(['0' as u8, 'm' as u8]);
48+
pub struct Terminal {
49+
color_supported: bool,
50+
priv out: @io::Writer,
51+
priv ti: ~TermInfo
4852
}
4953

50-
/// Returns true if the terminal supports color
51-
pub fn color_supported() -> bool {
52-
let supported_terms = ~[~"xterm-color", ~"xterm",
53-
~"screen-bce", ~"xterm-256color"];
54-
return match os::getenv("TERM") {
55-
option::Some(ref env) => {
56-
for supported_terms.each |term| {
57-
if *term == *env { return true; }
58-
}
59-
false
60-
}
61-
option::None => false
62-
};
63-
}
54+
pub impl Terminal {
55+
pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
56+
let term = os::getenv("TERM");
57+
if term.is_none() {
58+
return Err(~"TERM environment variable undefined");
59+
}
6460

65-
pub fn set_color(writer: @io::Writer, first_char: u8, color: u8) {
66-
assert!((color < 16u8));
67-
esc(writer);
68-
let mut color = color;
69-
if color >= 8u8 { writer.write(['1' as u8, ';' as u8]); color -= 8u8; }
70-
writer.write([first_char, ('0' as u8) + color, 'm' as u8]);
71-
}
61+
let entry = open(term.unwrap());
62+
if entry.is_err() {
63+
return Err(entry.get_err());
64+
}
7265

73-
/// Set the foreground color
74-
pub fn fg(writer: @io::Writer, color: u8) {
75-
return set_color(writer, '3' as u8, color);
76-
}
66+
let ti = parse(entry.get(), false);
67+
if ti.is_err() {
68+
return Err(entry.get_err());
69+
}
70+
71+
let mut inf = ti.get();
72+
let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16 && inf.strings.find(&~"setaf").is_some()
73+
&& inf.strings.find(&~"setab").is_some();
7774

78-
/// Set the background color
79-
pub fn bg(writer: @io::Writer, color: u8) {
80-
return set_color(writer, '4' as u8, color);
75+
return Ok(Terminal {out: out, ti: inf, color_supported: cs});
76+
}
77+
fn fg(&self, color: u8) {
78+
self.out.write(expand(*self.ti.strings.find(&~"setaf").unwrap(), [Number(color as int)], [], []));
79+
}
80+
fn bg(&self, color: u8) {
81+
self.out.write(expand(*self.ti.strings.find(&~"setab").unwrap(), [Number(color as int)], [], []));
82+
}
83+
fn reset(&self) {
84+
self.out.write(expand(*self.ti.strings.find(&~"op").unwrap(), [], [], []));
85+
}
8186
}

src/libextra/terminfo/parm.rs

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Parameterized string expansion
12+
13+
use core::prelude::*;
14+
use core::{char, int, vec};
15+
16+
#[deriving(Eq)]
17+
enum States {
18+
Nothing,
19+
Percent,
20+
SetVar,
21+
GetVar,
22+
PushParam,
23+
CharConstant,
24+
CharClose,
25+
IntConstant,
26+
IfCond,
27+
IfBody
28+
}
29+
30+
pub enum Param {
31+
String(~str),
32+
Char(char),
33+
Number(int)
34+
}
35+
36+
pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) -> ~[u8] {
37+
assert!(cap.len() != 0, "expanding an empty capability makes no sense");
38+
assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
39+
40+
assert!(sta.len() <= 26, "only 26 static vars are able to be used by capability strings");
41+
assert!(dyn.len() <= 26, "only 26 dynamic vars are able to be used by capability strings");
42+
43+
let mut state = Nothing;
44+
let mut i = 0;
45+
46+
// expanded cap will only rarely be smaller than the cap itself
47+
let mut output = vec::with_capacity(cap.len());
48+
49+
let mut cur;
50+
51+
let mut stack: ~[Param] = ~[];
52+
53+
let mut intstate = ~[];
54+
55+
while i < cap.len() {
56+
cur = cap[i] as char;
57+
let mut old_state = state;
58+
match state {
59+
Nothing => {
60+
if cur == '%' {
61+
state = Percent;
62+
} else {
63+
output.push(cap[i]);
64+
}
65+
},
66+
Percent => {
67+
match cur {
68+
'%' => { output.push(cap[i]); state = Nothing },
69+
'c' => match stack.pop() {
70+
Char(c) => output.push(c as u8),
71+
_ => fail!("a non-char was used with %c")
72+
},
73+
's' => match stack.pop() {
74+
String(s) => output.push_all(s.to_bytes()),
75+
_ => fail!("a non-str was used with %s")
76+
},
77+
'd' => match stack.pop() {
78+
Number(x) => output.push_all(x.to_str().to_bytes()),
79+
_ => fail!("a non-number was used with %d")
80+
},
81+
'p' => state = PushParam,
82+
'P' => state = SetVar,
83+
'g' => state = GetVar,
84+
'\'' => state = CharConstant,
85+
'{' => state = IntConstant,
86+
'l' => match stack.pop() {
87+
String(s) => stack.push(Number(s.len() as int)),
88+
_ => fail!("a non-str was used with %l")
89+
},
90+
'+' => match (stack.pop(), stack.pop()) {
91+
(Number(x), Number(y)) => stack.push(Number(x + y)),
92+
(_, _) => fail!("non-numbers on stack with +")
93+
},
94+
'-' => match (stack.pop(), stack.pop()) {
95+
(Number(x), Number(y)) => stack.push(Number(x - y)),
96+
(_, _) => fail!("non-numbers on stack with -")
97+
},
98+
'*' => match (stack.pop(), stack.pop()) {
99+
(Number(x), Number(y)) => stack.push(Number(x * y)),
100+
(_, _) => fail!("non-numbers on stack with *")
101+
},
102+
'/' => match (stack.pop(), stack.pop()) {
103+
(Number(x), Number(y)) => stack.push(Number(x / y)),
104+
(_, _) => fail!("non-numbers on stack with /")
105+
},
106+
'm' => match (stack.pop(), stack.pop()) {
107+
(Number(x), Number(y)) => stack.push(Number(x % y)),
108+
(_, _) => fail!("non-numbers on stack with %")
109+
},
110+
'&' => match (stack.pop(), stack.pop()) {
111+
(Number(x), Number(y)) => stack.push(Number(x & y)),
112+
(_, _) => fail!("non-numbers on stack with &")
113+
},
114+
'|' => match (stack.pop(), stack.pop()) {
115+
(Number(x), Number(y)) => stack.push(Number(x | y)),
116+
(_, _) => fail!("non-numbers on stack with |")
117+
},
118+
'A' => fail!("logical operations unimplemented"),
119+
'O' => fail!("logical operations unimplemented"),
120+
'!' => fail!("logical operations unimplemented"),
121+
'~' => match stack.pop() {
122+
Number(x) => stack.push(Number(!x)),
123+
_ => fail!("non-number on stack with %~")
124+
},
125+
'i' => match (copy params[0], copy params[1]) {
126+
(Number(x), Number(y)) => {
127+
params[0] = Number(x + 1);
128+
params[1] = Number(y + 1);
129+
},
130+
(_, _) => fail!("first two params not numbers with %i")
131+
},
132+
'?' => state = fail!("if expressions unimplemented"),
133+
_ => fail!("unrecognized format option %c", cur)
134+
}
135+
},
136+
PushParam => {
137+
// params are 1-indexed
138+
stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]);
139+
},
140+
SetVar => {
141+
if cur >= 'A' && cur <= 'Z' {
142+
let idx = (cur as u8) - ('A' as u8);
143+
sta[idx] = stack.pop();
144+
} else if cur >= 'a' && cur <= 'z' {
145+
let idx = (cur as u8) - ('a' as u8);
146+
dyn[idx] = stack.pop();
147+
} else {
148+
fail!("bad variable name in %P");
149+
}
150+
},
151+
GetVar => {
152+
if cur >= 'A' && cur <= 'Z' {
153+
let idx = (cur as u8) - ('A' as u8);
154+
stack.push(copy sta[idx]);
155+
} else if cur >= 'a' && cur <= 'z' {
156+
let idx = (cur as u8) - ('a' as u8);
157+
stack.push(copy dyn[idx]);
158+
} else {
159+
fail!("bad variable name in %g");
160+
}
161+
},
162+
CharConstant => {
163+
stack.push(Char(cur));
164+
state = CharClose;
165+
},
166+
CharClose => {
167+
assert!(cur == '\'', "malformed character constant");
168+
},
169+
IntConstant => {
170+
if cur == '}' {
171+
stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant")));
172+
state = Nothing;
173+
}
174+
intstate.push(cur as u8);
175+
old_state = Nothing;
176+
}
177+
_ => fail!("unimplemented state")
178+
}
179+
if state == old_state {
180+
state = Nothing;
181+
}
182+
i += 1;
183+
}
184+
output
185+
}
186+
187+
#[cfg(test)]
188+
mod test {
189+
use super::*;
190+
#[test]
191+
fn test_basic_setabf() {
192+
let s = bytes!("\\E[48;5;%p1%dm");
193+
assert_eq!(expand(s, [Number(1)], [], []), bytes!("\\E[48;5;1m").to_owned());
194+
}
195+
}

0 commit comments

Comments
 (0)