use log::{info, warn};
use crate::consts::*;
use crate::cqlsh::{self, Column};
use crate::lsp::Backend;
use tower_lsp::lsp_types::*;
impl Backend {
pub fn is_use_keyspace_line(&self, s: &str) -> bool {
let input: Vec<char> = s.trim().chars().collect();
if input.len() < 7 {
return false;
}
let keyword: String = input[0..3].iter().collect();
if keyword.eq_ignore_ascii_case("use") == false {
return false;
}
if input[3] != '"' || input[input.len() - 2] != '"' || input[input.len() - 1] != ';' {
return false;
}
true
}
pub async fn get_keyspaces(&self) -> Vec<String> {
let items = cqlsh::query_keyspaces(&self.config).await;
match items {
Ok(r) => r.into_iter().collect(),
Err(_) => {
vec![]
}
}
}
pub fn should_suggest_keyspaces(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
if let Some(semi_colon_pos) = line.find(&";") {
if position.character > semi_colon_pos as u32 {
return false;
}
}
let mut index: usize = 0;
let mut met_bracket = false;
let trimmed_prefix = prefix.trim_end().to_lowercase();
let split: Vec<&str> = trimmed_prefix.split(' ').collect();
for (idx, ch) in line.chars().enumerate() {
if idx >= position.character as usize {
break;
}
if met_bracket && (ch == '"' || ch == '\'') {
return false;
}
if !met_bracket && (ch == '"' || ch == '\'') {
met_bracket = true;
}
}
if !split.contains(&"use") {
return false;
}
if split.len() > 1 && split[0] != "use" {
return false;
}
for c in line.chars().enumerate() {
if c.1 == ';' && c.0 < position.character as usize {
return false;
}
}
true
}
pub fn should_suggest_drop_keyspaces(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_keyspace_kw = lw.contains("keyspace");
if !contains_drop_kw || !contains_keyspace_kw {
return false;
}
if let Some(ksp_index) = lw.rfind("keyspace") {
if position.character as usize <= ksp_index + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "keyspace" && !prefix.contains(";")
{
return true;
}
false
}
pub fn should_suggest_drop_aggregate(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_aggregate_kw = lw.contains("aggregate");
if !contains_drop_kw || !contains_aggregate_kw {
return false;
}
if let Some(ksp_index) = lw.rfind("aggregate") {
if position.character as usize <= ksp_index + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2
&& split[0] == "drop"
&& split[1] == "aggregate"
&& !prefix.contains(";")
{
return true;
}
false
}
pub fn should_suggest_drop_function(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_function_kw = lw.contains("function");
if !contains_drop_kw || !contains_function_kw {
return false;
}
if let Some(ksp_function) = lw.rfind("function") {
if position.character as usize <= ksp_function + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "function" && !prefix.contains(";")
{
return true;
}
false
}
pub fn should_suggest_drop_indexes(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_index_kw = lw.contains("index");
if !contains_drop_kw || !contains_index_kw {
return false;
}
if let Some(ksp_index) = lw.rfind("index") {
if position.character as usize <= ksp_index + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "index" && !prefix.contains(";") {
return true;
}
false
}
pub fn should_suggest_drop_types(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_type_kw = lw.contains("type");
if !contains_drop_kw || !contains_type_kw {
return false;
}
if let Some(ksp_type) = lw.rfind("type") {
if position.character as usize <= ksp_type + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "type" && !prefix.contains(";") {
return true;
}
false
}
pub fn should_suggest_drop_views(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_view_kw = lw.contains("view");
if !contains_drop_kw || !contains_view_kw {
return false;
}
if let Some(ksp_view) = lw.rfind("view") {
if position.character as usize <= ksp_view + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "view" && !prefix.contains(";") {
return true;
}
false
}
pub fn should_suggest_drop_tables(&self, line: &str, position: &Position) -> bool {
let lw = line.to_lowercase();
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let contains_drop_kw = lw.contains("drop");
let contains_keyspace_kw = lw.contains("table");
if !contains_drop_kw || !contains_keyspace_kw {
return false;
}
if let Some(ksp_index) = lw.rfind("table") {
if position.character as usize <= ksp_index + 8 {
return false;
}
}
let split: Vec<&str> = lw.split(' ').collect();
if split.len() >= 2 && split[0] == "drop" && split[1] == "table" && !prefix.contains(";") {
return true;
}
false
}
pub fn get_graph_engine_types(&self) -> Vec<String> {
vec!["Core".to_string(), "Classic".to_string()]
}
pub fn should_suggest_graph_engine_types(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let trimmed_prefix = prefix.trim_end();
let splitted: Vec<&str> = trimmed_prefix.split(' ').collect();
if splitted.len() < 2 || (splitted[0] != "graph_engine" && splitted[1] != "=") {
return false;
}
true
}
pub fn get_available_command_sequences(
&self,
) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
let items = vec![
CompletionItem {
label: "ALTER".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("ALTER KEYSPACE cql command".to_string()),
documentation: Some(Documentation::String(
"ALTER KEYSPACE cql command".to_string(),
)),
insert_text: Some(r#"ALTER KEYSPACE $0";"#.to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
},
CompletionItem {
label: "ALTER".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("ALTER MATERIALIZED VIEW cql command".to_string()),
documentation: Some(Documentation::String(
"ALTER MATERIALIZED VIEW cql command".to_string(),
)),
insert_text: Some(r#"ALTER MATERIALIZED VIEW $0";"#.to_string()),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
},
];
Ok(Some(CompletionResponse::Array(items)))
}
pub fn should_suggest_command_sequence(&self, line: &str, position: &Position) -> bool {
false
}
pub async fn should_suggest_keywords(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
if prefix.contains("--")
|| prefix.contains("//")
|| prefix.contains("/*")
|| line.ends_with("*/")
{
return false;
}
if let Some(semi_colon_pos) = line.find(&";") {
if position.character > semi_colon_pos as u32 {
return false;
}
}
let lw = line.to_lowercase();
if lw.contains("use") {
return false;
}
if lw.contains("select") && lw.contains("from") {
if let Some(from_pos) = line.find(&";") {
if position.character < (from_pos + 1) as u32 {
return false;
}
}
}
let trimmed_prefix = prefix.trim_end().to_lowercase();
let split: Vec<&str> = trimmed_prefix.split(' ').collect();
if split.len() > 0 && split[split.len() - 1].contains(";") {
return false;
}
if split.len() >= 2
&& (split[split.len() - 1].contains("from") || split[split.len() - 2].contains("from"))
{
return false;
}
if line.contains("(") && !line.contains(")") {
return false;
}
if line.contains("(") && line.contains(")") {
let posx = line.find(&")").unwrap();
if posx >= position.character as usize {
return false;
}
}
if lw.contains("drop")
&& (lw.contains("table")
|| lw.contains("index")
|| lw.contains("keyspace")
|| (lw.contains("materialized") && lw.contains("view"))
|| lw.contains("role")
|| (lw.contains("search") && lw.contains("index"))
|| lw.contains("type")
|| lw.contains("user")
|| lw.contains("function")
|| lw.contains("aggregate"))
&& split.len() >= 3
{
return false;
}
let current = self.current_document.read().await;
if let Some(ref document_lock) = *current {
let document = document_lock.read().await;
let splitx: Vec<&str> = document.text.split('\n').collect();
if self.is_line_in_multiline_comment_ref(line, position.line as usize, &splitx) {
return false;
}
let mut index_up = position.line as usize;
while index_up > 0 && index_up < splitx.len() {
if (!splitx[index_up].contains("(")
&& KEYWORDS_STRINGS_LWC.contains(&splitx[index_up].to_string()))
|| splitx[index_up].contains(";")
{
break;
}
if splitx[index_up].contains("(") {
return false;
}
index_up -= 1;
}
if index_up < splitx.len() && splitx[index_up].contains("(") {
return false;
}
}
if lw.contains("create") && lw.contains("if not exists") {
let mut index = lw.rfind(&"exists").unwrap();
index += 6;
if position.character > index as u32
&& (split[split.len() - 1] == "exists" || split[split.len() - 2] == "exists")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("table") {
let mut index = lw.rfind(&"table").unwrap();
index += 5;
if position.character > index as u32
&& (split[split.len() - 1] == "table" || split[split.len() - 2] == "table")
{
return false;
}
}
if lw.contains("create") && lw.contains("aggregate") {
let mut index = lw.rfind(&"aggregate").unwrap();
index += 9;
if position.character > index as u32
&& (split[split.len() - 1] == "aggregate" || split[split.len() - 2] == "aggregate")
{
return false;
}
}
if lw.contains("create") && lw.contains("function") {
let mut index = lw.rfind(&"function").unwrap();
index += 8;
if position.character > index as u32
&& (split[split.len() - 1] == "function" || split[split.len() - 2] == "function")
{
return false;
}
}
if lw.contains("create") && lw.contains("index") {
let mut index = lw.rfind(&"index").unwrap();
index += 5;
if position.character > index as u32
&& (split[split.len() - 1] == "index" || split[split.len() - 2] == "index")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("keyspace") {
let mut keyspace = lw.rfind(&"keyspace").unwrap();
keyspace += 8;
if position.character > keyspace as u32
&& (split[split.len() - 1] == "keyspace" || split[split.len() - 2] == "keyspace")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("view") {
let mut keyspace = lw.rfind(&"view").unwrap();
keyspace += 4;
if position.character > keyspace as u32
&& (split[split.len() - 1] == "view" || split[split.len() - 2] == "view")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("role") {
let mut keyspace = lw.rfind(&"role").unwrap();
keyspace += 4;
if position.character > keyspace as u32
&& (split[split.len() - 1] == "role" || split[split.len() - 2] == "role")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("type") {
let mut keyspace = lw.rfind(&"type").unwrap();
keyspace += 4;
if position.character > keyspace as u32
&& (split[split.len() - 1] == "type" || split[split.len() - 2] == "type")
{
return false;
}
}
if (lw.contains("create") || lw.contains("alter")) && lw.contains("user") {
let mut keyspace = lw.rfind(&"user").unwrap();
keyspace += 4;
if position.character > keyspace as u32
&& (split[split.len() - 1] == "user" || split[split.len() - 2] == "user")
{
return false;
}
}
if split.len() >= 2
&& (split[split.len() - 1].contains("and") || split[split.len() - 2].contains("and"))
{
return false;
}
if split.len() >= 2
&& (split[split.len() - 1].contains("where")
|| split[split.len() - 2].contains("where"))
{
return false;
}
return true;
}
#[warn(unused_mut)]
pub async fn latest_keyspace(&self, position: &Position) -> Option<String> {
let current = self.current_document.read().await;
if let Some(ref document_lock) = *current {
let document = document_lock.read().await;
let split: Vec<&str> = document.text.split('\n').collect();
let mut keyspace_latest: String = "".to_string();
let mut pos = 0;
for str in split {
let index = position.line;
if index == pos {
if keyspace_latest.len() > 0 {
return Some(keyspace_latest);
}
return None;
}
pos += 1;
if self.is_use_keyspace_line(str) {
let istr: Vec<char> = str.trim().chars().collect();
let extracted_ksp = String::from_iter(&istr[5..istr.len() - 2]);
keyspace_latest = extracted_ksp.clone();
}
}
if keyspace_latest.len() > 0 {
return Some(keyspace_latest);
}
}
None
}
pub fn should_field_be_edit(&self, line: &str) -> bool {
let lower_case = line.to_lowercase();
let line_split: Vec<&str> = lower_case.split(' ').collect();
if !line_split.contains(&"from") {
return true;
}
let mut met_from_kw = false;
for w in line_split {
if met_from_kw {
return !w.chars().any(|c| c.is_alphabetic());
}
if w == "from" {
met_from_kw = true;
}
}
true
}
pub fn get_start_offset(&self, line: &str, position: &Position) -> u32 {
let chars: Vec<char> = line.chars().collect();
let char_count = chars.len();
let mut index = (position.character as usize).min(char_count.saturating_sub(1));
while index > 0 {
if chars[index] == ' ' {
return index as u32;
}
index -= 1;
}
0
}
pub fn column_to_text_edit(&self, column: &Column, lates_keyspace: Option<&str>) -> String {
let mut result_str: String;
if let Some(keyspace) = lates_keyspace {
if keyspace == column.keyspace_name {
result_str = format!("{}, FROM {};", column.column_name, column.table_name);
} else {
result_str = format!(
"{}, FROM {}.{};",
column.column_name, column.keyspace_name, column.table_name
);
}
return result_str;
}
result_str = format!(
"{}, FROM {}.{};",
column.column_name, column.keyspace_name, column.table_name
);
result_str
}
pub async fn get_fields(
&self,
line: &str,
position: &Position,
) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
let mut tbl_name = "".to_string();
let lw_line = line.to_lowercase();
if lw_line.contains("from") {
let trimmed = lw_line.trim_end();
let split: Vec<&str> = trimmed.split(' ').collect();
if !split[split.len() - 1].contains("from") && split[split.len() - 1].len() > 1 {
let ksp_tbl = split[split.len() - 1].replace(";", "");
if ksp_tbl.contains(".") {
let keyspace_table: Vec<&str> = ksp_tbl.split('.').collect();
if keyspace_table.len() == 2 {
let ksp = keyspace_table[0];
let tbl = keyspace_table[1];
let mut items: Vec<Column> = Vec::new();
let result =
cqlsh::query_hard_scoped_fields(&self.config, &ksp, &tbl).await;
match result {
Ok(mut r) => {
items.append(&mut r);
}
Err(_) => {}
}
let mut result: Vec<CompletionItem> = Vec::new();
if self.should_field_be_edit(line) {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
let text_edit_str = self.column_to_text_edit(&item, Some(&ksp));
let text_edit = TextEdit {
range: Range {
start: Position {
line: position.line,
character: self.get_start_offset(line, position) + 1,
},
end: Position {
line: position.line,
character: line.len() as u32,
},
},
new_text: text_edit_str,
};
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::SNIPPET),
text_edit: Some(CompletionTextEdit::Edit(text_edit)),
..Default::default()
});
}
} else {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::FIELD),
insert_text: Some(format!("{}", item.column_name)),
..Default::default()
});
}
}
let mut x: Vec<CompletionItem> =
CQL_NATIVE_FUNCTIONS.iter().cloned().collect();
result.append(&mut x);
return Ok(Some(CompletionResponse::Array(result)));
}
} else {
tbl_name = ksp_tbl;
}
}
}
if let Some(keyspace) = self.latest_keyspace(position).await {
let mut items: Vec<Column> = Vec::new();
if tbl_name != "" {
let result =
cqlsh::query_hard_scoped_fields(&self.config, &keyspace, &tbl_name).await;
match result {
Ok(mut r) => {
items.append(&mut r);
}
Err(_) => {}
}
} else {
items = cqlsh::query_keyspace_scoped_fields(&self.config, &keyspace)
.await
.unwrap_or_else(|_| vec![]);
}
let mut result: Vec<CompletionItem> = Vec::new();
if self.should_field_be_edit(line) {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
let text_edit_str = self.column_to_text_edit(&item, Some(&keyspace));
let text_edit = TextEdit {
range: Range {
start: Position {
line: position.line,
character: self.get_start_offset(line, position) + 1,
},
end: Position {
line: position.line,
character: line.len() as u32,
},
},
new_text: text_edit_str,
};
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::SNIPPET),
text_edit: Some(CompletionTextEdit::Edit(text_edit)),
..Default::default()
});
}
} else {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::FIELD),
insert_text: Some(format!("{}", item.column_name)),
..Default::default()
});
}
}
let mut x: Vec<CompletionItem> = CQL_NATIVE_FUNCTIONS.iter().cloned().collect();
result.append(&mut x);
return Ok(Some(CompletionResponse::Array(result)));
}
let items = cqlsh::query_g_fields(&self.config)
.await
.unwrap_or_else(|_| vec![]);
let mut result: Vec<CompletionItem> = Vec::new();
if self.should_field_be_edit(line) {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
let text_edit_str = self.column_to_text_edit(&item, None);
let text_edit = TextEdit {
range: Range {
start: Position {
line: position.line,
character: self.get_start_offset(line, position) + 1,
},
end: Position {
line: position.line,
character: line.len() as u32,
},
},
new_text: text_edit_str,
};
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::SNIPPET),
text_edit: Some(CompletionTextEdit::Edit(text_edit)),
..Default::default()
});
}
} else {
for item in items {
if lw_line.contains(&item.column_name.to_lowercase()) {
continue;
}
result.push(CompletionItem {
label: format!(
"{} | {}.{}",
item.column_name, item.keyspace_name, item.table_name,
),
kind: Some(CompletionItemKind::VALUE),
insert_text: Some(format!("{}", item.column_name)),
..Default::default()
});
}
}
let mut x: Vec<CompletionItem> = CQL_NATIVE_FUNCTIONS.iter().cloned().collect();
result.append(&mut x);
Ok(Some(CompletionResponse::Array(result)))
}
pub fn should_suggest_fields(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let trimmed_prefix = prefix.trim_end().to_lowercase();
let splitted: Vec<&str> = trimmed_prefix.split(' ').collect();
if !splitted.contains(&"select") || splitted.contains(&"*") || splitted.contains(&"from") {
return false;
}
if splitted.contains(&"select") && splitted.len() == 1 {
return true;
}
if splitted.len() > 2 && !splitted[splitted.len() - 2].contains(",") {
return false;
}
if splitted.len() > 0
&& trimmed_prefix.len() != prefix.len()
&& !splitted[splitted.len() - 1].contains(",")
{
return false;
}
true
}
pub fn should_suggest_from(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let trimmed_prefix = prefix.trim_end().to_lowercase();
let splitted: Vec<&str> = trimmed_prefix.split(' ').collect();
if !splitted.contains(&"select")
|| splitted.contains(&"from")
|| line.to_lowercase().contains("from")
{
return false;
}
if splitted.len() == 1
&& splitted.contains(&"select")
&& trimmed_prefix.len() != prefix.len()
{
return false;
}
if splitted.len() == 2
&& splitted.contains(&"select")
&& trimmed_prefix.len() == prefix.len()
{
return false;
}
if splitted.len() >= 3
&& splitted.contains(&"select")
&& splitted[splitted.len() - 1].contains(",")
{
return false;
}
true
}
pub async fn get_table_completions(
&self,
position: &Position,
) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
info!("GET TABLE CMP");
if let Some(keyspace) = self.latest_keyspace(&position).await {
info!(
"GET TABLE CMP: enabled? {}",
self.config.has_feature("context_aware_completions")
);
let tables = cqlsh::query_keyspace_scoped_tables(&self.config, &keyspace)
.await
.unwrap_or_else(|_| vec![]);
let tables_unscoped = cqlsh::query_g_tables(&self.config)
.await
.unwrap_or_else(|_| vec![]);
let mut items = Vec::<CompletionItem>::new();
for table in tables {
items.push(CompletionItem {
label: table.table_name.clone(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some(format!("{}", table.united())),
insert_text: Some(format!(r#"{}"#, table.table_name)),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
})
}
for tablex in tables_unscoped {
items.push(CompletionItem {
label: tablex.united(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some(format!("{}", tablex.united())),
insert_text: Some(format!(r#"{}"#, tablex.united())),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
})
}
return Ok(Some(CompletionResponse::Array(items)));
}
let tables = cqlsh::query_g_tables(&self.config)
.await
.unwrap_or_else(|_| vec![]);
let mut items = Vec::<CompletionItem>::new();
for table in tables {
items.push(CompletionItem {
label: table.united(),
kind: Some(CompletionItemKind::VARIABLE),
detail: Some(format!("{}", table.united())),
insert_text: Some(format!(r#"{}"#, table.united())),
insert_text_format: Some(InsertTextFormat::SNIPPET),
..Default::default()
})
}
return Ok(Some(CompletionResponse::Array(items)));
}
pub fn is_inside_create_table_no_position(&self, line_index: usize, lines: &[String]) -> bool {
if line_index >= lines.len() {
return false;
}
let current_line = line_index;
let mut found_create_table = false;
let mut search_index = current_line;
loop {
let line_content = lines[search_index].to_lowercase();
let stripped = line_content
.find("--")
.or_else(|| line_content.find("//"))
.map(|pos| &line_content[..pos])
.unwrap_or(&line_content)
.trim()
.to_string();
if stripped.starts_with(")") || stripped.ends_with(";") {
return false;
}
if (stripped.contains("create table")
|| stripped.contains("create table if not exists"))
&& stripped.contains("(")
&& !stripped.contains(")")
{
found_create_table = true;
break;
}
if self.line_contains_cql_kw(&stripped) {
return false;
}
if search_index == 0 {
break;
}
search_index -= 1;
}
if !found_create_table {
return false;
}
for i in (current_line + 1)..lines.len() {
let line_content = &lines[i];
let stripped = line_content
.find("--")
.or_else(|| line_content.find("//"))
.map(|pos| &line_content[..pos])
.unwrap_or(line_content.as_str())
.trim();
if stripped.is_empty() {
continue;
}
if self.line_contains_cql_kw(stripped) {
return false;
}
if stripped.contains(")") {
return true;
}
}
false
}
pub async fn is_inside_multiline_comment_no_position(
&self,
line_index: usize,
document_url: &Url,
) -> bool {
let documents = self.documents.read().await;
if let Some(document) = documents.get(document_url) {
let lw_doc_text = document;
let lines: Vec<&str> = lw_doc_text.split('\n').collect();
if line_index >= lines.len() {
return false;
}
let mut search_index = line_index as isize;
let mut found_comment_start = false;
while search_index >= 0 {
let line_content = lines[search_index as usize];
if line_content.contains("*/") {
return false;
}
if line_content.contains("/*") {
found_comment_start = true;
break;
}
search_index -= 1;
}
if !found_comment_start {
return false;
}
for i in line_index..lines.len() {
let line_content = lines[i];
if line_content.contains("*/") {
return true;
}
}
}
false
}
pub async fn is_inside_curly_braces_block(
&self,
line_index: usize,
document_url: &Url,
) -> bool {
let documents = self.documents.read().await;
if let Some(document) = documents.get(document_url) {
let lw_doc_text = document;
let lines: Vec<&str> = lw_doc_text.split('\n').collect();
if line_index >= lines.len() {
return false;
}
let mut search_index = line_index as isize;
let mut found_open_brace = false;
while search_index >= 0 {
let line_content = lines[search_index as usize].to_lowercase();
if line_content.contains('}') {
return false;
}
if line_content.contains('{') {
found_open_brace = true;
break;
}
if self.line_contains_cql_kw(&line_content) {
return false;
}
search_index -= 1;
}
if !found_open_brace {
return false;
}
for i in line_index..lines.len() {
let line_content = lines[i];
if self.line_contains_cql_kw(line_content) {
return false;
}
if line_content.contains('}') {
return true;
}
}
}
false
}
pub async fn is_inside_create_type_no_position(
&self,
line_index: usize,
document_url: &Url,
) -> bool {
let documents = self.documents.read().await;
if let Some(document) = documents.get(document_url) {
let lw_doc_text = document;
let lines: Vec<&str> = lw_doc_text.split('\n').collect();
let current_line = line_index;
if current_line >= lines.len() {
return false;
}
let mut found_create_table = false;
let mut search_index = current_line;
loop {
let line_content = lines[search_index].to_lowercase();
if (line_content.contains("create type")
|| line_content.contains("create type if not exists"))
&& line_content.contains("(")
&& !line_content.contains(")")
{
info!("Found CRT: {}", line_content);
found_create_table = true;
break;
}
if self.line_contains_cql_kw(&line_content) {
return false;
}
if search_index == 0 {
break;
}
search_index -= 1;
}
if !found_create_table {
return false;
}
for i in (current_line + 1)..lines.len() {
let line_content = lines[i];
if self.line_contains_cql_kw(line_content) {
return false;
}
if line_content.contains(")") {
return true;
}
}
}
false
}
pub async fn is_inside_create_table(
&self,
line: &str,
position: &Position,
document_url: &Url,
) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let lw = prefix.to_lowercase();
let split: Vec<&str> = lw.split(' ').collect();
if split.len() < 2 {
return false;
}
if split[0] == "create"
&& split[1] == "table"
&& line.contains("(")
&& line.contains(")")
&& (prefix.contains("(") && !prefix.contains(")"))
{
return true;
}
let documents = self.documents.read().await;
if let Some(document) = documents.get(document_url) {
let lw_doc_text = document;
let lines: Vec<&str> = lw_doc_text.split('\n').collect();
let current_line = position.line as usize;
if current_line >= lines.len() {
return false;
}
let mut found_create_table = false;
let mut search_index = current_line;
loop {
let line_content = lines[search_index].to_lowercase();
if (line_content.contains("create table")
|| line_content.contains("create table if not exists"))
&& line_content.contains("(")
&& !line_content.contains(")")
{
info!("Found CRT: {}", line_content);
found_create_table = true;
break;
}
if self.line_contains_cql_kw(&line_content) {
return false;
}
if search_index == 0 {
break;
}
search_index -= 1;
}
if !found_create_table {
return false;
}
for i in (current_line + 1)..lines.len() {
let line_content = lines[i];
if self.line_contains_cql_kw(line_content) {
return false;
}
if line_content.contains(")") {
return true;
}
}
}
false
}
pub async fn should_suggest_types_completions(
&self,
line: &str,
position: &Position,
document_url: &Url,
) -> bool {
if !self
.is_inside_create_table(line, position, document_url)
.await
{
return false;
}
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let trimmed_prefix = prefix.trim();
let split: Vec<&str> = trimmed_prefix.split(' ').collect();
match split.len() {
0 => false,
1 => prefix.ends_with(' '),
2 => !prefix.ends_with(' '),
_ => false,
}
}
pub async fn should_suggest_type_modifiers(
&self,
line: &str,
position: &Position,
document_url: &Url,
) -> bool {
if !self
.is_inside_create_table(line, position, document_url)
.await
{
return false;
}
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let trimmed_prefix = prefix.trim().to_lowercase();
let split: Vec<&str> = trimmed_prefix.split(' ').collect();
match split.len() {
0 => false,
2 => prefix.ends_with(' ') && CQL_TYPES_LWC.contains(&split[1].to_string()),
3 => {
(!prefix.ends_with(' ') && CQL_TYPES_LWC.contains(&split[1].to_string()))
|| (prefix.ends_with(' ')
&& CQL_TYPES_LWC.contains(&split[1].to_string())
&& split[2] == "primary")
}
4 => {
!prefix.ends_with(' ')
&& CQL_TYPES_LWC.contains(&split[1].to_string())
&& split[2] == "primary"
}
_ => false,
}
}
pub fn should_suggest_table_completions(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
if let Some(semi_colon_pos) = line.find(&";") {
if position.character > semi_colon_pos as u32 {
return false;
}
}
let trimmed_prefix = prefix.trim_end().to_lowercase();
let splitted: Vec<&str> = trimmed_prefix.split(' ').collect();
if splitted.len() <= 2 && splitted[0].contains("update") {
return true;
}
if splitted.len() >= 2
&& (splitted[splitted.len() - 2].contains("insert")
|| splitted[splitted.len() - 1].contains("into"))
{
return true;
}
if splitted.len() >= 2
&& ((splitted[0].contains("drop") && splitted[1].contains("table"))
&& ((splitted[splitted.len() - 2].contains("drop")
&& splitted[splitted.len() - 1].contains("table"))
|| (splitted.len() > 2
&& splitted[splitted.len() - 3].contains("drop")
&& splitted[splitted.len() - 2].contains("table")
&& trimmed_prefix.len() == prefix.len())))
{
return true;
}
if splitted.len() >= 3
&& ((splitted[splitted.len() - 2].contains("insert")
|| splitted[splitted.len() - 1].contains("into"))
|| (splitted[splitted.len() - 3].contains("insert")
|| splitted[splitted.len() - 2].contains("into")))
{
return true;
}
if !splitted.contains(&"select") && !splitted.contains(&"from") {
return false;
}
if splitted.len() >= 2
&& !splitted[splitted.len() - 2].contains("from")
&& !splitted[splitted.len() - 1].contains("from")
{
return false;
}
if splitted.len() >= 2
&& splitted[splitted.len() - 2].contains("from")
&& trimmed_prefix.len() != prefix.len()
{
return false;
}
true
}
pub fn should_suggest_if_not_exists(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let lw = prefix.to_lowercase();
let split: Vec<&str> = lw.split(' ').collect();
if split.len() < 2 {
return false;
}
if split.contains(&"create")
&& ((split[split.len() - 1].to_lowercase() == "table"
|| split[split.len() - 2].to_lowercase() == "table")
|| (split[split.len() - 1].to_lowercase() == "view"
|| split[split.len() - 2].to_lowercase() == "view")
|| (split[split.len() - 1].to_lowercase() == "keyspace"
|| split[split.len() - 2].to_lowercase() == "keyspace")
|| (split[split.len() - 1].to_lowercase() == "aggregate"
|| split[split.len() - 2].to_lowercase() == "aggregate")
|| (split[split.len() - 1].to_lowercase() == "function"
|| split[split.len() - 2].to_lowercase() == "function")
|| (split[split.len() - 1].to_lowercase() == "index"
|| split[split.len() - 2].to_lowercase() == "index")
|| (split[split.len() - 1].to_lowercase() == "role"
|| split[split.len() - 2].to_lowercase() == "role")
|| (split[split.len() - 1].to_lowercase() == "type"
|| split[split.len() - 2].to_lowercase() == "type")
|| (split[split.len() - 1].to_lowercase() == "user")
|| split[split.len() - 2].to_lowercase() == "user")
{
return true;
}
false
}
pub fn should_suggest_create_keywords(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let lw = prefix.to_lowercase();
let split: Vec<&str> = lw.split(' ').collect();
if split.len() < 1 {
return false;
}
if split[0] == "create" && split.len() <= 2 {
return true;
}
false
}
pub fn should_suggest_alter_keywords(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let lw = prefix.to_lowercase();
let split: Vec<&str> = lw.split(' ').collect();
if split.len() < 1 {
return false;
}
if split[0] == "alter" && split.len() <= 2 {
return true;
}
false
}
pub fn should_suggest_drop_keywords(&self, line: &str, position: &Position) -> bool {
let byte_pos = line
.char_indices()
.nth(position.character as usize)
.map(|(i, _)| i)
.unwrap_or(line.len());
let prefix = match line.get(..byte_pos) {
Some(p) => p,
None => return false,
};
let lw = prefix.to_lowercase();
let split: Vec<&str> = lw.split(' ').collect();
if split.len() < 1 {
return false;
}
if split[0] == "drop" && split.len() <= 2 {
return true;
}
false
}
pub fn should_edit_select_statement(&self, line: &str, lines: &Vec<String>) -> bool {
false
}
pub async fn completion(
&self,
params: CompletionParams,
) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
let uri = params.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let documents = self.documents.read().await;
let text = match documents.get(&uri) {
Some(text) => text,
None => return Ok(None),
};
let line = match text.lines().nth(position.line as usize) {
Some(line) => line,
None => return Ok(None),
};
let in_string = Self::is_in_string_literal(line, position.character);
let ssh_keyspaces = self.should_suggest_keyspaces(line, &position);
let ssh_graph_types = self.should_suggest_graph_engine_types(line, &position);
let ssh_keywords = self.should_suggest_keywords(line, &position).await;
let ssh_fields = self.should_suggest_fields(line, &position);
let ssh_from = self.should_suggest_from(line, &position);
let ssh_table_completions = self.should_suggest_table_completions(line, &position);
let ssh_if_not_exists = self.should_suggest_if_not_exists(line, &position);
let ssh_create_keywords = self.should_suggest_create_keywords(line, &position);
let ssh_alter_keywords = self.should_suggest_alter_keywords(line, &position);
let ssh_drop_keywords = self.should_suggest_drop_keywords(line, &position);
let ssh_drop_keyspaces = self.should_suggest_drop_keyspaces(line, &position);
let ssh_drop_tables = self.should_suggest_drop_tables(line, &position);
let ssh_drop_aggregate = self.should_suggest_drop_aggregate(line, &position);
let ssh_drop_function = self.should_suggest_drop_function(line, &position);
let ssh_drop_index = self.should_suggest_drop_indexes(line, &position);
let ssh_drop_type = self.should_suggest_drop_types(line, &position);
let ssh_drop_view = self.should_suggest_drop_views(line, &position);
let ssh_types = self
.should_suggest_types_completions(line, &position, &uri)
.await;
let ssh_type_modifiers = self
.should_suggest_type_modifiers(line, &position, &uri)
.await;
if ssh_keyspaces {
return if in_string {
self.handle_in_string_keyspace_completion(line, &position)
.await
} else {
self.handle_out_of_string_keyspace_completion(line, &position)
.await
};
}
if ssh_create_keywords {
return self.handle_create_keywords();
}
if ssh_alter_keywords {
return self.handle_alter_keywords();
}
if ssh_drop_keywords {
return self.handle_drop_keywords();
}
if ssh_drop_keyspaces {
return self.handle_drop_keyspace_completions(line, &position).await;
}
if ssh_drop_tables {
return self.handle_table_completion(&position).await;
}
if ssh_drop_aggregate {
return self.handle_drop_aggregate_completions().await;
}
if ssh_drop_function {
return self.handle_drop_function_completions().await;
}
if ssh_drop_index {
return self.handle_drop_index_completions().await;
}
if ssh_drop_type {
return self.handle_drop_type_completions().await;
}
if ssh_drop_view {
return self.handle_drop_view_completions().await;
}
if ssh_from {
return self.handle_from_completion();
}
if ssh_fields {
return self.handle_fields_completion(line, &position).await;
}
if ssh_table_completions {
return self.handle_table_completion(&position).await;
}
if ssh_types {
return self.handle_types_completion();
}
if ssh_type_modifiers {
return self.handle_type_modifiers_completion(line);
}
if ssh_if_not_exists {
return self.handle_if_not_exists();
}
if ssh_graph_types {
return if in_string {
self.handle_in_string_graph_engine_completion(line, &position)
.await
} else {
self.handle_out_of_string_graph_engine_completion().await
};
}
if ssh_keywords && !in_string {
return self.handle_keywords_completion();
}
Ok(Some(CompletionResponse::Array(vec![])))
}
}