LanguagesAPI

The LanguagesAPI provides language feature support for plugins, enabling registration of completion providers, hover providers, formatters, and custom language definitions.

Overview

Use LanguagesAPI when you need to:

  • Register autocomplete/completion providers
  • Provide hover information for symbols
  • Add go-to-definition support
  • Register code actions (quick fixes)
  • Add document formatting capabilities
  • Define custom language configurations
  • Register new language types

Completion Provider Methods

registerCompletionProvider(selector, provider, …triggerCharacters)

Register an autocomplete/completion provider for providing suggestions.

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector (language ID or \\\{language, scheme, pattern\\}“)
providerobjectYesCompletion provider with provideCompletionItems method
triggerCharacters…stringNoCharacters that trigger completion

Document Selector Format:

// String (language ID):
"markdown"
 
// Object:
{
  language?: string,   // Language ID
  scheme?: string,     // URI scheme ('file', 'untitled')
  pattern?: string     // Glob pattern
}

Provider Interface:

{
  provideCompletionItems(
    document: TextDocument,
    position: Position,
    token: CancellationToken,
    context: CompletionContext
  ): CompletionItem[] | Promise<CompletionItem[]>
}

CompletionItem Format:

{
  label: string,                    // Display label
  kind?: number,                    // Type (1=Text, 2=Method, 3=Function, etc.)
  detail?: string,                  // Additional info
  documentation?: string,           // Documentation
  insertText?: string,              // Text to insert (default: label)
  filterText?: string,              // Text for filtering
  sortText?: string,                // Text for sorting
  preselect?: boolean,              // Preselect this item
  command?: Command                 // Command to execute after insertion
}

CompletionContext Format:

{
  triggerKind: number,              // 1=Invoked, 2=TriggerCharacter, 3=TriggerForIncompleteCompletions
  triggerCharacter?: string         // Character that triggered completion
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerCompletionProvider(
  'markdown',
  {
    provideCompletionItems(document, position, token, context) {
      // Provide emoji completions
      if (context.triggerCharacter === ':') {
        return [
          {
            label: ':smile:',
            kind: 1, // Text
            insertText: '😊',
            documentation: 'Smiling face'
          },
          {
            label: ':heart:',
            kind: 1,
            insertText: '❤️',
            documentation: 'Red heart'
          }
        ];
      }
 
      return [];
    }
  },
  ':' // Trigger on colon
);
 
// Later, to unregister:
disposable.dispose();

Hover Provider Methods

registerHoverProvider(selector, provider)

Register a hover provider to show information when hovering over text.

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector
providerobjectYesHover provider with provideHover method

Provider Interface:

{
  provideHover(
    document: TextDocument,
    position: Position,
    token: CancellationToken
  ): Hover | null | Promise<Hover | null>
}

Hover Format:

{
  contents: string | string[] | MarkdownString,
  range?: Range                  // Range to highlight
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerHoverProvider('markdown', {
  provideHover(document, position, token) {
    const text = document.getText();
    const offset = document.offsetAt(position);
 
    // Find wiki links at cursor position
    const wikiLinkRegex = /\[\[([^\]]+)\]\]/g;
    let match;
 
    while ((match = wikiLinkRegex.exec(text)) !== null) {
      const start = match.index;
      const end = match.index + match[0].length;
 
      if (offset >= start && offset <= end) {
        const pageName = match[1];
        return {
          contents: [
            `**Wiki Link**`,
            `Page: $\\{pageName\\}`,
            `Click to navigate`
          ],
          range: {
            start: document.positionAt(start),
            end: document.positionAt(end)
          }
        };
      }
    }
 
    return null;
  }
});

Definition Provider Methods

registerDefinitionProvider(selector, provider)

Register a go-to-definition provider.

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector
providerobjectYesDefinition provider with provideDefinition method

Provider Interface:

{
  provideDefinition(
    document: TextDocument,
    position: Position,
    token: CancellationToken
  ): Location | Location[] | null | Promise<Location | Location[] | null>
}

Location Format:

{
  uri: string,                     // File URI
  range: Range                     // Range in target document
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerDefinitionProvider('markdown', {
  async provideDefinition(document, position, token) {
    const text = document.getText();
    const offset = document.offsetAt(position);
 
    // Find wiki link at cursor
    const wikiLinkRegex = /\[\[([^\]]+)\]\]/g;
    let match;
 
    while ((match = wikiLinkRegex.exec(text)) !== null) {
      const start = match.index;
      const end = match.index + match[0].length;
 
      if (offset >= start && offset <= end) {
        const pageName = match[1];
        const targetUri = `file:///workspace/${pageName}.md`;
 
        // Check if file exists
        const exists = await api.workspace.findFiles(targetUri);
        if (exists.length > 0) {
          return {
            uri: targetUri,
            range: {
              start: { line: 0, character: 0 },
              end: { line: 0, character: 0 }
            }
          };
        }
      }
    }
 
    return null;
  }
});

Code Action Provider Methods

registerCodeActionProvider(selector, provider, metadata)

Register a code action provider (quick fixes, refactorings).

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector
providerobjectYesCode action provider with provideCodeActions method
metadataobjectNoProvider metadata

Provider Interface:

{
  provideCodeActions(
    document: TextDocument,
    range: Range,
    context: CodeActionContext,
    token: CancellationToken
  ): CodeAction[] | Promise<CodeAction[]>
}

CodeAction Format:

{
  title: string,                   // Required: Action title
  kind?: string,                   // Action kind ('quickfix', 'refactor', 'refactor.extract', 'refactor.inline', 'refactor.rewrite', 'source', 'source.organizeImports')
  edit?: WorkspaceEdit,            // Workspace edit to apply
  command?: Command,               // Command to execute
  diagnostics?: Diagnostic[],      // Diagnostics this action resolves
  isPreferred?: boolean            // Prefer this action
}

CodeActionContext Format:

{
  diagnostics: Diagnostic[],       // Diagnostics at range
  only?: string,                   // Requested action kind
  triggerKind: number              // 1=Invoked, 2=Automatic
}

Metadata Format:

{
  providedCodeActionKinds?: string[]  // Action kinds this provider can return
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerCodeActionProvider(
  'markdown',
  {
    provideCodeActions(document, range, context, token) {
      const text = document.getText(range);
      const actions = [];
 
      // Quick fix: Convert TODO to task
      if (text.includes('TODO:')) {
        actions.push({
          title: 'Convert TODO to task',
          kind: 'quickfix',
          edit: {
            changes: {
              [document.uri.toString()]: [
                {
                  range: range,
                  newText: text.replace('TODO:', '- [ ]')
                }
              ]
            }
          },
          isPreferred: true
        });
      }
 
      // Refactor: Extract to callout
      if (text.length > 10) {
        actions.push({
          title: 'Extract to callout',
          kind: 'refactor.extract',
          command: {
            command: 'myPlugin.extractToCallout',
            arguments: [document.uri, range]
          }
        });
      }
 
      return actions;
    }
  },
  {
    providedCodeActionKinds: ['quickfix', 'refactor.extract']
  }
);

Formatting Provider Methods

registerDocumentFormattingProvider(selector, provider)

Register a document formatting provider.

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector
providerobjectYesFormatting provider with provideDocumentFormattingEdits method

Provider Interface:

{
  provideDocumentFormattingEdits(
    document: TextDocument,
    options: FormattingOptions,
    token: CancellationToken
  ): TextEdit[] | Promise<TextEdit[]>
}

FormattingOptions Format:

{
  tabSize: number,                 // Number of spaces per tab
  insertSpaces: boolean            // Use spaces instead of tabs
}

TextEdit Format:

{
  range: Range,                    // Range to replace
  newText: string                  // New text
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerDocumentFormattingProvider('markdown', {
  provideDocumentFormattingEdits(document, options, token) {
    const text = document.getText();
 
    // Format markdown: normalize headers, fix spacing, etc.
    const formatted = text
      .split('\n')
      .map(line => {
        // Ensure space after # in headers
        if (/^#+[^ ]/.test(line)) {
          return line.replace(/^(#+)/, '$1 ');
        }
        return line;
      })
      .join('\n');
 
    // Return single edit replacing entire document
    return [
      {
        range: {
          start: { line: 0, character: 0 },
          end: { line: document.lineCount - 1, character: 9999 }
        },
        newText: formatted
      }
    ];
  }
});

registerRangeFormattingProvider(selector, provider)

Register a range formatting provider for formatting selected text.

Parameters:

NameTypeRequiredDescription
selectorstring | objectYesDocument selector
providerobjectYesRange formatting provider with provideDocumentRangeFormattingEdits method

Provider Interface:

{
  provideDocumentRangeFormattingEdits(
    document: TextDocument,
    range: Range,
    options: FormattingOptions,
    token: CancellationToken
  ): TextEdit[] | Promise<TextEdit[]>
}

Returns: Disposable - Disposable to unregister the provider

Example:

const disposable = api.languages.registerRangeFormattingProvider('markdown', {
  provideDocumentRangeFormattingEdits(document, range, options, token) {
    const text = document.getText(range);
 
    // Format only the selected range
    const formatted = text
      .split('\n')
      .map(line => line.trim())
      .join('\n');
 
    return [
      {
        range: range,
        newText: formatted
      }
    ];
  }
});

Language Configuration Methods

registerLanguage(language)

Register a new language definition.

Parameters:

NameTypeRequiredDescription
languageobjectYesLanguage definition

Language Definition Format:

{
  id: string,                      // Required: Unique language identifier
  extensions?: string[],           // File extensions (e.g., ['.md', '.markdown'])
  aliases?: string[],              // Language aliases
  configuration?: object           // Language configuration
}

Returns: Disposable - Disposable to unregister the language

Example:

const disposable = api.languages.registerLanguage({
  id: 'my-lang',
  extensions: ['.mylang', '.ml'],
  aliases: ['MyLang', 'my-language'],
  configuration: {
    comments: {
      lineComment: '//',
      blockComment: ['/*', '*/']
    },
    brackets: [
      ['{', '}'],
      ['[', ']'],
      ['(', ')']
    ],
    autoClosingPairs: [
      { open: '{', close: '}' },
      { open: '[', close: ']' },
      { open: '(', close: ')' },
      { open: '"', close: '"' },
      { open: "'", close: "'" }
    ]
  }
});

Events Emitted:

  • language_registered - When language is registered
  • did_change_languages - When language registry changes

Throws: Error if language ID already exists


setLanguageConfiguration(languageId, configuration)

Set or update language configuration.

Parameters:

NameTypeRequiredDescription
languageIdstringYesLanguage identifier
configurationobjectYesLanguage configuration

Configuration Format:

{
  comments?: {
    lineComment?: string,          // Line comment prefix (e.g., '//')
    blockComment?: [string, string] // Block comment [start, end]
  },
  brackets?: Array<[string, string]>, // Bracket pairs
  autoClosingPairs?: Array<{
    open: string,
    close: string,
    notIn?: string[]               // Don't auto-close in these contexts
  }>,
  surroundingPairs?: Array<{
    open: string,
    close: string
  }>,
  wordPattern?: string             // Regex pattern for word boundaries
}

Returns: Disposable - Disposable to unregister the configuration

Example:

const disposable = api.languages.setLanguageConfiguration('markdown', {
  comments: {
    lineComment: '//',
    blockComment: ['<!--', '-->']
  },
  brackets: [
    ['[', ']'],
    ['(', ')'],
    ['{', '}']
  ],
  autoClosingPairs: [
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '{', close: '}' },
    { open: '"', close: '"', notIn: ['string'] },
    { open: "'", close: "'", notIn: ['string', 'comment'] },
    { open: '`', close: '`', notIn: ['string', 'comment'] }
  ],
  surroundingPairs: [
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '{', close: '}' },
    { open: '"', close: '"' },
    { open: "'", close: "'" },
    { open: '`', close: '`' },
    { open: '*', close: '*' },
    { open: '_', close: '_' }
  ],
  wordPattern: '[a-zA-Z0-9_-]+'
});

Events Emitted:

  • language_configuration_set - When configuration is set
  • language_configuration_removed - When configuration is removed (on dispose)

Throws: Error if language ID is not provided


getLanguages()

Get all registered languages.

Returns: Array\\<object\\> - Array of language definitions

Language Object Format:

{
  id: string,
  extensions: string[],
  aliases: string[],
  configuration?: object
}

Example:

const languages = api.languages.getLanguages();
console.log('Registered languages:');
languages.forEach(lang => {
  console.log(`- ${lang.id} (${lang.extensions.join(', ')})`);
});

Event Listeners

onDidChangeLanguages(listener)

Subscribe to language registry changes.

Parameters:

NameTypeRequiredDescription
listenerFunctionYesCallback function

Listener Signature:

() => void

Returns: Disposable - Disposable to unsubscribe

Example:

const disposable = api.languages.onDidChangeLanguages(() => {
  console.log('Language registry updated');
  const languages = api.languages.getLanguages();
  console.log(`Total languages: $\\{languages.length\\}`);
});
 
// Later, to unsubscribe:
disposable.dispose();

Cleanup Methods

unregisterAll(pluginId)

Unregister all providers, languages, and configurations for a plugin.

Parameters:

NameTypeRequiredDescription
pluginIdstringYesPlugin ID

Returns: void

Example:

// Called automatically during plugin deactivation
api.languages.unregisterAll('my-plugin-id');

Events Emitted:

  • did_change_languages - When cleanup is complete

Complete Example: Custom Language Support

Here’s a complete example showing how to add comprehensive language support for a custom language:

export function activate(api) {
  const disposables = [];
 
  // 1. Register the language
  disposables.push(
    api.languages.registerLanguage({
      id: 'todolang',
      extensions: ['.todo'],
      aliases: ['TodoLang', 'TODO Language']
    })
  );
 
  // 2. Set language configuration
  disposables.push(
    api.languages.setLanguageConfiguration('todolang', {
      comments: {
        lineComment: '#'
      },
      brackets: [
        ['[', ']'],
        ['(', ')']
      ],
      autoClosingPairs: [
        { open: '[', close: ']' },
        { open: '(', close: ')' }
      ]
    })
  );
 
  // 3. Register completion provider
  disposables.push(
    api.languages.registerCompletionProvider(
      'todolang',
      {
        provideCompletionItems(document, position) {
          const line = document.lineAt(position.line).text;
 
          // Suggest priority levels
          if (line.includes('priority:')) {
            return [
              { label: 'high', kind: 1, insertText: 'high' },
              { label: 'medium', kind: 1, insertText: 'medium' },
              { label: 'low', kind: 1, insertText: 'low' }
            ];
          }
 
          // Suggest status values
          if (line.includes('status:')) {
            return [
              { label: 'pending', kind: 1, insertText: 'pending' },
              { label: 'in-progress', kind: 1, insertText: 'in-progress' },
              { label: 'completed', kind: 1, insertText: 'completed' }
            ];
          }
 
          return [];
        }
      },
      ':' // Trigger on colon
    )
  );
 
  // 4. Register hover provider
  disposables.push(
    api.languages.registerHoverProvider('todolang', {
      provideHover(document, position) {
        const line = document.lineAt(position.line).text;
 
        // Show info for priority levels
        if (line.includes('priority:')) {
          return {
            contents: [
              '**Priority Levels**',
              '- `high`: Urgent tasks',
              '- `medium`: Normal tasks',
              '- `low`: Optional tasks'
            ]
          };
        }
 
        return null;
      }
    })
  );
 
  // 5. Register formatting provider
  disposables.push(
    api.languages.registerDocumentFormattingProvider('todolang', {
      provideDocumentFormattingEdits(document, options) {
        const text = document.getText();
        const lines = text.split('\n');
 
        // Sort tasks by priority
        const formatted = lines
          .sort((a, b) => {
            const getPriority = (line) => {
              if (line.includes('priority:high')) return 0;
              if (line.includes('priority:medium')) return 1;
              if (line.includes('priority:low')) return 2;
              return 3;
            };
            return getPriority(a) - getPriority(b);
          })
          .join('\n');
 
        return [
          {
            range: {
              start: { line: 0, character: 0 },
              end: { line: document.lineCount - 1, character: 9999 }
            },
            newText: formatted
          }
        ];
      }
    })
  );
 
  // 6. Register code action provider
  disposables.push(
    api.languages.registerCodeActionProvider('todolang', {
      provideCodeActions(document, range) {
        const text = document.getText(range);
        const actions = [];
 
        // Quick fix: Add priority
        if (!text.includes('priority:')) {
          actions.push({
            title: 'Add priority',
            kind: 'quickfix',
            edit: {
              changes: {
                [document.uri.toString()]: [
                  {
                    range: {
                      start: range.end,
                      end: range.end
                    },
                    newText: ' priority:medium'
                  }
                ]
              }
            }
          });
        }
 
        // Quick fix: Mark as completed
        if (text.includes('status:pending')) {
          actions.push({
            title: 'Mark as completed',
            kind: 'quickfix',
            edit: {
              changes: {
                [document.uri.toString()]: [
                  {
                    range: range,
                    newText: text.replace('status:pending', 'status:completed')
                  }
                ]
              }
            }
          });
        }
 
        return actions;
      }
    })
  );
 
  // Return deactivation function
  return {
    deactivate() {
      disposables.forEach(d => d.dispose());
    }
  };
}

See Also