Location>code7788 >text

[VS Code extension] write a code snippet management plug-in (II): functionality implementation

Popularity:64 ℃/2024-08-22 10:01:04

@

catalogs
  • Creating and inserting code snippets
  • List of code snippets
  • Code snippet preview
  • Code snippet editing
  • Custom mapping
  • Default Mapping
  • autocomplete
  • Project Address

Creating and inserting code snippets

The VS Code extension provides data storage, where globalState is a Key-Value way of saving user state using global storage, supporting the retention of certain user states on different computers, for more information seeofficial document

If there is selected text in the editor area, click Create Snippet in the context menu to invoke thecommand to execute the create code snippet.

在这里插入图片描述

Creating Service ClassesThe code is as follows

export async function AddSnipp(context: ExtensionContext, state: Partial<ISnipp>) {
  const content = await getSnippText();
  const trimmedName = content?.text?.trim().substring(0, 20) || '';
  await _addOrUpdateSnipp(context, { ...state, name: trimmedName }, content)
}

exist_addOrUpdateSnippmethod of thesnippsPerform the update operation

async function _addOrUpdateSnipp(context: ExtensionContext, state: Partial<ISnipp>, content?: {
  text: string | undefined;
  type: string | undefined;
}, snippIndex?: number) {
   
  ...
  ("snipps", updatedSnipps);

If you click Insert Snippet in the right-click menu of the editor area or click on the entry in the code snippet view, call thecommand, which invokes theInsertSnippmethod performs the insert code snippet operation.

In the service category, insert the following code

export async function InsertSnipp(context: ExtensionContext, snipp: ISnipp) {
  const editor = ;
  if (editor && (snipp)) {
    const position = editor?.;
    (async (edit) => {

      (position,  || '');
    });
  } 
}

List of code snippets

Code snippets are displayed as a tree structure, with code snippet entries grouped according to the type of file content at the time of creation

在这里插入图片描述

Interface type for creating code snippets and grouped entries

import * as vscode from "vscode";

export interface ISnipp {
  name: string;
  content: string;
  contentType: string;
  created: Date;
  lastUsed: Date;
}

export interface IGroup {
  name: string;
  contentType: string | undefined;
}

Create get accessors in SnippItem to get all grouping types, and getChildren methods to get entries under the grouping


export class SnippItem {
  constructor(
    readonly view: string,
    private context: 
  ) { }

  public get roots(): Thenable<IGroup[]> {
    const snipps = ?.globalState?.get("snipps", []);
    const types = snipps
      .map((snipp: ISnipp) => )
      .filter((value, index, self) => (value) === index)
      .map((type) => ({ name: type, contentType: undefined }));
    return (types);
  }

  public getChildren(node: IGroup): Thenable<ISnipp[]> {
    const snipps = ?.globalState
      ?.get("snipps", [])
      .filter((snipp: ISnipp) => {
        return  === ;
      })
      .sort((a: ISnipp, b: ISnipp) => ());

    return (snipps);
  }



export class GroupItem { }

The content displayed in the sidebar of the VS Code extension needs to be tree-structured, by implementing theTreeDataProviderTo provide data for the content, please refer toofficial account

Implement the getChildren method

export class SnippDataProvider
  implements
    <ISnipp | IGroup>
{
  
  public getChildren(
    element?: ISnipp | IGroup
  ): ISnipp[] | Thenable<ISnipp[]> | IGroup[] | Thenable<IGroup[]> {
    return element ? (element) : ;
  }

}

Code snippet preview

Implement the getTreeItem method to show previews

Called on clickThe command implements the insertion of code snippets, the COMMAND section is described in the previous section.

Show tooltip preview when mouse over code snippet entry

在这里插入图片描述

The code is as follows:

public getTreeItem(element: ISnipp | IGroup):  {
    const t = ;
    const isSnip = (element);
    const snippcomm = {
      command: "",
      title: '',
      arguments: [element],
    };

    let snippetInfo: string = `[${}] ${}`;

    return {
      // @ts-ignore
      label: isSnip ? : ,
      command: isSnip ? snippcomm : undefined,
      iconPath:isSnip ? new ThemeIcon("code"):new ThemeIcon("folder"),
      tooltip: isSnip
        ? new (
            // @ts-ignore
            `**caption:**${snippetInfo}\n\n**Modify Time:**${}\n\n**Recent use:**${}\n\n**previews:**\n\`\`\`${}\n${}\n\`\`\``
          )
        : undefined,
      collapsibleState: !isSnip
        ?
        : undefined,
    };
  }

Code snippet editing

The editor is an input box. Since VS Code's input box does not support multi-line input, it needs to use a webview to realize multi-line input. Also need submit button and cancel button

在这里插入图片描述

First create a WebView with a multi-line text box.
In the service categoryto create a functiongetWebviewContent, returns an HTML string used to create a multi-line input box.

function getWebviewContent(placeholder: string, initialValue: string): string {
  return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Multiline Input</title>
      </head>
      <body>
        <textarea rows="10" cols="50" placeholder="${placeholder}">${initialValue}</textarea>
        <br>
        <button onclick="submitText()">submit (a report etc)</button>
        <button onclick="cancel()">abolish</button>
        <script>
          const vscode = acquireVsCodeApi();
          function submitText() {
            const text = ('inputBox').value;
            ({ command: 'submit', text: text });
          }
          function cancel() {
            ({ command: 'cancel' });
          }
        </script>
      </body>
      </html>
    `;
}

Add a handler to return the contents of the text input box when the user clicks "Submit" and close the input box window.


async function showInputBoxWithMultiline(context: ExtensionContext, placeholder: string, initialValue: string): Promise<string | undefined> {
  const panel = (
    'multilineInput',
    'Multiline Input',
    ,
    {
      enableScripts: true
    }
  );

   = getWebviewContent(placeholder, initialValue);

  return new Promise<string | undefined>((resolve) => {
    (
      message => {
        switch () {
          case 'submit':
            resolve();
            ();
            return;
          case 'cancel':
            resolve(undefined);
            ();
            return;
        }
      },
      undefined,
      
    );
  });
}

Functions triggered when adding code snippets and editing code snippets


export async function AddSnippFromEditor(context: ExtensionContext, state: Partial<ISnipp>) {
  const content = await showInputBoxWithMultiline(context, 'Please enterSnippetelement', '');
  if (content) {
    _addOrUpdateSnipp(context, state, { text: content, type: "TEXT" })

  }
}

export async function EditSnipp(context: ExtensionContext, state: Partial<ISnipp>, snippIndex: number) {
  const content = await showInputBoxWithMultiline(context, 'Please enterSnippetelement', ?? '');
  if (content) {
    _addOrUpdateSnipp(context, state, { text: content, type: ?? "TEXT" }, snippIndex)

  }
}

Custom mapping

Mappings are variables that are automatically replaced when a code snippet is inserted, and they are stored in globalState via Key-Value form.

code snippet by setting placeholders (such as the${AUTHOR}), will be automatically replaced with the value in the global variable when the code snippet is inserted.

When a custom mapping value is not set or is not available, a variable placeholder will be displayed directly

When the extension is initialized, three commonly used custom mappings are inserted and you are free to change or add custom mappings.

  • ${AUTHOR}:: Author's name
  • ${COMPANY}:: Company name
  • ${MAIL}: E-mail address

All custom mappings in the extension are presented in the Mapping Table tree view.

在这里插入图片描述

Example:

Code snippet content

value of 'AUTHOR' is: ${AUTHOR}
value of 'COMPANY' is: ${COMPANY}
value of 'MAIL' is: ${MAIL}
value of 'FOOBAR' (non-exist) is: ${FOOBAR}

After inserting the code snippet, the following is displayed:

value of 'AUTHOR' is: Lin Xiao (1969-), Chinese-American physicist, astronomer and mathematicianlx
value of 'COMPANY' is: my-company
value of 'MAIL' is: jevonsflash@
value of 'FOOBAR' (non-exist) is: ${FOOBAR}

First define the KVItem class:

export class KVItem extends  {
    constructor(
      public readonly key: string,
      public readonly value: string | undefined
    ) {
      super(key, );
       = `${}: ${}`;
       = ;
       = 'kvItem';
    }
  }

The content displayed in the "Mapping Table" tree view needs to be in a tree structure, which also needs to be defined.KVTreeDataProvider, where methods such as refresh, add, delete, get child nodes, etc. are implemented.

export class KVTreeDataProvider implements <KVItem> {
  private _onDidChangeTreeData: <KVItem | undefined> = new <KVItem | undefined>();
  readonly onDidChangeTreeData: <KVItem | undefined> = this._onDidChangeTreeData.event;

  constructor(private globalState: ) {}

  getTreeItem(element: KVItem):  {
    return element;
  }

  getChildren(element?: KVItem): Thenable<KVItem[]> {
    if (element) {
      return ([]);
    } else {
      const kvObject = <{ [key: string]: string }>('key-value', {});
      const keys = (kvObject);
      return ((key => new KVItem(key, kvObject[key])));
    }
  }

  refresh(): void {
    this._onDidChangeTreeData.fire(undefined);
  }

  addOrUpdateKey(key: string, value: string): void {
    const kvObject = <{ [key: string]: string }>('key-value', {});
    kvObject[key] = value;
    ('key-value', kvObject);
    ();
  }

  deleteKey(key: string): void {
    const kvObject = <{ [key: string]: string }>('key-value', {});
    delete kvObject[key];
    ('key-value', kvObject);
    ();
  }
}

Default Mapping

The default mapping is the extension's built-in mapping feature, and the available mappings are as follows

Documentation and editor related:

  • TM_SELECTED_TEXT: currently selected text or empty string
  • TM_CURRENT_LINE: content of the current line
  • TM_CURRENT_WORD: the content of the word or empty string under the cursor
  • TM_LINE_INDEX: Zero-index based line numbering
  • TM_LINE_NUMBER: line number based on an index
  • TM_FILENAME: file name of the current document
  • TM_FILENAME_BASE: file name of the current document (without extension)
  • TM_DIRECTORY: directory of the current document
  • TM_FILEPATH: full file path of the current document
  • RELATIVE_FILEPATH: relative file path of the current document (relative to the open workspace or folder)
  • CLIPBOARD: contents of the clipboard
  • WORKSPACE_NAME: name of the open workspace or folder
  • WORKSPACE_FOLDER: Path to the open workspace or folder
  • CURSOR_INDEX: Cursor number based on zero-indexing
  • CURSOR_NUMBER: Cursor number based on a single index

Time Related:

  • CURRENT_YEAR: Current year
  • CURRENT_YEAR_SHORT: The last two digits of the current year.
  • CURRENT_MONTH: two-digit month (e.g. "02")
  • CURRENT_MONTH_NAME: full name of the month (e.g. "July")
  • CURRENT_MONTH_NAME_SHORT: short name of the month (e.g. "Jul")
  • CURRENT_DATE: The day of the month in two digits (e.g. "08").
  • CURRENT_DAY_NAME: name of the date (e.g. "Monday")
  • CURRENT_DAY_NAME_SHORT: short name of the day (e.g. "Mon")
  • CURRENT_HOUR24: current hour in hourly format
  • CURRENT_MINUTE: two-digit current minute number
  • CURRENT_SECOND: current second in two digits
  • CURRENT_SECONDS_UNIX: number of seconds since the Unix epoch
  • CURRENT_TIMEZONE_OFFSET The current UTC time zone offset is +HH:MM or -HH:MM (e.g. "-07:00").

Other:

  • RANDOM6: Random Base-10 Number
  • RANDOM_HEX6: A random Base-16 number.
  • UUID: Fourth Edition UUID

These items are referenced to VS Code snippet variables, check out theOfficial VSCode Documentation

As with custom mappings, when the default mapping value is not set or is not available, variable placeholders will be displayed directly

The method of realization is as follows:


export async function ReplacePlaceholders(text: string, context: ExtensionContext): Promise<string> {
  const editor = ;
  const clipboard = await ();
  const workspaceFolders = ;
  const currentDate = new Date();
  const kvObject = <{ [key: string]: string }>('key-value', {});

  const replacements: { [key: string]: string } = {
    '${TM_SELECTED_TEXT}': editor?.() || '',
    '${TM_CURRENT_LINE}': editor?.().text || '',
    '${TM_CURRENT_WORD}': editor?.(()) || '',
    '${TM_LINE_INDEX}': (editor?. ?? 0).toString(),
    '${TM_LINE_NUMBER}': ((editor?. ?? 0) + 1).toString(),
    '${TM_FILENAME}': editor ? () : '',
    '${TM_FILENAME_BASE}': editor ? (, ()) : '',
    '${TM_DIRECTORY}': editor ? () : '',
    '${TM_FILEPATH}': editor?. || '',
    '${RELATIVE_FILEPATH}': editor && workspaceFolders ? (workspaceFolders[0]., ) : '',
    '${CLIPBOARD}': clipboard,
    '${WORKSPACE_NAME}': workspaceFolders ? workspaceFolders[0].name : '',
    '${WORKSPACE_FOLDER}': workspaceFolders ? workspaceFolders[0]. : '',
    '${CURSOR_INDEX}': (editor?.() ?? 0).toString(),
    '${CURSOR_NUMBER}': ((editor?.() ?? 0) + 1).toString(),
    '${CURRENT_YEAR}': ().toString(),
    '${CURRENT_YEAR_SHORT}': ().toString().slice(-2),
    '${CURRENT_MONTH}': (() + 1).toString().padStart(2, '0'),
    '${CURRENT_MONTH_NAME}': ('default', { month: 'long' }),
    '${CURRENT_MONTH_NAME_SHORT}': ('default', { month: 'short' }),
    '${CURRENT_DATE}': ().toString().padStart(2, '0'),
    '${CURRENT_DAY_NAME}': ('default', { weekday: 'long' }),
    '${CURRENT_DAY_NAME_SHORT}': ('default', { weekday: 'short' }),
    '${CURRENT_HOUR}': ().toString().padStart(2, '0'),
    '${CURRENT_MINUTE}': ().toString().padStart(2, '0'),
    '${CURRENT_SECOND}': ().toString().padStart(2, '0'),
    '${CURRENT_SECONDS_UNIX}': (() / 1000).toString(),
    '${CURRENT_TIMEZONE_OFFSET}': formatTimezoneOffset(()),
    '${RANDOM}': ().toString().slice(2, 8),
    '${RANDOM_HEX}': (() * 0xffffff).toString(16).padStart(6, '0'),
    '${UUID}': generateUUID()
  };

  (kvObject).forEach(key => {
    replacements[`$\{${key}\}`] = kvObject[key];
  });

  return (/\$\{(\w+)\}/g, (match, key) => {
    return replacements[match] || match;
  });
}

autocomplete

Auto-completion is a feature provided by the VS Code editor for displaying auto-prompts and completions in the editor. The extension provides auto-completion based on code snippets.

在这里插入图片描述

CompletionItemProviderThe rule used to register for auto-completion, the provider agrees that the auto-completion context menu will appear when the characters entered match under the specified document type.

The context menu lists all available autocomplete entries, each of which is defined by theCompletionItemdefinition, clicking on the corresponding entry returns the processed string to be filled in at the editor's current cursor.

Rule provider used to register for auto-completion.

existAll autocomplete entries when registering initialization in the

const providers = contentTypes
  .filter((value, index, self) => (value) === index)
  .map(type =>
    (type, {
      provideCompletionItems(
        document: TextDocument,
        position: Position,
        token: CancellationToken,
        context: CompletionContext
      ) {
        return new Promise<CompletionItem[]>((resolve, reject) => {

          var result = snipps
            .filter((snipp: ISnipp) => {
              return  === type;
            })
            .map(async (snipp: ISnipp) => {
              const replacedContentText = await ReplacePlaceholders(, extensionContext);

              const commandCompletion = new CompletionItem();
               = replacedContentText || '';
              return commandCompletion;
            });

          (result).then(resolve);
        });
      }
    })
  );

(...providers);

Configure modified or new autocomplete entries in the _addOrUpdateSnipp method


  if (content?.type && ) {
    (, {
      provideCompletionItems(
        document: TextDocument,
        position: Position,
        token: CancellationToken,
        context: CompletionContext
      ) {
        return new Promise<CompletionItem[]>((resolve, reject) => {
          ReplacePlaceholders( || '', extensionContext).then(res => {
            const replacedContentText = res;
            const commandCompletion = new CompletionItem( || '');
             = replacedContentText || '';
            resolve([commandCompletion]);
          });


        });
      }
    });
  }

Project Address

Github:snippet-craft