html_languageservice/
html_language_types.rs

1use std::{
2    path::{Component, PathBuf},
3    str::FromStr,
4};
5
6use lsp_types::{ClientCapabilities, Uri};
7
8#[derive(Default)]
9pub struct HTMLLanguageServiceOptions {
10    /// Abstract file system access away from the service.
11    /// Used for path completion, etc.
12    pub file_system_provider: Option<Box<dyn FileSystemProvider>>,
13
14    /// Describes the LSP capabilities the client supports.
15    pub client_capabilities: Option<ClientCapabilities>,
16
17    /// Whether the tag name and attribute name are case-sensitive, the default is `false`.
18    pub case_sensitive: Option<bool>,
19}
20
21pub trait FileSystemProvider: Send + Sync {
22    fn stat(&self, uri: DocumentUri) -> FileStat;
23
24    fn read_directory(&self, uri: DocumentUri) -> (String, FileType);
25}
26
27pub type DocumentUri = String;
28
29pub struct FileStat {
30    /// The type of the file, e.g. is a regular file, a directory, or symbolic link
31    /// to a file.
32    pub file_type: FileType,
33    /// The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
34    pub ctime: i128,
35    /// The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
36    pub mtime: i128,
37    /// The size in bytes.
38    pub size: usize,
39}
40
41pub enum FileType {
42    /// The file type is unknown.
43    Unknown = 0,
44    /// A regular file.
45    File = 1,
46    /// A directory.
47    Directory = 2,
48    /// A symbolic link to a file.
49    SymbolicLink = 64,
50}
51
52pub trait DocumentContext {
53    fn resolve_reference(&self, reference: &str, base: &str) -> Option<String>;
54}
55
56pub struct DefaultDocumentContext;
57
58impl DocumentContext for DefaultDocumentContext {
59    fn resolve_reference(&self, reference: &str, base: &str) -> Option<String> {
60        if let Ok(uri) = Uri::from_str(reference) {
61            let base_uri = Uri::from_str(base).unwrap();
62            if uri.scheme().is_some() {
63                return Some(uri.to_string());
64            }
65
66            let scheme = base_uri.scheme().unwrap();
67            let auth = if let Some(auth) = uri.authority() {
68                auth.to_string()
69            } else if let Some(auth) = base_uri.authority() {
70                auth.to_string()
71            } else {
72                "".to_string()
73            };
74
75            let mut base_uri_path = PathBuf::from_str(&base_uri.path().to_string()).unwrap();
76            if !base.ends_with("/") {
77                base_uri_path.pop();
78            }
79            let uri_path = PathBuf::from_str(&uri.path().to_string()).unwrap();
80            let path = base_uri_path.join(uri_path);
81            let suffix = if reference.ends_with("/") || reference.ends_with(".") {
82                "/"
83            } else {
84                ""
85            };
86            let mut new_path = vec![];
87            let mut components = path.components();
88            let mut base_uri_components = base_uri_path.components();
89            let base_prefix = {
90                match base_uri_components.next() {
91                    Some(Component::Prefix(preifx)) => Some(Component::Prefix(preifx)),
92                    Some(Component::RootDir) => {
93                        if let Some(Component::Normal(v)) = base_uri_components.next() {
94                            if v.to_string_lossy().contains(":") {
95                                Some(Component::Normal(v))
96                            } else {
97                                None
98                            }
99                        } else {
100                            None
101                        }
102                    }
103                    _ => None,
104                }
105            };
106            // first
107            match components.next() {
108                Some(Component::Prefix(prefix)) => new_path.push(Component::Prefix(prefix)),
109                Some(Component::RootDir) => {
110                    let add_prefx = if let Some(Component::Normal(v)) = components.clone().next() {
111                        !v.to_string_lossy().contains(":")
112                    } else {
113                        true
114                    };
115                    if add_prefx && base_prefix.is_some() {
116                        if let Some(Component::Prefix(prefix)) = base_prefix {
117                            new_path.push(Component::Prefix(prefix));
118                            new_path.push(Component::RootDir);
119                        } else if let Some(Component::Normal(prefix)) = base_prefix {
120                            new_path.push(Component::RootDir);
121                            new_path.push(Component::Normal(prefix));
122                        }
123                    } else {
124                        new_path.push(Component::RootDir);
125                    }
126                }
127                Some(Component::Normal(v)) => {
128                    if let Some(Component::Prefix(prefix)) = base_prefix {
129                        new_path.push(Component::Prefix(prefix));
130                        new_path.push(Component::RootDir);
131                    } else if let Some(Component::Normal(prefix)) = base_prefix {
132                        new_path.push(Component::RootDir);
133                        new_path.push(Component::Normal(prefix));
134                    } else {
135                        new_path.push(Component::RootDir);
136                    }
137                    new_path.push(Component::Normal(v));
138                }
139                _ => {}
140            }
141            // other
142            for component in components {
143                match component {
144                    Component::Prefix(prefix) => new_path.push(Component::Prefix(prefix)),
145                    Component::RootDir => new_path.push(Component::RootDir),
146                    Component::CurDir => {}
147                    Component::ParentDir => {
148                        new_path.pop();
149                    }
150                    Component::Normal(v) => new_path.push(Component::Normal(v)),
151                }
152            }
153            let new_path = new_path.iter().collect::<PathBuf>();
154            let new_path = new_path.to_string_lossy();
155
156            Some(format!("{}://{}{}{}", scheme, auth, new_path, suffix))
157        } else {
158            None
159        }
160    }
161}