racer 2.0.12

Code completion for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
#[macro_use] extern crate log;
extern crate env_logger;
#[macro_use] extern crate clap;

extern crate racer;

use racer::{Match, MatchType, FileCache, Session, Coordinate, Point};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::io::{self, BufRead, Read};
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};

fn point(cfg: Config) {
    let cache = FileCache::default();
    let session = Session::new(&cache);
    cfg.interface.emit(Message::Coords(cfg.coords()));
    racer::to_point(cfg.coords(), cfg.expect_file(), &session)
        .map(|point| cfg.interface.emit(Message::Point(point)));
    cfg.interface.emit(Message::End);
}

fn coord(cfg: Config) {
    let cache = FileCache::default();
    let session = Session::new(&cache);
    cfg.interface.emit(Message::Point(cfg.point));
    racer::to_coords(cfg.point, cfg.expect_file(), &session)
        .map(|coords| cfg.interface.emit(Message::Coords(coords)));
    cfg.interface.emit(Message::End);
}

fn match_with_snippet_fn(m: Match, session: &Session, interface: Interface) {
    let Coordinate { line: linenum, column: charnum } = m.coords.unwrap();
    if m.matchstr == "" {
        panic!("MATCHSTR is empty - waddup?");
    }

    let snippet = racer::snippet_for_match(&m, session);
    interface.emit(Message::MatchWithSnippet(m.matchstr, snippet, linenum, charnum,
                                             m.filepath.as_path(), m.mtype, m.contextstr, m.docs));
}

fn match_fn(m: Match, interface: Interface) {
    if let Some(coords) = m.coords {
        let Coordinate { line: linenum, column: charnum } = coords;
        interface.emit(Message::Match(m.matchstr, linenum, charnum, m.filepath.as_path(),
                                      m.mtype, m.contextstr));
    } else {
        error!("Could not resolve file coords for match {:?}", m);
    }
}

fn complete(cfg: Config, print_type: CompletePrinter) {
    if cfg.fqn.is_some() {
        return external_complete(cfg, print_type);
    }
    complete_by_line_coords(cfg, print_type);
}

fn complete_by_line_coords(cfg: Config,
                           print_type: CompletePrinter) {
    // input: linenum, colnum, fname
    let tb = std::thread::Builder::new().name("searcher".to_owned());
    let interface = cfg.interface;

    // PD: this probably sucks for performance, but lots of plugins
    // end up failing and leaving tmp files around if racer crashes,
    // so catch the crash.
    let res = tb.spawn(move || {
        run_the_complete_fn(&cfg, print_type);
    }).unwrap();
    if let Err(e) = res.join() {
        error!("Search thread paniced: {:?}", e);
    }

    interface.emit(Message::End);
}

#[derive(Debug)]
enum CompletePrinter {
    Normal,
    WithSnippets
}

fn read_file_from_stdin() -> String {
    let mut rawbytes = Vec::new();

    let stdin = io::stdin();
    stdin.lock().read_until(0x04, &mut rawbytes).expect("read until EOT");

    String::from_utf8(rawbytes).expect("utf8 from stdin")
}

fn read_file<P>(path: P) -> io::Result<String>
    where P: AsRef<Path>
{
    let mut res = String::new();
    let mut f = try!(File::open(path));

    try!(f.read_to_string(&mut res));
    Ok(res)
}

fn load_query_file<P, S>(path: P, sub: S, session: &Session)
    where P: Into<PathBuf>,
          S: AsRef<Path>
{
    let path = path.into();
    let sub = sub.as_ref();

    if sub.to_str() == Some("-") {
        let contents = read_file_from_stdin();
        session.cache_file_contents(path, contents);
    } else if sub != path {
        let contents = read_file(sub).unwrap();
        session.cache_file_contents(path, contents);
    }
}

fn run_the_complete_fn(cfg: &Config, print_type: CompletePrinter) {
    let fn_path = cfg.fn_name.as_ref().unwrap();
    let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path);

    let cache = FileCache::default();
    let session = Session::new(&cache);

    load_query_file(&fn_path, &substitute_file, &session);

    if let Some(expanded) = racer::expand_ident(&fn_path, cfg.coords(), &session) {
        cfg.interface.emit(Message::Prefix(expanded.start(), expanded.pos(), expanded.ident()));

        for m in racer::complete_from_file(&fn_path, cfg.coords(), &session) {
            match print_type {
                CompletePrinter::Normal => match_fn(m, cfg.interface),
                CompletePrinter::WithSnippets => match_with_snippet_fn(m, &session, cfg.interface),
            };
        }
    }
}

/// Completes a fully qualified name specified on command line
fn external_complete(cfg: Config, print_type: CompletePrinter) {
    let cwd = Path::new(".");
    let cache = FileCache::default();
    let session = Session::new(&cache);

    for m in racer::complete_fully_qualified_name(cfg.fqn.as_ref().unwrap(), &cwd, &session) {
        match print_type {
            CompletePrinter::Normal => match_fn(m, cfg.interface),
            CompletePrinter::WithSnippets => match_with_snippet_fn(m, &session, cfg.interface),
        }
    }
}

fn prefix(cfg: Config) {
    let fn_path = cfg.fn_name.as_ref().unwrap();
    let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path);
    let cache = FileCache::default();
    let session = Session::new(&cache);

    // Cache query file in session
    load_query_file(&fn_path, &substitute_file, &session);

    // print the start, end, and the identifier prefix being matched
    let expanded = racer::expand_ident(fn_path, cfg.coords(), &session).unwrap();
    cfg.interface.emit(Message::Prefix(expanded.start(), expanded.pos(), expanded.ident()));
}

fn find_definition(cfg: Config) {
    let fn_path = cfg.fn_name.as_ref().unwrap();
    let substitute_file = cfg.substitute_file.as_ref().unwrap_or(fn_path);
    let cache = FileCache::default();
    let session = Session::new(&cache);

    // Cache query file in session
    load_query_file(&fn_path, &substitute_file, &session);

    racer::find_definition(fn_path, cfg.coords(), &session)
        .map(|m| match_fn(m, cfg.interface));
    cfg.interface.emit(Message::End);
}

fn validate_rust_src_path_env_var() {
    match racer::get_rust_src_path() {
        Ok(_) => (),
        Err(err) => {
            println!("{}", err);
            std::process::exit(1);
        },
    }

}

fn daemon(cfg: Config) {
    let mut input = String::new();
    while let Ok(n) = io::stdin().read_line(&mut input) {
        // '\n' == 1
        if n == 1 {
            break;
        }
        // We add the setting NoBinaryName because in daemon mode we won't be passed the preceeding
        // binary name
        let cli = build_cli().setting(AppSettings::NoBinaryName);
        let matches = match cfg.interface {
            Interface::Text => cli.get_matches_from(input.trim_right().split_whitespace()),
            Interface::TabText => cli.get_matches_from(input.trim_right().split('\t'))
        };
        run(matches, cfg.interface);

        input.clear();
    }
}

enum Message<'a> {
    End,
    Prefix(Point, Point, &'a str),
    Match(String, Point, Point, &'a Path, MatchType, String),
    MatchWithSnippet(String, String, Point, Point, &'a Path, MatchType, String, String),
    Point(Point),
    Coords(Coordinate),
}

#[derive(Copy, Clone)]
enum Interface {
    Text,    // The original human-readable format.
    TabText, // Machine-readable format.  This is basically the same as Text, except that all field
             // separators are replaced with tabs.
             // In `daemon` mode tabs are also used to delimit command arguments.
}

impl Default for Interface {
    fn default() -> Self { Interface::Text }
}

impl Interface {
    fn leading_space(&self) -> &str {
        match *self {
            Interface::Text => " ",
            Interface::TabText => "\t"
        }
    }

    fn field_separator(&self) -> &str {
        match *self {
            Interface::Text => ",",
            Interface::TabText => "\t"
        }
    }

    fn emit(&self, message: Message) {
        match message {
            Message::End => println!("END"),
            Message::Prefix(start, pos, text) => match *self {
                Interface::Text => println!("PREFIX {},{},{}", start, pos, text),
                Interface::TabText => println!("PREFIX\t{}\t{}\t{}", start, pos, text),
            },
            Message::Point(point) => println!("POINT{}{}", self.leading_space(), point),
            Message::Coords(coord) => {
                println!("COORD{lead}{}{field}{}", 
                    coord.line,
                    coord.column,
                    lead = self.leading_space(),
                    field = self.field_separator());
            }
            Message::Match(mstr, linenum, charnum, path, mtype, context) => match *self {
                Interface::Text => {
                    let context = context.split_whitespace().collect::<Vec<&str>>().join(" ");
                    println!("MATCH {},{},{},{},{:?},{}",
                             mstr, linenum, charnum, path.display(), mtype, context);
                }
                Interface::TabText => {
                    let context = context.split_whitespace().collect::<Vec<&str>>().join(" ");
                    println!("MATCH\t{}\t{}\t{}\t{}\t{:?}\t{}",
                             mstr, linenum, charnum, path.display(), mtype, context);
                }
            },
            Message::MatchWithSnippet(mstr, snippet, linenum, charnum, path,
                                      mtype, context, docs) => match *self {
                Interface::Text => {
                    let context = context.replace(";", "\\;").split_whitespace()
                                                             .collect::<Vec<&str>>().join(" ");
                    let docs = format!("{:?}", docs).replace(";", "\\;");
                    println!("MATCH {};{};{};{};{};{:?};{};{}",
                             mstr, snippet, linenum, charnum, path.display(), mtype, context, docs);
                }
                Interface::TabText => {
                    let context = context.replace("\t", "\\t").split_whitespace()
                                                              .collect::<Vec<&str>>().join(" ");
                    println!("MATCH\t{}\t{}\t{}\t{}\t{}\t{:?}\t{}\t{:?}",
                             mstr, snippet, linenum, charnum, path.display(), mtype, context, docs);
                }
            }
        }
    }
}

#[derive(Default)]
struct Config {
    fqn: Option<String>,
    linenum: usize,
    charnum: usize,
    fn_name: Option<PathBuf>,
    substitute_file: Option<PathBuf>,
    interface: Interface,
    point: usize,
}

impl Config {
    fn coords(&self) -> Coordinate {
        Coordinate {
            line: self.linenum,
            column: self.charnum
        }
    }

    fn expect_file(&self) -> &PathBuf {
        self.fn_name.as_ref().expect("File path required")
    }
}

impl<'a> From<&'a ArgMatches<'a>> for Config {
    fn from(m: &'a ArgMatches) -> Self {
        // Check for the presence of the `point` argument that indicates we're
        // being asked to convert from point to coordinates
        if m.is_present("point") && m.is_present("path") {
            return Config {
                point: value_t_or_exit!(m.value_of("point"), usize),
                fn_name: m.value_of("path").map(PathBuf::from),
                ..Default::default()
            }
        }

        // We check for charnum because it's the second argument, which means more than just
        // an FQN was used (i.e. racer complete <linenum> <charnum> <fn_name> [substitute_file])
        if m.is_present("charnum") {
             let cfg = Config {
                charnum: value_t_or_exit!(m.value_of("charnum"), usize),
                fn_name: m.value_of("path").map(PathBuf::from),
                substitute_file: m.value_of("substitute_file").map(PathBuf::from),
                ..Default::default()
             };
             if !m.is_present("linenum") {
            // Because of the hack to allow fqn and linenum to share a single arg we set FQN
            // to None and set the charnum correctly using the FQN arg so there's no
            // hackery later
                return Config {linenum: value_t_or_exit!(m.value_of("fqn"), usize), .. cfg };
            }
            return Config {linenum: value_t_or_exit!(m.value_of("linenum"), usize), .. cfg };
        }
        Config {fqn: m.value_of("fqn").map(ToOwned::to_owned), ..Default::default() }
    }
}

fn build_cli<'a, 'b>() -> App<'a, 'b> {
    // we use the more verbose "Builder Pattern" to create the CLI because it's a littel faster
    // than the less verbose "Usage String" method...faster, meaning runtime speed since that's
    // extremely important here
    App::new("racer")
        .version(env!("CARGO_PKG_VERSION"))
        .author("Phil Dawes")
        .about("A Rust code completion utility")
        .settings(&[AppSettings::GlobalVersion,
		    AppSettings::SubcommandRequiredElseHelp])
        .arg(Arg::with_name("interface")
            .long("interface")
            .short("i")
            .takes_value(true)
            .possible_value("text")
            .possible_value("tab-text")
            .value_name("mode")
            .help("Interface mode"))
        .subcommand(SubCommand::with_name("complete")
            .about("performs completion and returns matches")
            // We set an explicit usage string here, instead of letting `clap` write one due to
            // using a single arg for multiple purposes
            .usage("racer complete <fqn>\n    \
                    racer complete <linenum> <charnum> <path> [substitute_file]")
            // Next we make it an error to run without any args
            .setting(AppSettings::ArgRequiredElseHelp)
            // Because we want a single arg to play two roles and be compatible with previous
            // racer releases, we have to be a little hacky here...
            //
            // We start by making 'fqn' the first positional arg, which will hold this dual value
            // of either an FQN as it says, or secretly a line-number
            .arg(Arg::with_name("fqn")
                .help("complete with a fully-qualified-name (e.g. std::io::)"))
            .arg(Arg::with_name("charnum")
                .help("The char number to search for matches")
                .requires("path"))
            .arg(Arg::with_name("path")
                .help("The path to search for name to match"))
            .arg(Arg::with_name("substitute_file")
                .help("An optional substitute file"))
            // 'linenum' **MUST** be last (or have the highest index so that it's never actually
            // used by the user, but still appears in the help text)
            .arg(Arg::with_name("linenum")
                .help("The line number at which to find the match")))
        .subcommand(SubCommand::with_name("daemon")
            .about("start a process that receives the above commands via stdin"))
        .subcommand(SubCommand::with_name("find-definition")
            .about("finds the definition of a function")
            .arg(Arg::with_name("linenum")
                .help("The line number at which to find the match")
                .required(true))
            .arg(Arg::with_name("charnum")
                .help("The char number at which to find the match")
                .required(true))
            .arg(Arg::with_name("path")
                .help("The path to search for name to match")
                .required(true))
            .arg(Arg::with_name("substitute_file")
                .help("An optional substitute file")))
        .subcommand(SubCommand::with_name("prefix")
            .arg(Arg::with_name("linenum")
                .help("The line number at which to find the match")
                .required(true))
            .arg(Arg::with_name("charnum")
                .help("The char number at which to find the match")
                .required(true))
            .arg(Arg::with_name("path")
                .help("The path to search for the match to prefix")
                .required(true)))
        .subcommand(SubCommand::with_name("complete-with-snippet")
            .about("performs completion and returns more detailed matches")
            .usage("racer complete-with-snippet <fqn>\n    \
                    racer complete-with-snippet <linenum> <charnum> <path> [substitute_file]")
            .setting(AppSettings::ArgRequiredElseHelp)
            .arg(Arg::with_name("fqn")
                .help("complete with a fully-qualified-name (e.g. std::io::)"))
            .arg(Arg::with_name("charnum")
                .help("The char number to search for matches")
                .requires("path"))
            .arg(Arg::with_name("path")
                .help("The path to search for name to match"))
            .arg(Arg::with_name("substitute_file")
                .help("An optional substitute file"))
            .arg(Arg::with_name("linenum")
                .help("The line number at which to find the match")))
         .subcommand(SubCommand::with_name("point")
            .about("converts linenum and charnum in a file to a point")
            // Next we make it an error to run without any args
            .setting(AppSettings::ArgRequiredElseHelp)
            .arg(Arg::with_name("linenum")
                .help("The line number at which to convert to point")
                .required(true))
            .arg(Arg::with_name("charnum")
                .help("The char number at which to convert to point")
                .required(true))
            .arg(Arg::with_name("path")
                .help("The path where the line and char occur")
                .required(true)))
        .subcommand(SubCommand::with_name("coord")
            .about("converts a racer point to line and character numbers")
            // Next we make it an error to run without any args
            .setting(AppSettings::ArgRequiredElseHelp)
            .arg(Arg::with_name("point")
                .help("The point to convert to line and character coordinates")
                .required(true))
            .arg(Arg::with_name("path")
                .help("The path where the line and char occur")
                .required(true)))
        .after_help("For more information about a specific command try 'racer <command> --help'")
}

fn main() {
    env_logger::init().unwrap();

    let matches = build_cli().get_matches();
    let interface = match matches.value_of("interface") {
            Some("tab-text") => Interface::TabText,
            Some("text") | _ => Interface::Text
        };
    
    validate_rust_src_path_env_var();
    
    run(matches, interface);
}

fn run(m: ArgMatches, interface: Interface) {
    use CompletePrinter::{Normal, WithSnippets};
    // match raw subcommand, and get it's sub-matches "m"
    if let (name, Some(sub_m)) = m.subcommand() {
        let mut cfg = Config::from(sub_m);
        cfg.interface = interface;
        match name {
            "daemon"                => daemon(cfg),
            "prefix"                => prefix(cfg),
            "complete"              => complete(cfg, Normal),
            "complete-with-snippet" => complete(cfg, WithSnippets),
            "find-definition"       => find_definition(cfg),
            "point"                 => point(cfg),
            "coord"                => coord(cfg),
            _                       => unreachable!()
        }
    }
}