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
use std::{borrow::Cow, path::PathBuf};
use anyhow::Context;
use clap::Parser;
use crate::{
cache::Cache,
config::{raw::RawConfig, Config, Language},
ext::{OptionExt, ResultExt},
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
pub struct Args {
/// Language configurations.
///
/// This can either be a path to a .toml file or a TOML string.
///
/// The default configuration will be applied if this argument is not
/// provided or if it is the empty string.
#[arg(short, long)]
config: Option<String>,
/// Directory to cache parsed symbols.
///
/// Files are reparsed if their cached mtime differs from than their current
/// mtime, or the path of the file doesn't exist in the cache. This option
/// is typically used when `symbols` is called from the same directory
/// multiple times, such as searching over a code base in an editor.
///
/// The directory is created if it does not exist.
#[arg(long)]
cache_dir: Option<PathBuf>,
/// The characters between properties of a single symbol.
///
/// This is the character between the path, location, kind, text, and
/// leading/trailing text written to stdout.
///
/// This defaults to U+200B (zero-width space).
#[arg(short, long, default_value_t = '\u{200B}')]
pub delimiter: char,
/// The character between symbols.
///
/// This defaults to the U+0 (null byte).
#[arg(short, long, default_value_t = '\0')]
pub separator: char,
/// Whether to spawn a detached process to index symbols.
///
/// Only useful with the `cache-dir` option.
///
/// If this option is false, the cache may not be created if the process
/// is exited prematurely. This can happen if using `symbols` in a pipeline
/// (such as with `fzf`) and selecting a symbol before indexing is complete.
///
/// If this option is true, indexing is performed by a separate detached
/// process whose output is redirected to stdout. Then, if `symbols` is
/// exited prematurely, the indexing will still be able to complete.
#[arg(long, default_value_t)]
detached: bool,
/// The number of worker threads to use when parsing files.
///
/// This defaults to `std::thread::available_parallelism` if it is available,
/// and otherwise defaults to 8.
#[arg(short, long)]
threads: Option<usize>,
/// Only show symbols from files with extensions matching this language.
///
/// This option takes precedence over `--extension`.
#[arg(short, long)]
language: Option<Language>,
/// Only show symbols from files with extensions matching this extension's
/// language. Note that this will not filter for symbols in files matching
/// this extension, but for files with the same language as this extension's.
///
/// The `--language` option takes precedence over `--extension`.
#[arg(short, long)]
extension: Option<String>,
#[arg(default_value = ".")]
path: Option<PathBuf>,
}
impl Args {
fn raw_config(&self) -> Result<RawConfig, anyhow::Error> {
if let Some(config) = &self.config {
let config_content: Cow<str> = if config.ends_with(".toml") {
std::fs::read_to_string(config)
.context("failed to read config file")?
.into()
} else {
config.into()
};
if !config_content.is_empty() {
return toml::from_str(&config_content).context("failed to parse config");
}
}
RawConfig::default().ok()
}
fn language(&self) -> Result<Option<Language>, anyhow::Error> {
if let Some(language) = self.language {
return language.some().ok();
}
if let Some(extension) = &self.extension {
return Language::from_extension(extension)
.context("unknown language")
.map(Some);
}
None.ok()
}
pub fn config(&self) -> Result<Config, anyhow::Error> {
let mut raw_config = self.raw_config()?;
if let Some(language) = self.language()? {
raw_config = raw_config.for_language(language);
}
raw_config.try_into()
}
// pub fn config(&self) -> Result<Config, anyhow::Error> {
// let config = self.full_config()?;
// if let Some(language) = &self.language {
// config.
// }
// }
pub fn cache(&self) -> Result<Cache, anyhow::Error> {
if let Some(cache_dir) = &self.cache_dir {
Cache::from_dir(cache_dir)
} else {
Ok(Cache::default())
}
}
pub fn num_threads(&self) -> usize {
self
.threads
.or_else(|| std::thread::available_parallelism().map(usize::from).ok())
.unwrap_or(8)
}
}