Skip to content

Commit 8a43b31

Browse files
committed
auto merge of #6826 : cmr/rust/terminfo, r=thestinger
This will let *everyone* (non-windows, at least) who can see colors see the glorious colors rustc produces.
2 parents c68c015 + ae5f3de commit 8a43b31

File tree

9 files changed

+794
-81
lines changed

9 files changed

+794
-81
lines changed

src/libextra/std.rc

+2
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

+84-33
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;
@@ -39,43 +43,90 @@ pub static color_bright_magenta: u8 = 13u8;
3943
pub static color_bright_cyan: u8 = 14u8;
4044
pub static color_bright_white: u8 = 15u8;
4145

42-
pub fn esc(writer: @io::Writer) { writer.write([0x1bu8, '[' as u8]); }
46+
#[cfg(not(target_os = "win32"))]
47+
pub struct Terminal {
48+
color_supported: bool,
49+
priv out: @io::Writer,
50+
priv ti: ~TermInfo
51+
}
4352

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]);
53+
#[cfg(target_os = "win32")]
54+
pub struct Terminal {
55+
color_supported: bool,
56+
priv out: @io::Writer,
4857
}
4958

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; }
59+
#[cfg(not(target_os = "win32"))]
60+
pub impl Terminal {
61+
pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
62+
let term = os::getenv("TERM");
63+
if term.is_none() {
64+
return Err(~"TERM environment variable undefined");
65+
}
66+
67+
let entry = open(term.unwrap());
68+
if entry.is_err() {
69+
return Err(entry.get_err());
70+
}
71+
72+
let ti = parse(entry.get(), false);
73+
if ti.is_err() {
74+
return Err(entry.get_err());
75+
}
76+
77+
let mut inf = ti.get();
78+
let cs = *inf.numbers.find_or_insert(~"colors", 0) >= 16
79+
&& inf.strings.find(&~"setaf").is_some()
80+
&& inf.strings.find_equiv(&("setab")).is_some();
81+
82+
return Ok(Terminal {out: out, ti: inf, color_supported: cs});
83+
}
84+
fn fg(&self, color: u8) {
85+
if self.color_supported {
86+
let s = expand(*self.ti.strings.find_equiv(&("setaf")).unwrap(),
87+
[Number(color as int)], [], []);
88+
if s.is_ok() {
89+
self.out.write(s.get());
90+
} else {
91+
warn!(s.get_err());
5892
}
59-
false
60-
}
61-
option::None => false
62-
};
93+
}
94+
}
95+
fn bg(&self, color: u8) {
96+
if self.color_supported {
97+
let s = expand(*self.ti.strings.find_equiv(&("setab")).unwrap(),
98+
[Number(color as int)], [], []);
99+
if s.is_ok() {
100+
self.out.write(s.get());
101+
} else {
102+
warn!(s.get_err());
103+
}
104+
}
105+
}
106+
fn reset(&self) {
107+
if self.color_supported {
108+
let s = expand(*self.ti.strings.find_equiv(&("op")).unwrap(), [], [], []);
109+
if s.is_ok() {
110+
self.out.write(s.get());
111+
} else {
112+
warn!(s.get_err());
113+
}
114+
}
115+
}
63116
}
64117

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-
}
118+
#[cfg(target_os = "win32")]
119+
pub impl Terminal {
120+
pub fn new(out: @io::Writer) -> Result<Terminal, ~str> {
121+
return Ok(Terminal {out: out, color_supported: false});
122+
}
72123

73-
/// Set the foreground color
74-
pub fn fg(writer: @io::Writer, color: u8) {
75-
return set_color(writer, '3' as u8, color);
76-
}
124+
fn fg(&self, color: u8) {
125+
}
126+
127+
fn bg(&self, color: u8) {
128+
}
77129

78-
/// Set the background color
79-
pub fn bg(writer: @io::Writer, color: u8) {
80-
return set_color(writer, '4' as u8, color);
130+
fn reset(&self) {
131+
}
81132
}

src/libextra/terminfo/parm.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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+
/// Types of parameters a capability can use
31+
pub enum Param {
32+
String(~str),
33+
Char(char),
34+
Number(int)
35+
}
36+
37+
/**
38+
Expand a parameterized capability
39+
40+
# Arguments
41+
* `cap` - string to expand
42+
* `params` - vector of params for %p1 etc
43+
* `sta` - vector of params corresponding to static variables
44+
* `dyn` - vector of params corresponding to stativ variables
45+
46+
To be compatible with ncurses, `sta` and `dyn` should be the same between calls to `expand` for
47+
multiple capabilities for the same terminal.
48+
*/
49+
pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param])
50+
-> Result<~[u8], ~str> {
51+
assert!(cap.len() != 0, "expanding an empty capability makes no sense");
52+
assert!(params.len() <= 9, "only 9 parameters are supported by capability strings");
53+
54+
assert!(sta.len() <= 26, "only 26 static vars are able to be used by capability strings");
55+
assert!(dyn.len() <= 26, "only 26 dynamic vars are able to be used by capability strings");
56+
57+
let mut state = Nothing;
58+
let mut i = 0;
59+
60+
// expanded cap will only rarely be smaller than the cap itself
61+
let mut output = vec::with_capacity(cap.len());
62+
63+
let mut cur;
64+
65+
let mut stack: ~[Param] = ~[];
66+
67+
let mut intstate = ~[];
68+
69+
while i < cap.len() {
70+
cur = cap[i] as char;
71+
let mut old_state = state;
72+
match state {
73+
Nothing => {
74+
if cur == '%' {
75+
state = Percent;
76+
} else {
77+
output.push(cap[i]);
78+
}
79+
},
80+
Percent => {
81+
match cur {
82+
'%' => { output.push(cap[i]); state = Nothing },
83+
'c' => match stack.pop() {
84+
Char(c) => output.push(c as u8),
85+
_ => return Err(~"a non-char was used with %c")
86+
},
87+
's' => match stack.pop() {
88+
String(s) => output.push_all(s.to_bytes()),
89+
_ => return Err(~"a non-str was used with %s")
90+
},
91+
'd' => match stack.pop() {
92+
Number(x) => output.push_all(x.to_str().to_bytes()),
93+
_ => return Err(~"a non-number was used with %d")
94+
},
95+
'p' => state = PushParam,
96+
'P' => state = SetVar,
97+
'g' => state = GetVar,
98+
'\'' => state = CharConstant,
99+
'{' => state = IntConstant,
100+
'l' => match stack.pop() {
101+
String(s) => stack.push(Number(s.len() as int)),
102+
_ => return Err(~"a non-str was used with %l")
103+
},
104+
'+' => match (stack.pop(), stack.pop()) {
105+
(Number(x), Number(y)) => stack.push(Number(x + y)),
106+
(_, _) => return Err(~"non-numbers on stack with +")
107+
},
108+
'-' => match (stack.pop(), stack.pop()) {
109+
(Number(x), Number(y)) => stack.push(Number(x - y)),
110+
(_, _) => return Err(~"non-numbers on stack with -")
111+
},
112+
'*' => match (stack.pop(), stack.pop()) {
113+
(Number(x), Number(y)) => stack.push(Number(x * y)),
114+
(_, _) => return Err(~"non-numbers on stack with *")
115+
},
116+
'/' => match (stack.pop(), stack.pop()) {
117+
(Number(x), Number(y)) => stack.push(Number(x / y)),
118+
(_, _) => return Err(~"non-numbers on stack with /")
119+
},
120+
'm' => match (stack.pop(), stack.pop()) {
121+
(Number(x), Number(y)) => stack.push(Number(x % y)),
122+
(_, _) => return Err(~"non-numbers on stack with %")
123+
},
124+
'&' => match (stack.pop(), stack.pop()) {
125+
(Number(x), Number(y)) => stack.push(Number(x & y)),
126+
(_, _) => return Err(~"non-numbers on stack with &")
127+
},
128+
'|' => match (stack.pop(), stack.pop()) {
129+
(Number(x), Number(y)) => stack.push(Number(x | y)),
130+
(_, _) => return Err(~"non-numbers on stack with |")
131+
},
132+
'A' => return Err(~"logical operations unimplemented"),
133+
'O' => return Err(~"logical operations unimplemented"),
134+
'!' => return Err(~"logical operations unimplemented"),
135+
'~' => match stack.pop() {
136+
Number(x) => stack.push(Number(!x)),
137+
_ => return Err(~"non-number on stack with %~")
138+
},
139+
'i' => match (copy params[0], copy params[1]) {
140+
(Number(x), Number(y)) => {
141+
params[0] = Number(x + 1);
142+
params[1] = Number(y + 1);
143+
},
144+
(_, _) => return Err(~"first two params not numbers with %i")
145+
},
146+
'?' => state = return Err(fmt!("if expressions unimplemented (%?)", cap)),
147+
_ => return Err(fmt!("unrecognized format option %c", cur))
148+
}
149+
},
150+
PushParam => {
151+
// params are 1-indexed
152+
stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]);
153+
},
154+
SetVar => {
155+
if cur >= 'A' && cur <= 'Z' {
156+
let idx = (cur as u8) - ('A' as u8);
157+
sta[idx] = stack.pop();
158+
} else if cur >= 'a' && cur <= 'z' {
159+
let idx = (cur as u8) - ('a' as u8);
160+
dyn[idx] = stack.pop();
161+
} else {
162+
return Err(~"bad variable name in %P");
163+
}
164+
},
165+
GetVar => {
166+
if cur >= 'A' && cur <= 'Z' {
167+
let idx = (cur as u8) - ('A' as u8);
168+
stack.push(copy sta[idx]);
169+
} else if cur >= 'a' && cur <= 'z' {
170+
let idx = (cur as u8) - ('a' as u8);
171+
stack.push(copy dyn[idx]);
172+
} else {
173+
return Err(~"bad variable name in %g");
174+
}
175+
},
176+
CharConstant => {
177+
stack.push(Char(cur));
178+
state = CharClose;
179+
},
180+
CharClose => {
181+
assert!(cur == '\'', "malformed character constant");
182+
},
183+
IntConstant => {
184+
if cur == '}' {
185+
stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant")));
186+
state = Nothing;
187+
}
188+
intstate.push(cur as u8);
189+
old_state = Nothing;
190+
}
191+
_ => return Err(~"unimplemented state")
192+
}
193+
if state == old_state {
194+
state = Nothing;
195+
}
196+
i += 1;
197+
}
198+
Ok(output)
199+
}
200+
201+
#[cfg(test)]
202+
mod test {
203+
use super::*;
204+
#[test]
205+
fn test_basic_setabf() {
206+
let s = bytes!("\\E[48;5;%p1%dm");
207+
assert_eq!(expand(s, [Number(1)], [], []).unwrap(), bytes!("\\E[48;5;1m").to_owned());
208+
}
209+
}

0 commit comments

Comments
 (0)