Skip to content

Commit

Permalink
feat(component): autocomplet for input names
Browse files Browse the repository at this point in the history
Inputs for components are now autocompleted.
  • Loading branch information
alesbrelih committed May 3, 2024
1 parent d03055c commit 1cf480f
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 88 deletions.
169 changes: 124 additions & 45 deletions src/gitlab_ci_ls_parser/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, path::PathBuf, sync::Mutex, time::Instant};

use anyhow::anyhow;
use log::{debug, error, info};
use log::{debug, error, info, warn};
use lsp_server::{Notification, Request};
use lsp_types::{
request::GotoTypeDefinitionParams, CompletionParams, Diagnostic, DidChangeTextDocumentParams,
Expand All @@ -10,7 +10,7 @@ use lsp_types::{
};

use super::{
fs_utils, parser, parser_utils, treesitter, CompletionResult, DefinitionResult,
fs_utils, parser, parser_utils, treesitter, CompletionResult, Component, DefinitionResult,
DiagnosticsResult, GitlabElement, HoverResult, IncludeInformation, LSPCompletion, LSPConfig,
LSPLocation, LSPPosition, LSPResult, Range, ReferencesResult, RuleReference,
};
Expand All @@ -22,6 +22,7 @@ pub struct LSPHandlers {
nodes: Mutex<HashMap<String, HashMap<String, String>>>,
stages: Mutex<HashMap<String, GitlabElement>>,
variables: Mutex<HashMap<String, GitlabElement>>,
components: Mutex<HashMap<String, Component>>,
indexing_in_progress: Mutex<bool>,
parser: Box<dyn parser::Parser>,
}
Expand All @@ -32,6 +33,7 @@ impl LSPHandlers {
let nodes = Mutex::new(HashMap::new());
let stages = Mutex::new(HashMap::new());
let variables = Mutex::new(HashMap::new());
let components = Mutex::new(HashMap::new());
let indexing_in_progress = Mutex::new(false);

let events = LSPHandlers {
Expand All @@ -40,6 +42,7 @@ impl LSPHandlers {
nodes,
stages,
variables,
components,
indexing_in_progress,
parser: Box::new(parser::ParserImpl::new(
cfg.remote_urls,
Expand Down Expand Up @@ -149,6 +152,8 @@ impl LSPHandlers {

let mut all_variables = self.variables.lock().unwrap();

let mut all_components = self.components.lock().unwrap();

if let Some(results) = self.parser.parse_contents(
&params.text_document.uri,
&params.content_changes.first()?.text,
Expand Down Expand Up @@ -182,6 +187,11 @@ impl LSPHandlers {
info!("found variable: {:?}", &variable);
all_variables.insert(variable.key.clone(), variable);
}

for component in results.components {
info!("found component: {:?}", &component);
all_components.insert(component.uri.clone(), component);
}
}

info!("ONCHANGE ELAPSED: {:?}", start.elapsed());
Expand Down Expand Up @@ -402,48 +412,30 @@ impl LSPHandlers {
basic: None,
component: Some(component),
} => {
error!("HELLO: {:?}", component);
let file = component.uri?;
let file = file.trim_matches('"').trim_matches('\'').to_string();
let component_uri = component
.uri
.trim_matches('"')
.trim_matches('\'')
.to_string();

let component_info =
match parser_utils::ParserUtils::extract_component_from_uri(&file) {
Ok(c) => c,
Err(err) => {
error!("error extracting component info from uri; got: {err}");

return None;
}
};

let repo_dest = parser_utils::ParserUtils::get_component_dest_dir(
&self.cfg.cache_path,
&component_info,
);

if let Ok(component) =
parser_utils::ParserUtils::get_component(&repo_dest, &component_info.component)
{
return store
.keys()
.find(|uri| uri.ends_with(&component.uri))
.map(|uri| LSPLocation {
uri: uri.clone(),
range: Range {
start: LSPPosition {
line: 0,
character: 0,
},
end: LSPPosition {
line: 0,
character: 0,
},
self.components
.lock()
.unwrap()
.values()
.find(|c| c.uri == component_uri)
.map(|c| LSPLocation {
uri: c.local_path.clone(),
range: Range {
start: LSPPosition {
line: 0,
character: 0,
},
});
}

error!("could not find component for: {file}; at: {repo_dest}");
None
end: LSPPosition {
line: 0,
character: 0,
},
},
})
}
IncludeInformation {
local: None,
Expand Down Expand Up @@ -537,6 +529,15 @@ impl LSPHandlers {
parser::PositionType::Extend => self.on_completion_extends(line, position).ok()?,
parser::PositionType::Variable => self.on_completion_variables(line, position).ok()?,
parser::PositionType::Needs(_) => self.on_completion_needs(line, position).ok()?,
parser::PositionType::Include(IncludeInformation {
remote: None,
remote_url: None,
local: None,
basic: None,
component: Some(component),
}) => self
.on_completion_component(line, position, &component)
.ok()?,
parser::PositionType::RuleReference(_) => {
self.on_completion_rule_reference(line, position).ok()?
}
Expand Down Expand Up @@ -626,7 +627,7 @@ impl LSPHandlers {
|(node_key, node_description)| -> anyhow::Result<LSPCompletion> {
Ok(LSPCompletion {
label: node_key.to_string(),
details: Some(node_description.to_string()),
details: Some(format!("```yaml\r\n{node_description}\r\n```")),
location: LSPLocation {
range: Range {
start: LSPPosition {
Expand Down Expand Up @@ -727,7 +728,7 @@ impl LSPHandlers {
|(node_key, node_description)| -> anyhow::Result<LSPCompletion> {
Ok(LSPCompletion {
label: node_key.clone(),
details: Some(node_description.clone()),
details: Some(format!("```yaml\r\n{node_description}\r\n```")),
location: LSPLocation {
range: Range {
start: LSPPosition {
Expand Down Expand Up @@ -776,7 +777,7 @@ impl LSPHandlers {
|(node_key, node_description)| -> anyhow::Result<LSPCompletion> {
Ok(LSPCompletion {
label: node_key.clone(),
details: Some(node_description.clone()),
details: Some(format!("```yaml\r\n{node_description}\r\n```")),
location: LSPLocation {
range: Range {
start: LSPPosition {
Expand Down Expand Up @@ -808,6 +809,7 @@ impl LSPHandlers {
let mut all_nodes = self.nodes.lock().unwrap();
let mut all_stages = self.stages.lock().unwrap();
let mut all_variables = self.variables.lock().unwrap();
let mut all_components = self.components.lock().unwrap();

info!("importing files from base");
let base_uri = format!("{}base", self.cfg.cache_path);
Expand Down Expand Up @@ -841,6 +843,11 @@ impl LSPHandlers {
info!("found variable: {:?}", &variable);
all_variables.insert(variable.key.clone(), variable);
}

for component in results.components {
info!("found component: {:?}", &component);
all_components.insert(component.uri.clone(), component);
}
}
}

Expand Down Expand Up @@ -896,6 +903,11 @@ impl LSPHandlers {
info!("found variable: {:?}", &variable);
all_variables.insert(variable.key.clone(), variable);
}

for component in results.components {
info!("found component: {:?}", &component);
all_components.insert(component.uri.clone(), component);
}
}

error!("INDEX WORKSPACE ELAPSED: {:?}", start.elapsed());
Expand Down Expand Up @@ -1084,4 +1096,71 @@ impl LSPHandlers {
locations: references,
}))
}

#[allow(clippy::unnecessary_wraps)]
fn on_completion_component(
&self,
line: &str,
position: Position,
component: &Component,
) -> anyhow::Result<Vec<LSPCompletion>> {
if !component.inputs.iter().any(|i| i.hovered) {
return Ok(vec![]);
}

let word = parser_utils::ParserUtils::word_before_cursor(
line,
position.character as usize,
|c: char| c.is_whitespace(),
);

let after =
parser_utils::ParserUtils::word_after_cursor(line, position.character as usize, |c| {
c.is_whitespace() || c == ':'
});

let components_store = self.components.lock().unwrap();
let Some(component_spec) = components_store.get(&component.uri) else {
warn!(
"could not find component spec; indexing went wrong!; searching for {}",
component.uri
);

return Ok(vec![]);
};

// filter out those that were already used
let valid_input_autocompletes: Vec<super::ComponentInput> = component_spec
.inputs
.iter()
.filter(|&i| !component.inputs.iter().any(|ci| ci.key == i.key))
.cloned() // Clone each element to get an owned version
.collect();

let items = valid_input_autocompletes
.into_iter()
.filter(|i| i.key.starts_with(word))
.flat_map(|i| -> anyhow::Result<LSPCompletion> {
Ok(LSPCompletion {
label: i.key.clone(),
details: Some(i.autocomplete_details()),
location: LSPLocation {
range: Range {
start: LSPPosition {
line: position.line,
character: position.character - u32::try_from(word.len())?,
},
end: LSPPosition {
line: position.line,
character: position.character + u32::try_from(after.len())?,
},
},
..Default::default()
},
})
})
.collect();

Ok(items)
}
}
2 changes: 1 addition & 1 deletion src/gitlab_ci_ls_parser/messages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ fn completion(result: CompletionResult) -> Message {
item.documentation =
Some(lsp_types::Documentation::MarkupContent(MarkupContent {
kind: lsp_types::MarkupKind::Markdown,
value: format!("```yaml\r\n{}\r\n```", documentation.clone()),
value: documentation.clone(),
}));
}

Expand Down
79 changes: 68 additions & 11 deletions src/gitlab_ci_ls_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct ParseResults {
pub files: Vec<GitlabFile>,
pub nodes: Vec<GitlabElement>,
pub stages: Vec<GitlabElement>,
pub components: Vec<Component>,
pub variables: Vec<GitlabElement>,
}

Expand All @@ -118,17 +119,6 @@ pub struct RemoteInclude {
pub file: Option<String>,
}

#[derive(Debug, Default)]
pub struct ComponentInput {
pub key: String,
pub hovered: bool,
}
#[derive(Debug, Default)]
pub struct Component {
pub uri: Option<String>,
pub inputs: Vec<ComponentInput>,
}

impl RemoteInclude {
pub fn is_valid(&self) -> bool {
self.project.is_some() && self.reference.is_some() && self.file.is_some()
Expand All @@ -153,3 +143,70 @@ pub struct RuleReference {
pub struct NodeDefinition {
pub name: String,
}

#[derive(Debug, Default, Clone)]
pub struct ComponentInput {
pub key: String,
pub default: Option<serde_yaml::Value>,
pub description: Option<String>,
pub options: Option<Vec<String>>,
pub regex: Option<String>,
pub prop_type: Option<String>,
pub hovered: bool,
}

impl ComponentInput {
pub fn autocomplete_details(&self) -> String {
let mut details = String::new();

if let Some(d) = &self.description {
details = format!(
"## Description:
{d}
"
);
}

if let Some(d) = &self.prop_type {
details = format!(
"{}
## Type:
{}
",
details,
d.as_str()
);
}

if let Some(d) = &self.default {
details = format!(
"{}
## Default:
{}
",
details,
d.as_str().unwrap_or_default()
);
}

if let Some(d) = &self.regex {
details = format!(
"{}
## Regex:
{}
",
details,
d.as_str()
);
}

details
}
}

#[derive(Debug, Default)]
pub struct Component {
pub uri: String,
pub local_path: String,
pub inputs: Vec<ComponentInput>,
}
Loading

0 comments on commit 1cf480f

Please sign in to comment.