aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/src/cli
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2020-02-18 11:37:45 +0000
committerAleksey Kladov <[email protected]>2020-02-18 11:37:45 +0000
commit865759925be6b72f7ef39124ed0e4c86c0412a69 (patch)
tree0fc36373073a66c2bbd6c7cfae6cb734527d847f /crates/ra_lsp_server/src/cli
parentc855e36696afa54260773a6bc8a358df67d60dea (diff)
Rename folder
Diffstat (limited to 'crates/ra_lsp_server/src/cli')
-rw-r--r--crates/ra_lsp_server/src/cli/analysis_bench.rs158
-rw-r--r--crates/ra_lsp_server/src/cli/analysis_stats.rs260
-rw-r--r--crates/ra_lsp_server/src/cli/load_cargo.rs155
-rw-r--r--crates/ra_lsp_server/src/cli/progress_report.rs120
4 files changed, 0 insertions, 693 deletions
diff --git a/crates/ra_lsp_server/src/cli/analysis_bench.rs b/crates/ra_lsp_server/src/cli/analysis_bench.rs
deleted file mode 100644
index 91855e592..000000000
--- a/crates/ra_lsp_server/src/cli/analysis_bench.rs
+++ /dev/null
@@ -1,158 +0,0 @@
1//! Benchmark operations like highlighting or goto definition.
2
3use std::{
4 path::{Path, PathBuf},
5 str::FromStr,
6 sync::Arc,
7 time::Instant,
8};
9
10use anyhow::{format_err, Result};
11use ra_db::{
12 salsa::{Database, Durability},
13 FileId, SourceDatabaseExt,
14};
15use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol};
16
17use crate::cli::{load_cargo::load_cargo, Verbosity};
18
19pub enum BenchWhat {
20 Highlight { path: PathBuf },
21 Complete(Position),
22 GotoDef(Position),
23}
24
25pub struct Position {
26 pub path: PathBuf,
27 pub line: u32,
28 pub column: u32,
29}
30
31impl FromStr for Position {
32 type Err = anyhow::Error;
33 fn from_str(s: &str) -> Result<Self> {
34 let (path_line, column) = rsplit_at_char(s, ':')?;
35 let (path, line) = rsplit_at_char(path_line, ':')?;
36 Ok(Position { path: path.into(), line: line.parse()?, column: column.parse()? })
37 }
38}
39
40fn rsplit_at_char(s: &str, c: char) -> Result<(&str, &str)> {
41 let idx = s.rfind(c).ok_or_else(|| format_err!("no `{}` in {}", c, s))?;
42 Ok((&s[..idx], &s[idx + 1..]))
43}
44
45pub fn analysis_bench(verbosity: Verbosity, path: &Path, what: BenchWhat) -> Result<()> {
46 ra_prof::init();
47
48 let start = Instant::now();
49 eprint!("loading: ");
50 let (mut host, roots) = load_cargo(path)?;
51 let db = host.raw_database();
52 eprintln!("{:?}\n", start.elapsed());
53
54 let file_id = {
55 let path = match &what {
56 BenchWhat::Highlight { path } => path,
57 BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => &pos.path,
58 };
59 let path = std::env::current_dir()?.join(path).canonicalize()?;
60 roots
61 .iter()
62 .find_map(|(source_root_id, project_root)| {
63 if project_root.is_member() {
64 for file_id in db.source_root(*source_root_id).walk() {
65 let rel_path = db.file_relative_path(file_id);
66 let abs_path = rel_path.to_path(project_root.path());
67 if abs_path == path {
68 return Some(file_id);
69 }
70 }
71 }
72 None
73 })
74 .ok_or_else(|| format_err!("Can't find {}", path.display()))?
75 };
76
77 match &what {
78 BenchWhat::Highlight { .. } => {
79 let res = do_work(&mut host, file_id, |analysis| {
80 analysis.diagnostics(file_id).unwrap();
81 analysis.highlight_as_html(file_id, false).unwrap()
82 });
83 if verbosity.is_verbose() {
84 println!("\n{}", res);
85 }
86 }
87 BenchWhat::Complete(pos) | BenchWhat::GotoDef(pos) => {
88 let is_completion = match what {
89 BenchWhat::Complete(..) => true,
90 _ => false,
91 };
92
93 let offset = host
94 .analysis()
95 .file_line_index(file_id)?
96 .offset(LineCol { line: pos.line - 1, col_utf16: pos.column });
97 let file_postion = FilePosition { file_id, offset };
98
99 if is_completion {
100 let res =
101 do_work(&mut host, file_id, |analysis| analysis.completions(file_postion));
102 if verbosity.is_verbose() {
103 println!("\n{:#?}", res);
104 }
105 } else {
106 let res =
107 do_work(&mut host, file_id, |analysis| analysis.goto_definition(file_postion));
108 if verbosity.is_verbose() {
109 println!("\n{:#?}", res);
110 }
111 }
112 }
113 }
114 Ok(())
115}
116
117fn do_work<F: Fn(&Analysis) -> T, T>(host: &mut AnalysisHost, file_id: FileId, work: F) -> T {
118 {
119 let start = Instant::now();
120 eprint!("from scratch: ");
121 work(&host.analysis());
122 eprintln!("{:?}", start.elapsed());
123 }
124 {
125 let start = Instant::now();
126 eprint!("no change: ");
127 work(&host.analysis());
128 eprintln!("{:?}", start.elapsed());
129 }
130 {
131 let start = Instant::now();
132 eprint!("trivial change: ");
133 host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW);
134 work(&host.analysis());
135 eprintln!("{:?}", start.elapsed());
136 }
137 {
138 let start = Instant::now();
139 eprint!("comment change: ");
140 {
141 let mut text = host.analysis().file_text(file_id).unwrap().to_string();
142 text.push_str("\n/* Hello world */\n");
143 let mut change = AnalysisChange::new();
144 change.change_file(file_id, Arc::new(text));
145 host.apply_change(change);
146 }
147 work(&host.analysis());
148 eprintln!("{:?}", start.elapsed());
149 }
150 {
151 let start = Instant::now();
152 eprint!("const change: ");
153 host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH);
154 let res = work(&host.analysis());
155 eprintln!("{:?}", start.elapsed());
156 res
157 }
158}
diff --git a/crates/ra_lsp_server/src/cli/analysis_stats.rs b/crates/ra_lsp_server/src/cli/analysis_stats.rs
deleted file mode 100644
index 99ab6e443..000000000
--- a/crates/ra_lsp_server/src/cli/analysis_stats.rs
+++ /dev/null
@@ -1,260 +0,0 @@
1//! Fully type-check project and print various stats, like the number of type
2//! errors.
3
4use std::{collections::HashSet, fmt::Write, path::Path, time::Instant};
5
6use hir::{
7 db::{DefDatabase, HirDatabase},
8 AssocItem, Crate, HasSource, HirDisplay, ModuleDef,
9};
10use hir_def::FunctionId;
11use hir_ty::{Ty, TypeWalk};
12use itertools::Itertools;
13use ra_db::SourceDatabaseExt;
14use ra_syntax::AstNode;
15use rand::{seq::SliceRandom, thread_rng};
16
17use crate::cli::{load_cargo::load_cargo, progress_report::ProgressReport, Result, Verbosity};
18
19pub fn analysis_stats(
20 verbosity: Verbosity,
21 memory_usage: bool,
22 path: &Path,
23 only: Option<&str>,
24 with_deps: bool,
25 randomize: bool,
26) -> Result<()> {
27 let db_load_time = Instant::now();
28 let (mut host, roots) = load_cargo(path)?;
29 let db = host.raw_database();
30 println!("Database loaded, {} roots, {:?}", roots.len(), db_load_time.elapsed());
31 let analysis_time = Instant::now();
32 let mut num_crates = 0;
33 let mut visited_modules = HashSet::new();
34 let mut visit_queue = Vec::new();
35
36 let members =
37 roots
38 .into_iter()
39 .filter_map(|(source_root_id, project_root)| {
40 if with_deps || project_root.is_member() {
41 Some(source_root_id)
42 } else {
43 None
44 }
45 })
46 .collect::<HashSet<_>>();
47
48 let mut krates = Crate::all(db);
49 if randomize {
50 krates.shuffle(&mut thread_rng());
51 }
52 for krate in krates {
53 let module = krate.root_module(db).expect("crate without root module");
54 let file_id = module.definition_source(db).file_id;
55 if members.contains(&db.file_source_root(file_id.original_file(db))) {
56 num_crates += 1;
57 visit_queue.push(module);
58 }
59 }
60
61 if randomize {
62 visit_queue.shuffle(&mut thread_rng());
63 }
64
65 println!("Crates in this dir: {}", num_crates);
66 let mut num_decls = 0;
67 let mut funcs = Vec::new();
68 while let Some(module) = visit_queue.pop() {
69 if visited_modules.insert(module) {
70 visit_queue.extend(module.children(db));
71
72 for decl in module.declarations(db) {
73 num_decls += 1;
74 if let ModuleDef::Function(f) = decl {
75 funcs.push(f);
76 }
77 }
78
79 for impl_block in module.impl_blocks(db) {
80 for item in impl_block.items(db) {
81 num_decls += 1;
82 if let AssocItem::Function(f) = item {
83 funcs.push(f);
84 }
85 }
86 }
87 }
88 }
89 println!("Total modules found: {}", visited_modules.len());
90 println!("Total declarations: {}", num_decls);
91 println!("Total functions: {}", funcs.len());
92 println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
93
94 if randomize {
95 funcs.shuffle(&mut thread_rng());
96 }
97
98 let inference_time = Instant::now();
99 let mut bar = match verbosity {
100 Verbosity::Quiet | Verbosity::Spammy => ProgressReport::hidden(),
101 _ => ProgressReport::new(funcs.len() as u64),
102 };
103
104 bar.tick();
105 let mut num_exprs = 0;
106 let mut num_exprs_unknown = 0;
107 let mut num_exprs_partially_unknown = 0;
108 let mut num_type_mismatches = 0;
109 for f in funcs {
110 let name = f.name(db);
111 let full_name = f
112 .module(db)
113 .path_to_root(db)
114 .into_iter()
115 .rev()
116 .filter_map(|it| it.name(db))
117 .chain(Some(f.name(db)))
118 .join("::");
119 if let Some(only_name) = only {
120 if name.to_string() != only_name && full_name != only_name {
121 continue;
122 }
123 }
124 let mut msg = format!("processing: {}", full_name);
125 if verbosity.is_verbose() {
126 let src = f.source(db);
127 let original_file = src.file_id.original_file(db);
128 let path = db.file_relative_path(original_file);
129 let syntax_range = src.value.syntax().text_range();
130 write!(msg, " ({:?} {})", path, syntax_range).unwrap();
131 }
132 if verbosity.is_spammy() {
133 bar.println(format!("{}", msg));
134 }
135 bar.set_message(&msg);
136 let f_id = FunctionId::from(f);
137 let body = db.body(f_id.into());
138 let inference_result = db.infer(f_id.into());
139 let (previous_exprs, previous_unknown, previous_partially_unknown) =
140 (num_exprs, num_exprs_unknown, num_exprs_partially_unknown);
141 for (expr_id, _) in body.exprs.iter() {
142 let ty = &inference_result[expr_id];
143 num_exprs += 1;
144 if let Ty::Unknown = ty {
145 num_exprs_unknown += 1;
146 } else {
147 let mut is_partially_unknown = false;
148 ty.walk(&mut |ty| {
149 if let Ty::Unknown = ty {
150 is_partially_unknown = true;
151 }
152 });
153 if is_partially_unknown {
154 num_exprs_partially_unknown += 1;
155 }
156 }
157 if only.is_some() && verbosity.is_spammy() {
158 // in super-verbose mode for just one function, we print every single expression
159 let (_, sm) = db.body_with_source_map(f_id.into());
160 let src = sm.expr_syntax(expr_id);
161 if let Some(src) = src {
162 let original_file = src.file_id.original_file(db);
163 let line_index = host.analysis().file_line_index(original_file).unwrap();
164 let text_range = src.value.either(
165 |it| it.syntax_node_ptr().range(),
166 |it| it.syntax_node_ptr().range(),
167 );
168 let (start, end) = (
169 line_index.line_col(text_range.start()),
170 line_index.line_col(text_range.end()),
171 );
172 bar.println(format!(
173 "{}:{}-{}:{}: {}",
174 start.line + 1,
175 start.col_utf16,
176 end.line + 1,
177 end.col_utf16,
178 ty.display(db)
179 ));
180 } else {
181 bar.println(format!("unknown location: {}", ty.display(db)));
182 }
183 }
184 if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) {
185 num_type_mismatches += 1;
186 if verbosity.is_verbose() {
187 let (_, sm) = db.body_with_source_map(f_id.into());
188 let src = sm.expr_syntax(expr_id);
189 if let Some(src) = src {
190 // FIXME: it might be nice to have a function (on Analysis?) that goes from Source<T> -> (LineCol, LineCol) directly
191 let original_file = src.file_id.original_file(db);
192 let path = db.file_relative_path(original_file);
193 let line_index = host.analysis().file_line_index(original_file).unwrap();
194 let text_range = src.value.either(
195 |it| it.syntax_node_ptr().range(),
196 |it| it.syntax_node_ptr().range(),
197 );
198 let (start, end) = (
199 line_index.line_col(text_range.start()),
200 line_index.line_col(text_range.end()),
201 );
202 bar.println(format!(
203 "{} {}:{}-{}:{}: Expected {}, got {}",
204 path,
205 start.line + 1,
206 start.col_utf16,
207 end.line + 1,
208 end.col_utf16,
209 mismatch.expected.display(db),
210 mismatch.actual.display(db)
211 ));
212 } else {
213 bar.println(format!(
214 "{}: Expected {}, got {}",
215 name,
216 mismatch.expected.display(db),
217 mismatch.actual.display(db)
218 ));
219 }
220 }
221 }
222 }
223 if verbosity.is_spammy() {
224 bar.println(format!(
225 "In {}: {} exprs, {} unknown, {} partial",
226 full_name,
227 num_exprs - previous_exprs,
228 num_exprs_unknown - previous_unknown,
229 num_exprs_partially_unknown - previous_partially_unknown
230 ));
231 }
232 bar.inc(1);
233 }
234 bar.finish_and_clear();
235 println!("Total expressions: {}", num_exprs);
236 println!(
237 "Expressions of unknown type: {} ({}%)",
238 num_exprs_unknown,
239 if num_exprs > 0 { num_exprs_unknown * 100 / num_exprs } else { 100 }
240 );
241 println!(
242 "Expressions of partially unknown type: {} ({}%)",
243 num_exprs_partially_unknown,
244 if num_exprs > 0 { num_exprs_partially_unknown * 100 / num_exprs } else { 100 }
245 );
246 println!("Type mismatches: {}", num_type_mismatches);
247 println!("Inference: {:?}, {}", inference_time.elapsed(), ra_prof::memory_usage());
248 println!("Total: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage());
249
250 if memory_usage {
251 for (name, bytes) in host.per_query_memory_usage() {
252 println!("{:>8} {}", bytes, name)
253 }
254 let before = ra_prof::memory_usage();
255 drop(host);
256 println!("leftover: {}", before.allocated - ra_prof::memory_usage().allocated)
257 }
258
259 Ok(())
260}
diff --git a/crates/ra_lsp_server/src/cli/load_cargo.rs b/crates/ra_lsp_server/src/cli/load_cargo.rs
deleted file mode 100644
index 8cd08ecb6..000000000
--- a/crates/ra_lsp_server/src/cli/load_cargo.rs
+++ /dev/null
@@ -1,155 +0,0 @@
1//! Loads a Cargo project into a static instance of analysis, without support
2//! for incorporating changes.
3
4use std::path::Path;
5
6use anyhow::Result;
7use crossbeam_channel::{unbounded, Receiver};
8use ra_db::{CrateGraph, FileId, SourceRootId};
9use ra_ide::{AnalysisChange, AnalysisHost, FeatureFlags};
10use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace};
11use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch};
12use rustc_hash::{FxHashMap, FxHashSet};
13
14use crate::vfs_glob::RustPackageFilterBuilder;
15
16fn vfs_file_to_id(f: ra_vfs::VfsFile) -> FileId {
17 FileId(f.0)
18}
19fn vfs_root_to_id(r: ra_vfs::VfsRoot) -> SourceRootId {
20 SourceRootId(r.0)
21}
22
23pub(crate) fn load_cargo(
24 root: &Path,
25) -> Result<(AnalysisHost, FxHashMap<SourceRootId, PackageRoot>)> {
26 let root = std::env::current_dir()?.join(root);
27 let ws = ProjectWorkspace::discover(root.as_ref(), &Default::default())?;
28 let project_roots = ws.to_roots();
29 let (sender, receiver) = unbounded();
30 let sender = Box::new(move |t| sender.send(t).unwrap());
31 let (mut vfs, roots) = Vfs::new(
32 project_roots
33 .iter()
34 .map(|pkg_root| {
35 RootEntry::new(
36 pkg_root.path().clone(),
37 RustPackageFilterBuilder::default()
38 .set_member(pkg_root.is_member())
39 .into_vfs_filter(),
40 )
41 })
42 .collect(),
43 sender,
44 Watch(false),
45 );
46
47 // FIXME: cfg options?
48 let default_cfg_options = {
49 let mut opts = get_rustc_cfg_options();
50 opts.insert_atom("test".into());
51 opts.insert_atom("debug_assertion".into());
52 opts
53 };
54
55 let (crate_graph, _crate_names) =
56 ws.to_crate_graph(&default_cfg_options, &mut |path: &Path| {
57 let vfs_file = vfs.load(path);
58 log::debug!("vfs file {:?} -> {:?}", path, vfs_file);
59 vfs_file.map(vfs_file_to_id)
60 });
61 log::debug!("crate graph: {:?}", crate_graph);
62
63 let source_roots = roots
64 .iter()
65 .map(|&vfs_root| {
66 let source_root_id = vfs_root_to_id(vfs_root);
67 let project_root = project_roots
68 .iter()
69 .find(|it| it.path() == &vfs.root2path(vfs_root))
70 .unwrap()
71 .clone();
72 (source_root_id, project_root)
73 })
74 .collect::<FxHashMap<_, _>>();
75 let host = load(&source_roots, crate_graph, &mut vfs, receiver);
76 Ok((host, source_roots))
77}
78
79pub(crate) fn load(
80 source_roots: &FxHashMap<SourceRootId, PackageRoot>,
81 crate_graph: CrateGraph,
82 vfs: &mut Vfs,
83 receiver: Receiver<VfsTask>,
84) -> AnalysisHost {
85 let lru_cap = std::env::var("RA_LRU_CAP").ok().and_then(|it| it.parse::<usize>().ok());
86 let mut host = AnalysisHost::new(lru_cap, FeatureFlags::default());
87 let mut analysis_change = AnalysisChange::new();
88 analysis_change.set_crate_graph(crate_graph);
89
90 // wait until Vfs has loaded all roots
91 let mut roots_loaded = FxHashSet::default();
92 for task in receiver {
93 vfs.handle_task(task);
94 let mut done = false;
95 for change in vfs.commit_changes() {
96 match change {
97 VfsChange::AddRoot { root, files } => {
98 let source_root_id = vfs_root_to_id(root);
99 let is_local = source_roots[&source_root_id].is_member();
100 log::debug!(
101 "loaded source root {:?} with path {:?}",
102 source_root_id,
103 vfs.root2path(root)
104 );
105 analysis_change.add_root(source_root_id, is_local);
106 analysis_change.set_debug_root_path(
107 source_root_id,
108 source_roots[&source_root_id].path().display().to_string(),
109 );
110
111 let mut file_map = FxHashMap::default();
112 for (vfs_file, path, text) in files {
113 let file_id = vfs_file_to_id(vfs_file);
114 analysis_change.add_file(source_root_id, file_id, path.clone(), text);
115 file_map.insert(path, file_id);
116 }
117 roots_loaded.insert(source_root_id);
118 if roots_loaded.len() == vfs.n_roots() {
119 done = true;
120 }
121 }
122 VfsChange::AddFile { root, file, path, text } => {
123 let source_root_id = vfs_root_to_id(root);
124 let file_id = vfs_file_to_id(file);
125 analysis_change.add_file(source_root_id, file_id, path, text);
126 }
127 VfsChange::RemoveFile { .. } | VfsChange::ChangeFile { .. } => {
128 // We just need the first scan, so just ignore these
129 }
130 }
131 }
132 if done {
133 break;
134 }
135 }
136
137 host.apply_change(analysis_change);
138 host
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 use hir::Crate;
146
147 #[test]
148 fn test_loading_rust_analyzer() {
149 let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
150 let (host, _roots) = load_cargo(path).unwrap();
151 let n_crates = Crate::all(host.raw_database()).len();
152 // RA has quite a few crates, but the exact count doesn't matter
153 assert!(n_crates > 20);
154 }
155}
diff --git a/crates/ra_lsp_server/src/cli/progress_report.rs b/crates/ra_lsp_server/src/cli/progress_report.rs
deleted file mode 100644
index 31867a1e9..000000000
--- a/crates/ra_lsp_server/src/cli/progress_report.rs
+++ /dev/null
@@ -1,120 +0,0 @@
1//! A simple progress bar
2//!
3//! A single thread non-optimized progress bar
4use std::io::Write;
5
6/// A Simple ASCII Progress Bar
7pub struct ProgressReport {
8 curr: f32,
9 text: String,
10 hidden: bool,
11
12 len: u64,
13 pos: u64,
14 msg: String,
15}
16
17impl ProgressReport {
18 pub fn new(len: u64) -> ProgressReport {
19 ProgressReport {
20 curr: 0.0,
21 text: String::new(),
22 hidden: false,
23 len,
24 pos: 0,
25 msg: String::new(),
26 }
27 }
28
29 pub fn hidden() -> ProgressReport {
30 ProgressReport {
31 curr: 0.0,
32 text: String::new(),
33 hidden: true,
34 len: 0,
35 pos: 0,
36 msg: String::new(),
37 }
38 }
39
40 pub fn set_message(&mut self, msg: &str) {
41 self.msg = msg.to_string();
42 self.tick();
43 }
44
45 pub fn println<I: Into<String>>(&mut self, msg: I) {
46 self.clear();
47 println!("{}", msg.into());
48 self.tick();
49 }
50
51 pub fn inc(&mut self, delta: u64) {
52 self.pos += delta;
53 if self.len == 0 {
54 self.set_value(0.0)
55 } else {
56 self.set_value((self.pos as f32) / (self.len as f32))
57 }
58 self.tick();
59 }
60
61 pub fn finish_and_clear(&mut self) {
62 self.clear();
63 }
64
65 pub fn tick(&mut self) {
66 if self.hidden {
67 return;
68 }
69 let percent = (self.curr * 100.0) as u32;
70 let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg);
71 self.update_text(&text);
72 }
73
74 fn update_text(&mut self, text: &str) {
75 // Get length of common portion
76 let mut common_prefix_length = 0;
77 let common_length = usize::min(self.text.len(), text.len());
78
79 while common_prefix_length < common_length
80 && text.chars().nth(common_prefix_length).unwrap()
81 == self.text.chars().nth(common_prefix_length).unwrap()
82 {
83 common_prefix_length += 1;
84 }
85
86 // Backtrack to the first differing character
87 let mut output = String::new();
88 output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length);
89 // Output new suffix
90 output += &text[common_prefix_length..text.len()];
91
92 // If the new text is shorter than the old one: delete overlapping characters
93 if let Some(overlap_count) = self.text.len().checked_sub(text.len()) {
94 if overlap_count > 0 {
95 output += &" ".repeat(overlap_count);
96 output += &"\x08".repeat(overlap_count);
97 }
98 }
99
100 let _ = std::io::stdout().write(output.as_bytes());
101 let _ = std::io::stdout().flush();
102 self.text = text.to_string();
103 }
104
105 fn set_value(&mut self, value: f32) {
106 self.curr = f32::max(0.0, f32::min(1.0, value));
107 }
108
109 fn clear(&mut self) {
110 if self.hidden {
111 return;
112 }
113
114 // Fill all last text to space and return the cursor
115 let spaces = " ".repeat(self.text.len());
116 let backspaces = "\x08".repeat(self.text.len());
117 print!("{}{}{}", backspaces, spaces, backspaces);
118 self.text = String::new();
119 }
120}