nu_command/filesystem/idx/
watch.rs1use super::state::{WatchStreamOptions, stream_watch};
2use nu_engine::command_prelude::*;
3use std::time::Duration;
4
5#[derive(Clone)]
6pub struct IdxWatch;
7
8impl Command for IdxWatch {
9 fn name(&self) -> &str {
10 "idx watch"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build(self.name())
15 .optional(
16 "pattern",
17 SyntaxShape::String,
18 "Base-relative glob, path, or directory to watch. Omit or pass empty to watch the whole indexed tree.",
19 )
20 .named(
21 "ignore",
22 SyntaxShape::List(Box::new(SyntaxShape::String)),
23 "List of globs or path-prefixes to exclude.",
24 Some('i'),
25 )
26 .named(
27 "timeout",
28 SyntaxShape::Duration,
29 "Stop streaming after this duration.",
30 Some('t'),
31 )
32 .named(
33 "max-events",
34 SyntaxShape::Int,
35 "Stop after emitting this many events.",
36 Some('n'),
37 )
38 .input_output_types(vec![(
39 Type::Nothing,
40 Type::Table(
41 vec![
42 ("kind".into(), Type::String),
43 ("path".into(), Type::String),
44 ]
45 .into(),
46 ),
47 )])
48 .category(Category::FileSystem)
49 }
50
51 fn description(&self) -> &str {
52 "Stream filesystem change events from the live idx index."
53 }
54
55 fn extra_description(&self) -> &str {
56 "Requires a live runtime initialized without `--no-watch`. \
57Events are debounced by fff-search and emitted as records with `kind` (`created`, `modified`, `removed`, `rescan`) \
58and absolute `path`. Gitignored and other index-ignored files do not produce events. \
59Patterns must be inside the indexed base path. Use plain `watch` for ad-hoc path watching without an index."
60 }
61
62 fn search_terms(&self) -> Vec<&str> {
63 vec!["watcher", "filesystem", "notify", "events"]
64 }
65
66 fn examples(&self) -> Vec<Example<'_>> {
67 vec![
68 Example {
69 description: "Watch the whole indexed tree after initializing idx.",
70 example: "idx init .; idx watch",
71 result: None,
72 },
73 Example {
74 description: "Watch only Rust files, ignoring a vendor-style path prefix.",
75 example: r#"idx watch "**/*.rs" --ignore [target]"#,
76 result: None,
77 },
78 Example {
79 description: "Take action on modified files in a pipeline.",
80 example: r#"idx watch | where kind == "modified" | each { |e| print $"changed: ($e.path)" }"#,
81 result: None,
82 },
83 Example {
84 description: "Stop after a single event (useful in scripts).",
85 example: "idx watch --max-events 1",
86 result: None,
87 },
88 Example {
89 description: "Stop after a duration if no more events are needed.",
90 example: "idx watch --timeout 5sec",
91 result: None,
92 },
93 ]
94 }
95
96 fn run(
97 &self,
98 engine_state: &EngineState,
99 stack: &mut Stack,
100 call: &Call,
101 _input: PipelineData,
102 ) -> Result<PipelineData, ShellError> {
103 let pattern: Option<String> = call.opt(engine_state, stack, 0)?;
104 let ignore = call
105 .get_flag::<Vec<String>>(engine_state, stack, "ignore")?
106 .unwrap_or_default();
107 let timeout: Option<Duration> = call.get_flag(engine_state, stack, "timeout")?;
108 let max_events: Option<i64> = call.get_flag(engine_state, stack, "max-events")?;
109
110 let max_events = max_events
111 .map(|value| {
112 usize::try_from(value)
113 .map_err(|_| ShellError::NeedsPositiveValue { span: call.head })
114 })
115 .transpose()?;
116
117 stream_watch(WatchStreamOptions {
118 pattern: pattern.unwrap_or_default(),
119 ignore,
120 timeout,
121 max_events,
122 span: call.head,
123 signals: engine_state.signals().clone(),
124 })
125 }
126}