0.1.13 improve completion
This commit is contained in:
+38
-1
@@ -1,4 +1,5 @@
|
||||
import type { XmlAttribute, XmlDocument, XmlElement } from "./xmlParser";
|
||||
import { parseTag } from "./xmlParser";
|
||||
|
||||
export type ContextKind =
|
||||
| "element-name"
|
||||
@@ -42,6 +43,15 @@ export function analyzeContext(
|
||||
return analyzeStartTag(container, text, offset);
|
||||
}
|
||||
|
||||
// A start tag that had to be recovered (e.g. an attribute quote is still
|
||||
// open) is truncated at the first line break, so attributes typed on later
|
||||
// lines of the same tag are not part of the parsed element. Re-parse the
|
||||
// partial tag up to the cursor so value/attribute completion keeps working
|
||||
// while typing in the malformed tag.
|
||||
if (container.recoveredStartTag) {
|
||||
return analyzeRecoveredStartTag(container, text, offset);
|
||||
}
|
||||
|
||||
// Otherwise the cursor is in element content.
|
||||
return {
|
||||
kind: "content",
|
||||
@@ -53,6 +63,33 @@ export function analyzeContext(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies the cursor inside a recovered (truncated) start tag by re-parsing
|
||||
* the raw tag content up to the cursor. The original element only kept the
|
||||
* attributes that fit on its first line; re-parsing the partial text recovers
|
||||
* attributes typed on later lines without affecting the rest of the document.
|
||||
*/
|
||||
function analyzeRecoveredStartTag(
|
||||
el: XmlElement,
|
||||
text: string,
|
||||
offset: number,
|
||||
): CompletionContext {
|
||||
const raw = parseTag(text.slice(el.start + 1, offset), el.start + 1);
|
||||
const partial: XmlElement = {
|
||||
name: raw.name,
|
||||
attrs: raw.attrs,
|
||||
children: [],
|
||||
parent: el.parent,
|
||||
start: raw.start,
|
||||
startTagEnd: offset,
|
||||
end: offset,
|
||||
selfClosing: raw.selfClosing,
|
||||
closeTagStart: -1,
|
||||
depth: el.depth,
|
||||
};
|
||||
return analyzeStartTag(partial, text, offset);
|
||||
}
|
||||
|
||||
function analyzeStartTag(
|
||||
el: XmlElement,
|
||||
text: string,
|
||||
@@ -83,7 +120,7 @@ function analyzeStartTag(
|
||||
attr.hasValue &&
|
||||
attr.quoteStart >= 0 &&
|
||||
offset >= attr.quoteStart &&
|
||||
(attr.quoteEnd < 0 || offset <= attr.quoteEnd)
|
||||
(attr.quoteEnd < 0 || offset < attr.quoteEnd)
|
||||
) {
|
||||
const start = attr.valueStart;
|
||||
const prefix = offset > start ? text.slice(start, offset) : "";
|
||||
|
||||
@@ -43,6 +43,12 @@ export interface XmlElement {
|
||||
/** Offset of "</" of the closing tag, or -1 when self-closing. */
|
||||
closeTagStart: number;
|
||||
depth: number;
|
||||
/**
|
||||
* True when the start tag was unterminated and recovered at a line break.
|
||||
* Attributes typed on later lines of the same tag are not part of the
|
||||
* parsed element; completion re-parses the partial tag up to the cursor.
|
||||
*/
|
||||
recoveredStartTag?: boolean;
|
||||
}
|
||||
|
||||
export interface XmlParseError {
|
||||
@@ -119,7 +125,7 @@ interface RawTag {
|
||||
|
||||
const NAME_RE = /[A-Za-z_][\w:.-]*/y;
|
||||
|
||||
function parseTag(content: string, contentStart: number): RawTag {
|
||||
export function parseTag(content: string, contentStart: number): RawTag {
|
||||
const base = contentStart;
|
||||
let j = 0;
|
||||
while (j < content.length && /\s/.test(content[j])) {
|
||||
@@ -357,8 +363,15 @@ export function parseXml(text: string): XmlDocument {
|
||||
const raw = parseTag(content, i + 1);
|
||||
if (raw.name) {
|
||||
const el = buildElement(raw, stack.length);
|
||||
el.recoveredStartTag = true;
|
||||
elements.push(el);
|
||||
root = root ?? el;
|
||||
if (stack.length === 0) {
|
||||
root = root ?? el;
|
||||
} else {
|
||||
const parent = stack[stack.length - 1];
|
||||
parent.children.push(el);
|
||||
el.parent = parent;
|
||||
}
|
||||
stack.push(el);
|
||||
}
|
||||
i = recoverTo + 1;
|
||||
|
||||
Reference in New Issue
Block a user