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::{async_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]
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 version: Box<dyn 'static + Send + Any>,
52 container_dir: PathBuf,
53 _: &dyn LspAdapterDelegate,
54 ) -> Result<LanguageServerBinary> {
55 let version = version.downcast::<String>().unwrap();
56 let server_path = container_dir.join(SERVER_PATH);
57
58 if fs::metadata(&server_path).await.is_err() {
59 self.node
60 .npm_install_packages(
61 &container_dir,
62 &[("dockerfile-language-server-nodejs", version.as_str())],
63 )
64 .await?;
65 }
66
67 Ok(LanguageServerBinary {
68 path: self.node.binary_path().await?,
69 env: None,
70 arguments: server_binary_arguments(&server_path),
71 })
72 }
73
74 async fn cached_server_binary(
75 &self,
76 container_dir: PathBuf,
77 _: &dyn LspAdapterDelegate,
78 ) -> Option<LanguageServerBinary> {
79 get_cached_server_binary(container_dir, &*self.node).await
80 }
81
82 async fn installation_test_binary(
83 &self,
84 container_dir: PathBuf,
85 ) -> Option<LanguageServerBinary> {
86 get_cached_server_binary(container_dir, &*self.node).await
87 }
88}
89
90async fn get_cached_server_binary(
91 container_dir: PathBuf,
92 node: &dyn NodeRuntime,
93) -> Option<LanguageServerBinary> {
94 async_maybe!({
95 let mut last_version_dir = None;
96 let mut entries = fs::read_dir(&container_dir).await?;
97 while let Some(entry) = entries.next().await {
98 let entry = entry?;
99 if entry.file_type().await?.is_dir() {
100 last_version_dir = Some(entry.path());
101 }
102 }
103
104 let last_version_dir = last_version_dir.ok_or_else(|| anyhow!("no cached binary"))?;
105 let server_path = last_version_dir.join(SERVER_PATH);
106 if server_path.exists() {
107 Ok(LanguageServerBinary {
108 path: node.binary_path().await?,
109 env: None,
110 arguments: server_binary_arguments(&server_path),
111 })
112 } else {
113 Err(anyhow!(
114 "missing executable in directory {:?}",
115 last_version_dir
116 ))
117 }
118 })
119 .await
120 .log_err()
121}