Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 61 additions & 3 deletions crates/pgls_completions/src/sanitization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ pub(crate) fn is_sanitized_token_with_quote(node_under_cursor_txt: &str) -> bool
// Node under cursor text will be "REPLACED_TOKEN_WITH_QUOTE".
// The SANITIZED_TOKEN_WITH_QUOTE does not have the leading ".
// We need to omit it from the txt.
&node_under_cursor_txt[1..] == SANITIZED_TOKEN_WITH_QUOTE
node_under_cursor_txt
.strip_prefix('"')
.is_some_and(|it| it == SANITIZED_TOKEN_WITH_QUOTE)
}

impl<'larger, 'smaller> From<CompletionParams<'larger>> for SanitizedCompletionParams<'smaller>
Expand All @@ -47,6 +49,13 @@ where
{
fn from(mut params: CompletionParams<'larger>) -> Self {
params.text = params.text.to_ascii_lowercase();

while !params.text.is_char_boundary(params.position.into())
&& params.position < TextSize::new(params.text.len().try_into().unwrap())
{
params.position = params.position.checked_add(1.into()).unwrap();
}

if cursor_inbetween_nodes(&params.text, params.position)
|| cursor_prepared_to_write_token_after_last_node(&params.text, params.position)
|| cursor_before_semicolon(params.tree, params.position)
Expand Down Expand Up @@ -275,7 +284,11 @@ fn cursor_between_parentheses(sql: &str, position: TextSize) -> bool {
.unwrap_or_default();

// (.. and |)
let after_and_keyword = &sql[position.saturating_sub(4)..position] == "and " && after == ')';
let and_check_position = position.saturating_sub(4);
let after_and_keyword = sql.is_char_boundary(and_check_position)
&& &sql[and_check_position..position] == "and "
&& after == ')';

let after_eq_sign = before == '=' && after == ')';

let head_of_list = before == '(' && after == ',';
Expand Down Expand Up @@ -312,7 +325,7 @@ mod tests {
use pgls_text_size::TextSize;

use crate::{
CompletionParams, SanitizedCompletionParams,
CompletionParams, SanitizedCompletionParams, is_sanitized_token_with_quote,
sanitization::{
cursor_after_opened_quote, cursor_before_semicolon, cursor_between_parentheses,
cursor_inbetween_nodes, cursor_on_a_dot,
Expand Down Expand Up @@ -635,4 +648,49 @@ mod tests {
TextSize::new(20)
));
}

#[test]
fn multibyte_characters() {
is_sanitized_token_with_quote("é"); // should not panic

{
// cursor in the middle of multi-byte char
// select * from "auth"."é|é; <-- cursor in the middle of the é multi-byte char
let input = r#"select * from "auth"."é;"#;
let position = TextSize::new(23);

let params = get_test_params(input, position);

let sanitized = SanitizedCompletionParams::from(params);

assert_eq!(sanitized.text, r#"select * from "auth"."é;"#);
assert_eq!(sanitized.position, TextSize::new(24)); // adjusts the cursor position to end of multibyte char
}

{
// cursor in front of multibyte char
// select * from "auth"."|é; <-- cursor front of the é multi-byte char
let input = r#"select * from "auth"."é;"#;
let position = TextSize::new(22);

let params = get_test_params(input, position);

let sanitized = SanitizedCompletionParams::from(params);

assert_eq!(
sanitized.text,
r#"select * from "auth"."REPLACED_TOKEN_WITH_QUOTE"é; "#
);
assert_eq!(sanitized.position, TextSize::new(22)); // leaves cursor position as is
}

{
let input = "insert into instruments (name, id, €, )";
let position = TextSize::new(36);

let params = get_test_params(input, position);

let _ = SanitizedCompletionParams::from(params); // should not panic
}
}
}
Loading