Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T05:34:21.713Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

bash.rs

322 lines · 10.2 KB · rust
1use anyhow::Result;
2use async_trait::async_trait;
3use collections::HashMap;
4use language::{LanguageServerName, LspAdapter, LspAdapterDelegate, LspInstaller, Toolchain};
5use lsp::LanguageServerBinary;
6use node_runtime::{NodeRuntime, VersionStrategy};
7use project::ContextProviderWithTasks;
8use semver::Version;
9use std::{future::Future, path::PathBuf, sync::Arc, vec};
10use task::{TaskTemplate, TaskTemplates, VariableName};
11use util::{ResultExt, maybe};
12
13pub(super) fn bash_task_context() -> ContextProviderWithTasks {
14    ContextProviderWithTasks::new(TaskTemplates(vec![
15        TaskTemplate {
16            label: "execute selection".to_owned(),
17            command: VariableName::SelectedText.template_value(),
18            ..TaskTemplate::default()
19        },
20        TaskTemplate {
21            label: format!("run '{}'", VariableName::File.template_value()),
22            command: VariableName::File.template_value(),
23            tags: vec!["bash-script".to_owned()],
24            ..TaskTemplate::default()
25        },
26    ]))
27}
28
29pub struct BashLspAdapter {
30    node: NodeRuntime,
31}
32
33impl BashLspAdapter {
34    const PACKAGE_NAME: &str = "bash-language-server";
35    const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "bash-language-server/out/cli.js";
36
37    pub fn new(node: NodeRuntime) -> Self {
38        Self { node }
39    }
40
41    async fn get_cached_server_binary(
42        container_dir: PathBuf,
43        env: HashMap<String, String>,
44        node: &NodeRuntime,
45    ) -> Option<lsp::LanguageServerBinary> {
46        maybe!(async {
47            let server_path = container_dir
48                .join("node_modules")
49                .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
50            anyhow::ensure!(
51                server_path.exists(),
52                "missing executable in directory {server_path:?}"
53            );
54            Ok(LanguageServerBinary {
55                path: node.binary_path().await?,
56                env: Some(env),
57                arguments: vec![server_path.into(), "start".into()],
58            })
59        })
60        .await
61        .log_err()
62    }
63}
64
65impl LspInstaller for BashLspAdapter {
66    type BinaryVersion = Version;
67
68    async fn cached_server_binary(
69        &self,
70        container_dir: std::path::PathBuf,
71        delegate: &dyn LspAdapterDelegate,
72    ) -> Option<lsp::LanguageServerBinary> {
73        let env = delegate.shell_env().await;
74        Self::get_cached_server_binary(container_dir, env, &self.node).await
75    }
76
77    async fn check_if_user_installed(
78        &self,
79        delegate: &Arc<dyn LspAdapterDelegate>,
80        _: Option<Toolchain>,
81        _: &gpui::AsyncApp,
82    ) -> Option<lsp::LanguageServerBinary> {
83        let path = delegate.which(Self::PACKAGE_NAME.as_ref()).await?;
84        let env = delegate.shell_env().await;
85
86        Some(LanguageServerBinary {
87            path,
88            env: Some(env),
89            arguments: vec!["start".into()],
90        })
91    }
92
93    fn check_if_version_installed(
94        &self,
95        version: &Self::BinaryVersion,
96        container_dir: &PathBuf,
97        delegate: &Arc<dyn LspAdapterDelegate>,
98    ) -> impl Send + Future<Output = Option<lsp::LanguageServerBinary>> + use<> {
99        let node = self.node.clone();
100        let version = version.clone();
101        let container_dir = container_dir.clone();
102        let delegate = delegate.clone();
103
104        async move {
105            let server_path = container_dir
106                .join("node_modules")
107                .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
108
109            let should_install_language_server = node
110                .should_install_npm_package(
111                    Self::PACKAGE_NAME,
112                    &server_path,
113                    &container_dir,
114                    VersionStrategy::Latest(&version),
115                )
116                .await;
117
118            if should_install_language_server {
119                None
120            } else {
121                let env = delegate.shell_env().await;
122                Some(LanguageServerBinary {
123                    path: node.binary_path().await.ok()?,
124                    env: Some(env),
125                    arguments: vec![server_path.into(), "start".into()],
126                })
127            }
128        }
129    }
130
131    async fn fetch_latest_server_version(
132        &self,
133        _: &Arc<dyn LspAdapterDelegate>,
134        _: bool,
135        _: &mut gpui::AsyncApp,
136    ) -> Result<Self::BinaryVersion> {
137        self.node
138            .npm_package_latest_version(Self::PACKAGE_NAME)
139            .await
140    }
141
142    fn fetch_server_binary(
143        &self,
144        _latest_version: Self::BinaryVersion,
145        container_dir: std::path::PathBuf,
146        delegate: &Arc<dyn LspAdapterDelegate>,
147    ) -> impl Send + Future<Output = Result<lsp::LanguageServerBinary>> + use<> {
148        let node = self.node.clone();
149        let delegate = delegate.clone();
150
151        async move {
152            let server_path = container_dir
153                .join("node_modules")
154                .join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
155
156            node.npm_install_latest_packages(&container_dir, &[Self::PACKAGE_NAME])
157                .await?;
158
159            let env = delegate.shell_env().await;
160            Ok(LanguageServerBinary {
161                path: node.binary_path().await?,
162                env: Some(env),
163                arguments: vec![server_path.into(), "start".into()],
164            })
165        }
166    }
167}
168
169#[async_trait(?Send)]
170impl LspAdapter for BashLspAdapter {
171    fn name(&self) -> LanguageServerName {
172        LanguageServerName::new_static(Self::PACKAGE_NAME)
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use gpui::{AppContext as _, BorrowAppContext, Context, TestAppContext};
179    use language::{AutoindentMode, Buffer};
180    use settings::SettingsStore;
181    use std::num::NonZeroU32;
182    use unindent::Unindent;
183    use util::test::marked_text_offsets;
184
185    #[gpui::test]
186    async fn test_bash_autoindent(cx: &mut TestAppContext) {
187        cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
188        let language = crate::language("bash", tree_sitter_bash::LANGUAGE.into());
189        cx.update(|cx| {
190            let test_settings = SettingsStore::test(cx);
191            cx.set_global(test_settings);
192            cx.update_global::<SettingsStore, _>(|store, cx| {
193                store.update_user_settings(cx, |s| {
194                    s.project.all_languages.defaults.tab_size = NonZeroU32::new(2)
195                });
196            });
197        });
198
199        cx.new(|cx| {
200            let mut buffer = Buffer::local("", cx).with_language(language, cx);
201
202            let expect_indents_to =
203                |buffer: &mut Buffer, cx: &mut Context<Buffer>, input: &str, expected: &str| {
204                    buffer.edit(
205                        [(0..buffer.len(), input)],
206                        Some(AutoindentMode::EachLine),
207                        cx,
208                    );
209                    assert_eq!(buffer.text(), expected);
210                };
211
212            // Do not indent after shebang
213            expect_indents_to(
214                &mut buffer,
215                cx,
216                "#!/usr/bin/env bash\n#",
217                "#!/usr/bin/env bash\n#",
218            );
219
220            // indent function correctly
221            expect_indents_to(
222                &mut buffer,
223                cx,
224                "function name() {\necho \"Hello, World!\"\n}",
225                "function name() {\n  echo \"Hello, World!\"\n}",
226            );
227
228            // indent if-else correctly
229            expect_indents_to(
230                &mut buffer,
231                cx,
232                "if true;then\nfoo\nelse\nbar\nfi",
233                "if true;then\n  foo\nelse\n  bar\nfi",
234            );
235
236            // indent if-elif-else correctly
237            expect_indents_to(
238                &mut buffer,
239                cx,
240                "if true;then\nfoo\nelif true;then\nbar\nelse\nbar\nfi",
241                "if true;then\n  foo\nelif true;then\n  bar\nelse\n  bar\nfi",
242            );
243
244            // indent case-when-else correctly
245            expect_indents_to(
246                &mut buffer,
247                cx,
248                "case $1 in\nfoo) echo \"Hello, World!\";;\n*) echo \"Unknown argument\";;\nesac",
249                "case $1 in\n  foo) echo \"Hello, World!\";;\n  *) echo \"Unknown argument\";;\nesac",
250            );
251
252            // indent for-loop correctly
253            expect_indents_to(
254                &mut buffer,
255                cx,
256                "for i in {1..10};do\nfoo\ndone",
257                "for i in {1..10};do\n  foo\ndone",
258            );
259
260            // indent while-loop correctly
261            expect_indents_to(
262                &mut buffer,
263                cx,
264                "while true; do\nfoo\ndone",
265                "while true; do\n  foo\ndone",
266            );
267
268            // indent array correctly
269            expect_indents_to(
270                &mut buffer,
271                cx,
272                "array=(\n1\n2\n3\n)",
273                "array=(\n  1\n  2\n  3\n)",
274            );
275
276            // indents non-"function" function correctly
277            expect_indents_to(
278                &mut buffer,
279                cx,
280                "foo() {\necho \"Hello, World!\"\n}",
281                "foo() {\n  echo \"Hello, World!\"\n}",
282            );
283
284            let (input, offsets) = marked_text_offsets(
285                &r#"
286                if foo; then
287
288                else
289                  3
290                fi
291                "#
292                .unindent(),
293            );
294
295            buffer.edit([(0..buffer.len(), input)], None, cx);
296            buffer.edit(
297                [(offsets[0]..offsets[0], "\n")],
298                Some(AutoindentMode::EachLine),
299                cx,
300            );
301            buffer.edit(
302                [(offsets[0] + 3..offsets[0] + 3, "elif")],
303                Some(AutoindentMode::EachLine),
304                cx,
305            );
306            let expected = r#"
307                if foo; then
308                  1
309                elif
310                else
311                  3
312                fi
313                "#
314            .unindent();
315
316            pretty_assertions::assert_eq!(buffer.text(), expected);
317
318            buffer
319        });
320    }
321}
322
Served at tenant.openagents/omega Member data and write actions are omitted.