Sfoglia il codice sorgente

Upcase keywords on separator commit; PROCEDURE offers its snippet

Eric Streit 1 giorno fa
parent
commit
d880aaef1f
1 ha cambiato i file con 62 aggiunte e 28 eliminazioni
  1. 62 28
      extensions/modula2-language/src/extension.ts

+ 62 - 28
extensions/modula2-language/src/extension.ts

@@ -15,6 +15,45 @@ function isIdentifierCharacter(ch: string | undefined): boolean {
   return !!ch && /[A-Za-z0-9_]/.test(ch);
 }
 
+/** A separator that commits a keyword (`procedure `, `procedure(`, ...). */
+function isCommitTrigger(ch: string): boolean {
+  return !isIdentifierCharacter(ch);
+}
+
+/** Separators after which a fresh PROCEDURE skeleton is offered. */
+function isSnippetTrigger(ch: string): boolean {
+  return ch === ' ' || ch === '\t' || ch === '(';
+}
+
+/**
+ * Decide what typing `inserted` at `insertOffset` (post-change coords)
+ * does to the keyword immediately before it.
+ *
+ * Returns the keyword range plus its uppercase form when a commit
+ * trigger (any non-identifier character) was just typed after a
+ * complete lowercase keyword. While a word is still being typed
+ * (`beginning`) the inserted text starts with an identifier character
+ * and nothing happens — this is what prevents `BEGINning`.
+ */
+export function upcaseTrigger(
+  text: string, insertOffset: number, inserted: string,
+): { start: number; end: number; upper: string } | null {
+  if (!inserted || !isCommitTrigger(inserted[0])) return null;
+  let start = insertOffset;
+  while (start > 0 && isIdentifierCharacter(text[start - 1])) start--;
+  if (start >= insertOffset) return null;
+  const token = text.slice(start, insertOffset);
+  const upper = token.toUpperCase();
+  if (!KEYWORDS.has(upper) || token === upper) return null;
+  return { start, end: insertOffset, upper };
+}
+
+/** True when offset `start` is preceded only by whitespace on its line. */
+export function isLineStart(text: string, start: number): boolean {
+  const lineStart = text.lastIndexOf('\n', start - 1) + 1;
+  return /^\s*$/.test(text.slice(lineStart, start));
+}
+
 export function activate(context: vscode.ExtensionContext): void {
   createDiagnostics(context);
   const serverModule = context.asAbsolutePath('out/server.js');
@@ -60,40 +99,35 @@ export function activate(context: vscode.ExtensionContext): void {
     }
 
     for (const change of event.contentChanges) {
-      const position = change.range.start.translate(0, change.text.length);
-      const line = event.document.lineAt(position.line).text;
-      let start = position.character;
-
-      while (start > 0 && isIdentifierCharacter(line[start - 1])) {
-        start--;
-      }
-
-      let end = position.character;
-      while (end < line.length && isIdentifierCharacter(line[end])) {
-        end++;
-      }
-
-      // Do not modify a token while it is still being typed.
-      // This is what prevents "beginning" from becoming "BEGINning".
-      if (end >= line.length || isIdentifierCharacter(line[end])) {
-        continue;
-      }
-
-      const token = line.slice(start, end);
-      const upper = token.toUpperCase();
-
-      if (!KEYWORDS.has(upper) || token === upper) {
-        continue;
-      }
-
+      // Post-change coordinates: the insertion point sits right after
+      // the just-typed text.
+      const doc = event.document;
+      const text = doc.getText();
+      const end = doc.offsetAt(change.range.start);
+      const hit = upcaseTrigger(text, end, change.text);
+      if (!hit) continue;
+      const keywordStart = doc.positionAt(hit.start);
       applyingEdit = true;
       try {
         await editor.edit(builder => {
           builder.replace(
-            new vscode.Range(position.line, start, position.line, end),
-            upper
+            new vscode.Range(keywordStart, doc.positionAt(hit.end)),
+            hit.upper
           );
         });
+        // A freshly upcased PROCEDURE at line start offers its skeleton:
+        // `procedure ` becomes a snippet asking name and parameters.
+        // The trigger separator is swallowed so the snippet owns spacing.
+        if (hit.upper === 'PROCEDURE' && change.text.length === 1 &&
+            isSnippetTrigger(change.text[0]) && isLineStart(text, hit.start)) {
+          const snippet = new vscode.SnippetString(
+            'PROCEDURE ${1:ProcedureName}(${2});\nBEGIN\n    ${3}\nEND ${1:ProcedureName};'
+          );
+          await editor.insertSnippet(
+            snippet,
+            new vscode.Range(keywordStart, doc.positionAt(hit.end + 1))
+          );
+        }
       } finally {
         applyingEdit = false;
       }