1use anyhow::{anyhow, Result};
2use async_trait::async_trait;
3use futures::StreamExt;
4use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
5use lsp::LanguageServerBinary;
6use node_runtime::NodeRuntime;
7use smol::fs;
8use std::{
9 any::Any,
10 ffi::OsString,
11 path::{Path, PathBuf},
12 sync::Arc,
13};
14use util::{maybe, ResultExt};
15
16const SERVER_PATH: &str = "node_modules/dockerfile-language-server-nodejs/bin/docker-langserver";
17
18fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
19 vec![server_path.into(), "--stdio".into()]
20}
21
22pub struct DockerfileLspAdapter {
23 node: Arc<dyn NodeRuntime>,
24}
25
26impl DockerfileLspAdapter {
27 pub fn new(node: Arc<dyn NodeRuntime>) -> Self {
28 Self { node }
29 }
30}
31
32#[async_trait(?Send)]
33impl LspAdapter for DockerfileLspAdapter {
34 fn name(&self) -> LanguageServerName {
35 LanguageServerName("docker-langserver".into())
36 }
37
38 async fn fetch_latest_server_version(
39 &self,
40 _: &dyn LspAdapterDelegate,
41 ) -> Result<Box<dyn 'static + Send + Any>> {
42 Ok(Box::new(
43 self.node
44 .npm_package_latest_version("dockerfile-language-server-nodejs")
45 .await?,
46 ) as Box<_>)
47 }
48
49 async fn fetch_server_binary(
50 &self,
51 latest_version: Box<dyn 'static + Send + Any>,
52 container_dir: PathBuf,
53 _: &dyn LspAdapterDelegate,
54 ) -> Result<LanguageServerBinary> {
55 let latest_version = latest_version.downcast::<String>().unwrap();
56 let server_path = container_dir.join(SERVER_PATH);
57 let package_name = "dockerfile-language-server-nodejs";
58
59 let should_install_language_server = self
60 .node
61 .should_install_npm_package(package_name, &server_path, &container_dir, &latest_version)
62 .await;
63
64 if should_install_language_server {
65 self.node
66 .npm_install_packages(&container_dir, &[(package_name, latest_version.as_str())])
67 .await?;
68 }
69
70 Ok(LanguageServerBinary {
71 path: self.node.binary_path().await?,
72 env: None,
73 arguments: server_binary_arguments(&server_path),
74 })
75 }
76
77 async fn cached_server_binary(
78 &self,
79 container_dir: PathBuf,
80 _: &dyn LspAdapterDelegate,
81 ) -> Option<LanguageServerBinary> {
82 get_cached_server_binary(container_dir, &*self.node).await
83 }
84
85 async fn installation_test_binary(
86 &self,
87 container_dir: PathBuf,
88 ) -> Option<LanguageServerBinary> {
89 get_cached_server_binary(container_dir, &*self.node).await
90 }
91}
92
93async fn get_cached_server_binary(
94 container_dir: PathBuf,
95 node: &dyn NodeRuntime,
96) -> Option<LanguageServerBinary> {
97 maybe!(async {
98 let mut last_version_dir = None;
99 let mut entries = fs::read_dir(&container_dir).await?;
100 while let Some(entry) = entries.next().await {
101 let entry = entry?;
102 if entry.file_type().await?.is_dir() {
103 last_version_dir = Some(entry.path());
104 }
105 }
106
107 let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
108 let server_path = last_version_dir.join(SERVER_PATH);
109 if server_path.exists() {
110 Ok(LanguageServerBinary {
111 path: node.binary_path().await?,
112 env: None,
113 arguments: server_binary_arguments(&server_path),
114 })
115 } else {
116 Err(anyhow!(
117 "missing executable in directory {:?}",
118 last_version_dir
119 ))
120 }
121 })
122 .await
123 .log_err()
124}