fix: preserve configuration completion context
This commit is contained in:
@@ -101,9 +101,11 @@ pub fn complete_current(
|
|||||||
let utf8_byte_offset = utf16_to_utf8_offset(&source, utf16_offset)?;
|
let utf8_byte_offset = utf16_to_utf8_offset(&source, utf16_offset)?;
|
||||||
let result = session_environment(snapshot.clone())
|
let result = session_environment(snapshot.clone())
|
||||||
.complete_config(&entrypoint, &source, utf8_byte_offset, explicit)
|
.complete_config(&entrypoint, &source, utf8_byte_offset, explicit)
|
||||||
.map_err(|error| JsValue::from_str(&format!("{error:?}")))?
|
.map_err(|error| JsValue::from_str(&format!("{error:?}")))?;
|
||||||
.map(|result| WasmCompletionResult {
|
let result = result
|
||||||
from: result.from,
|
.map(|result| {
|
||||||
|
Ok::<WasmCompletionResult, JsValue>(WasmCompletionResult {
|
||||||
|
from: utf8_to_utf16_offset(&source, result.from)?,
|
||||||
items: result
|
items: result
|
||||||
.items
|
.items
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -114,7 +116,9 @@ pub fn complete_current(
|
|||||||
priority: item.priority,
|
priority: item.priority,
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
});
|
})
|
||||||
|
})
|
||||||
|
.transpose()?;
|
||||||
encode(result)
|
encode(result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -177,6 +181,16 @@ fn utf16_to_utf8_offset(source: &str, utf16_offset: usize) -> Result<usize, JsVa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn utf8_to_utf16_offset(source: &str, utf8_offset: usize) -> Result<usize, JsValue> {
|
||||||
|
if utf8_offset > source.len() {
|
||||||
|
return Err(JsValue::from_str("UTF-8 offset is outside the source"));
|
||||||
|
}
|
||||||
|
if !source.is_char_boundary(utf8_offset) {
|
||||||
|
return Err(JsValue::from_str("UTF-8 offset splits a character"));
|
||||||
|
}
|
||||||
|
Ok(source[..utf8_offset].encode_utf16().count())
|
||||||
|
}
|
||||||
|
|
||||||
fn decode<T: serde::de::DeserializeOwned>(value: JsValue) -> Result<T, JsValue> {
|
fn decode<T: serde::de::DeserializeOwned>(value: JsValue) -> Result<T, JsValue> {
|
||||||
from_value(value).map_err(|error| JsValue::from_str(&error.to_string()))
|
from_value(value).map_err(|error| JsValue::from_str(&error.to_string()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1203,6 +1203,9 @@ impl SnapshotEnvironment {
|
|||||||
{
|
{
|
||||||
let mut member_source = format!("{WORKSPACE_CONFIG_SCHEMA_GLOBAL}.");
|
let mut member_source = format!("{WORKSPACE_CONFIG_SCHEMA_GLOBAL}.");
|
||||||
member_source.push_str(&context.schema_path.join("."));
|
member_source.push_str(&context.schema_path.join("."));
|
||||||
|
if !context.schema_path.is_empty() && context.from == utf8_byte_offset {
|
||||||
|
member_source.push('.');
|
||||||
|
}
|
||||||
let mut completion = LanguageService::new(self).complete(
|
let mut completion = LanguageService::new(self).complete(
|
||||||
entrypoint.as_str(),
|
entrypoint.as_str(),
|
||||||
&member_source,
|
&member_source,
|
||||||
@@ -1961,6 +1964,31 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|item| item.label == "default_profile")
|
.any(|item| item.label == "default_profile")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let blank_nested_source = "{ profile = { } } as WorkspaceConfigSchema";
|
||||||
|
let blank_nested_cursor = blank_nested_source.find("{ }").unwrap() + 2;
|
||||||
|
let blank_nested = environment
|
||||||
|
.complete_config(
|
||||||
|
&path("main.dcdl"),
|
||||||
|
blank_nested_source,
|
||||||
|
blank_nested_cursor,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(blank_nested.from, blank_nested_cursor);
|
||||||
|
assert!(
|
||||||
|
blank_nested
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.label == "default_profile")
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!blank_nested
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.label == "profile")
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -328,10 +328,12 @@
|
|||||||
</header>
|
</header>
|
||||||
<DecodalSourceEditor
|
<DecodalSourceEditor
|
||||||
value={source}
|
value={source}
|
||||||
readonly={!selectedPath || busy}
|
readonly={!selectedPath || busy || !analysisReady}
|
||||||
fixedSchemaWrapper={mainSelected}
|
fixedSchemaWrapper={mainSelected}
|
||||||
onChange={(value) => source = value}
|
onChange={(value) => source = value}
|
||||||
onComplete={(value, offset, explicit) => toolchain?.complete(selectedPath, value, offset, explicit) ?? Promise.resolve(null)}
|
onComplete={(value, offset, explicit) => analysisReady && toolchain
|
||||||
|
? toolchain.complete(selectedPath, value, offset, explicit)
|
||||||
|
: Promise.resolve(null)}
|
||||||
/>
|
/>
|
||||||
<p class="config-source-status" aria-live="polite">{status}</p>
|
<p class="config-source-status" aria-live="polite">{status}</p>
|
||||||
{#if conflict}
|
{#if conflict}
|
||||||
|
|||||||
Binary file not shown.
@@ -299,6 +299,45 @@ Deno.test("generated WASM returns completion items for the editor adapter", () =
|
|||||||
assertEquals(result.items[0].kind, "file");
|
assertEquals(result.items[0].kind, "file");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("generated WASM completes blank nested schema positions after Unicode", () => {
|
||||||
|
const source =
|
||||||
|
'{ description = "日本語"; profile = { }\n} as WorkspaceConfigSchema';
|
||||||
|
const cursor = source.indexOf("{ }") + 2;
|
||||||
|
set_snapshot({
|
||||||
|
...snapshot,
|
||||||
|
entries: {
|
||||||
|
...snapshot.entries,
|
||||||
|
"workspace.dcdl": {
|
||||||
|
...snapshot.entries["workspace.dcdl"],
|
||||||
|
content: source,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
set_schema_bundle({
|
||||||
|
contributions: [],
|
||||||
|
source: "{ profile = { default_profile = String; }; prompts = {}; }",
|
||||||
|
fingerprint: "sha256:test-schema",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = complete_current(
|
||||||
|
"workspace.dcdl",
|
||||||
|
source,
|
||||||
|
cursor,
|
||||||
|
true,
|
||||||
|
) as {
|
||||||
|
from: number;
|
||||||
|
items: Array<{ label: string; kind: string }>;
|
||||||
|
};
|
||||||
|
|
||||||
|
assertEquals(result.from, cursor);
|
||||||
|
assertEquals(
|
||||||
|
result.items.some((item) => item.label === "default_profile"),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
assertEquals(result.items.some((item) => item.label === "profile"), false);
|
||||||
|
assertEquals(result.items.some((item) => item.label === "prompts"), false);
|
||||||
|
});
|
||||||
|
|
||||||
Deno.test("generated WASM completes asserted WorkspaceConfigSchema keys", () => {
|
Deno.test("generated WASM completes asserted WorkspaceConfigSchema keys", () => {
|
||||||
const bareSource = "{ pro }";
|
const bareSource = "{ pro }";
|
||||||
const source = "{ pro } as WorkspaceConfigSchema";
|
const source = "{ pro } as WorkspaceConfigSchema";
|
||||||
|
|||||||
Reference in New Issue
Block a user