-
Notifications
You must be signed in to change notification settings - Fork 13.6k
extra::term overhaul #6826
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+794
−81
Merged
extra::term overhaul #6826
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cf64324
extra::term overhaul
emberian 7281166
Use find_equiv in term
emberian 1f27c63
rustpkg borrowed pointers
emberian 100ee84
Only output colors if colors are supported (removes burden from caller)
emberian 5311d59
extra::term: better error handling and win32 compat
emberian 11f31b9
Fix formatting for tidy
emberian 023861c
test fixes
emberian ae5f3de
Ignore tests that cannot pass on buildbot
emberian File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
//! Parameterized string expansion | ||
use core::prelude::*; | ||
use core::{char, int, vec}; | ||
|
||
#[deriving(Eq)] | ||
enum States { | ||
Nothing, | ||
Percent, | ||
SetVar, | ||
GetVar, | ||
PushParam, | ||
CharConstant, | ||
CharClose, | ||
IntConstant, | ||
IfCond, | ||
IfBody | ||
} | ||
|
||
/// Types of parameters a capability can use | ||
pub enum Param { | ||
String(~str), | ||
Char(char), | ||
Number(int) | ||
} | ||
|
||
/** | ||
Expand a parameterized capability | ||
# Arguments | ||
* `cap` - string to expand | ||
* `params` - vector of params for %p1 etc | ||
* `sta` - vector of params corresponding to static variables | ||
* `dyn` - vector of params corresponding to stativ variables | ||
To be compatible with ncurses, `sta` and `dyn` should be the same between calls to `expand` for | ||
multiple capabilities for the same terminal. | ||
*/ | ||
pub fn expand(cap: &[u8], params: &mut [Param], sta: &mut [Param], dyn: &mut [Param]) | ||
-> Result<~[u8], ~str> { | ||
assert!(cap.len() != 0, "expanding an empty capability makes no sense"); | ||
assert!(params.len() <= 9, "only 9 parameters are supported by capability strings"); | ||
|
||
assert!(sta.len() <= 26, "only 26 static vars are able to be used by capability strings"); | ||
assert!(dyn.len() <= 26, "only 26 dynamic vars are able to be used by capability strings"); | ||
|
||
let mut state = Nothing; | ||
let mut i = 0; | ||
|
||
// expanded cap will only rarely be smaller than the cap itself | ||
let mut output = vec::with_capacity(cap.len()); | ||
|
||
let mut cur; | ||
|
||
let mut stack: ~[Param] = ~[]; | ||
|
||
let mut intstate = ~[]; | ||
|
||
while i < cap.len() { | ||
cur = cap[i] as char; | ||
let mut old_state = state; | ||
match state { | ||
Nothing => { | ||
if cur == '%' { | ||
state = Percent; | ||
} else { | ||
output.push(cap[i]); | ||
} | ||
}, | ||
Percent => { | ||
match cur { | ||
'%' => { output.push(cap[i]); state = Nothing }, | ||
'c' => match stack.pop() { | ||
Char(c) => output.push(c as u8), | ||
_ => return Err(~"a non-char was used with %c") | ||
}, | ||
's' => match stack.pop() { | ||
String(s) => output.push_all(s.to_bytes()), | ||
_ => return Err(~"a non-str was used with %s") | ||
}, | ||
'd' => match stack.pop() { | ||
Number(x) => output.push_all(x.to_str().to_bytes()), | ||
_ => return Err(~"a non-number was used with %d") | ||
}, | ||
'p' => state = PushParam, | ||
'P' => state = SetVar, | ||
'g' => state = GetVar, | ||
'\'' => state = CharConstant, | ||
'{' => state = IntConstant, | ||
'l' => match stack.pop() { | ||
String(s) => stack.push(Number(s.len() as int)), | ||
_ => return Err(~"a non-str was used with %l") | ||
}, | ||
'+' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x + y)), | ||
(_, _) => return Err(~"non-numbers on stack with +") | ||
}, | ||
'-' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x - y)), | ||
(_, _) => return Err(~"non-numbers on stack with -") | ||
}, | ||
'*' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x * y)), | ||
(_, _) => return Err(~"non-numbers on stack with *") | ||
}, | ||
'/' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x / y)), | ||
(_, _) => return Err(~"non-numbers on stack with /") | ||
}, | ||
'm' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x % y)), | ||
(_, _) => return Err(~"non-numbers on stack with %") | ||
}, | ||
'&' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x & y)), | ||
(_, _) => return Err(~"non-numbers on stack with &") | ||
}, | ||
'|' => match (stack.pop(), stack.pop()) { | ||
(Number(x), Number(y)) => stack.push(Number(x | y)), | ||
(_, _) => return Err(~"non-numbers on stack with |") | ||
}, | ||
'A' => return Err(~"logical operations unimplemented"), | ||
'O' => return Err(~"logical operations unimplemented"), | ||
'!' => return Err(~"logical operations unimplemented"), | ||
'~' => match stack.pop() { | ||
Number(x) => stack.push(Number(!x)), | ||
_ => return Err(~"non-number on stack with %~") | ||
}, | ||
'i' => match (copy params[0], copy params[1]) { | ||
(Number(x), Number(y)) => { | ||
params[0] = Number(x + 1); | ||
params[1] = Number(y + 1); | ||
}, | ||
(_, _) => return Err(~"first two params not numbers with %i") | ||
}, | ||
'?' => state = return Err(fmt!("if expressions unimplemented (%?)", cap)), | ||
_ => return Err(fmt!("unrecognized format option %c", cur)) | ||
} | ||
}, | ||
PushParam => { | ||
// params are 1-indexed | ||
stack.push(copy params[char::to_digit(cur, 10).expect("bad param number") - 1]); | ||
}, | ||
SetVar => { | ||
if cur >= 'A' && cur <= 'Z' { | ||
let idx = (cur as u8) - ('A' as u8); | ||
sta[idx] = stack.pop(); | ||
} else if cur >= 'a' && cur <= 'z' { | ||
let idx = (cur as u8) - ('a' as u8); | ||
dyn[idx] = stack.pop(); | ||
} else { | ||
return Err(~"bad variable name in %P"); | ||
} | ||
}, | ||
GetVar => { | ||
if cur >= 'A' && cur <= 'Z' { | ||
let idx = (cur as u8) - ('A' as u8); | ||
stack.push(copy sta[idx]); | ||
} else if cur >= 'a' && cur <= 'z' { | ||
let idx = (cur as u8) - ('a' as u8); | ||
stack.push(copy dyn[idx]); | ||
} else { | ||
return Err(~"bad variable name in %g"); | ||
} | ||
}, | ||
CharConstant => { | ||
stack.push(Char(cur)); | ||
state = CharClose; | ||
}, | ||
CharClose => { | ||
assert!(cur == '\'', "malformed character constant"); | ||
}, | ||
IntConstant => { | ||
if cur == '}' { | ||
stack.push(Number(int::parse_bytes(intstate, 10).expect("bad int constant"))); | ||
state = Nothing; | ||
} | ||
intstate.push(cur as u8); | ||
old_state = Nothing; | ||
} | ||
_ => return Err(~"unimplemented state") | ||
} | ||
if state == old_state { | ||
state = Nothing; | ||
} | ||
i += 1; | ||
} | ||
Ok(output) | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
#[test] | ||
fn test_basic_setabf() { | ||
let s = bytes!("\\E[48;5;%p1%dm"); | ||
assert_eq!(expand(s, [Number(1)], [], []).unwrap(), bytes!("\\E[48;5;1m").to_owned()); | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
/// Implement ncurses-compatible database discovery | ||
/// Does not support hashed database, only filesystem! | ||
use core::prelude::*; | ||
use core::{os, str}; | ||
use core::os::getenv; | ||
use core::io::{file_reader, Reader}; | ||
use path = core::path::Path; | ||
|
||
/// Return path to database entry for `term` | ||
pub fn get_dbpath_for_term(term: &str) -> Option<~path> { | ||
if term.len() == 0 { | ||
return None; | ||
} | ||
|
||
let homedir = os::homedir(); | ||
|
||
let mut dirs_to_search = ~[]; | ||
let first_char = term.substr(0, 1); | ||
|
||
// Find search directory | ||
match getenv("TERMINFO") { | ||
Some(dir) => dirs_to_search.push(path(dir)), | ||
None => { | ||
if homedir.is_some() { | ||
dirs_to_search.push(homedir.unwrap().push(".terminfo")); // ncurses compatability | ||
} | ||
match getenv("TERMINFO_DIRS") { | ||
Some(dirs) => for str::each_split_char(dirs, ':') |i| { | ||
if i == "" { | ||
dirs_to_search.push(path("/usr/share/terminfo")); | ||
} else { | ||
dirs_to_search.push(path(i.to_owned())); | ||
} | ||
}, | ||
// Found nothing, use the default path | ||
None => dirs_to_search.push(path("/usr/share/terminfo")) | ||
} | ||
} | ||
}; | ||
|
||
// Look for the terminal in all of the search directories | ||
for dirs_to_search.each |p| { | ||
let newp = ~p.push_many(&[first_char.to_owned(), term.to_owned()]); | ||
if os::path_exists(p) && os::path_exists(newp) { | ||
return Some(newp); | ||
} | ||
} | ||
None | ||
} | ||
|
||
/// Return open file for `term` | ||
pub fn open(term: &str) -> Result<@Reader, ~str> { | ||
match get_dbpath_for_term(term) { | ||
Some(x) => file_reader(x), | ||
None => Err(fmt!("could not find terminfo entry for %s", term)) | ||
} | ||
} | ||
|
||
#[test] | ||
#[ignore(reason = "buildbots don't have ncurses installed and I can't mock everything I need")] | ||
fn test_get_dbpath_for_term() { | ||
// woefully inadequate test coverage | ||
use std::os::{setenv, unsetenv}; | ||
fn x(t: &str) -> ~str { get_dbpath_for_term(t).expect("no terminfo entry found").to_str() }; | ||
assert!(x("screen") == ~"/usr/share/terminfo/s/screen"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this is a test, so it doesn't matter, but |
||
assert!(get_dbpath_for_term("") == None); | ||
setenv("TERMINFO_DIRS", ":"); | ||
assert!(x("screen") == ~"/usr/share/terminfo/s/screen"); | ||
unsetenv("TERMINFO_DIRS"); | ||
} | ||
#[test] | ||
#[ignore(reason = "see test_get_dbpath_for_term")] | ||
fn test_open() { | ||
open("screen"); | ||
let t = open("nonexistent terminal that hopefully does not exist"); | ||
assert!(t.is_err()); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT | ||
// file at the top-level directory of this distribution and at | ||
// http://rust-lang.org/COPYRIGHT. | ||
// | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use core::hashmap::HashMap; | ||
|
||
/// A parsed terminfo entry. | ||
pub struct TermInfo { | ||
/// Names for the terminal | ||
names: ~[~str], | ||
/// Map of capability name to boolean value | ||
bools: HashMap<~str, bool>, | ||
/// Map of capability name to numeric value | ||
numbers: HashMap<~str, u16>, | ||
/// Map of capability name to raw (unexpanded) string | ||
strings: HashMap<~str, ~[u8]> | ||
} | ||
|
||
pub mod searcher; | ||
pub mod parser { | ||
pub mod compiled; | ||
} | ||
pub mod parm; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you have to allocate here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returns
Option<~path>
, notOption<path>