Skip to main content

nu_command/filesystem/idx/
watch.rs

1use 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}