1 VS Code Extension API
Brett Cannon редактировал(а) эту страницу 2021-01-14 14:33:36 -08:00

The VS Code extension API is divided into two distinct parts (conceptually):

  • registration of handlers by which VS Code requests actions or data from an extension
  • functions (and types, constants, etc.) by which an extension requests actions or data from VS Code

In addition to that concrete API, VS Code defines several other ways that extensions may interact with it or provide it resources:

  • the package.json file (a superset of the npm package.json) defines static data about the extension. This includes commands, settings, language configuration, and debugger definitions. (Note that a number of items from that static data can be extended or overridden via the API.)
  • debug adapters (via "DAP") interact with VS Code (which acts as the debug client) to support debugging operations. They help make debuggers more independent from VS Code and its extensions.
  • Language Servers (via "LSP") consolidate much of the language-specific functionality provided by registered handlers in the VS Code API, effectively replacing those handlers. As with debug adapters, this allows such language services to be more independent from VS Code and its extensions.

The Python extension for VS Code takes adavantage of all of the above.


Note: hypothetically, an extension could be written in Python, either directly or with a light typescript wrapper around the VS Code API. I'm just saying...

Analysis of Hooks Used by the Extension

Usage:

  • `src/client/extension.ts'
    • debug.registerDebugConfigurationProvider (+1)
    • languages.registerCodeActionsProvider (+1)
    • languages.registerDocumentFormattingEditProvider
    • languages.registerDocumentRangeFormattingEditProvider
    • languages.setLanguageConfiguration
  • src/client/activation/jedi.ts
    • commands.registerCommand (+2)
    • languages.registerCodeLensProvider (+2)
    • languages.registerCompletionItemProvider (+1)
    • languages.registerDefinitionProvider
    • languages.registerDocumentSymbolProvider
    • languages.registerHoverProvider
    • languages.registerOnTypeFormattingEditProvider
    • languages.registerReferenceProvider
    • languages.registerRenameProvider
    • languages.registerSignatureHelpProvider
  • src/client/common/application/debugService.ts
    • debug.registerDebugAdapterDescriptorFactory
    • debug.registerDebugAdapterTrackerFactory
    • debug.registerDebugConfigurationProvider (+1)
  • in other files:
    • commands.registerCommand (3, incl. jedi)
    • languages.registerCodeLensProvider (3, incl. jedi)
    • languages.registerCompletionItemProvider (2, incl. jedi)
    • languages.registerCodeActionsProvider (2, incl. extension.ts)
    • commands.registerTextEditorCommand (1)
    • languages.registerWorkspaceSymbolProvider (1)
    • window.registerTreeDataProvider (1)
USED (by hook)
commands.registerCommand                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  src/client/activation/jedi.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
  src/client/common/application/commandManager.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
  src/client/providers/simpleRefactorProvider.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
  src/client/providers/simpleRefactorProvider.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
commands.registerTextEditorCommand                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  src/client/common/application/commandManager.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
debug.registerDebugAdapterDescriptorFactory                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  src/client/common/application/debugService.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
debug.registerDebugAdapterTrackerFactory                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  src/client/common/application/debugService.ts                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      
debug.registerDebugConfigurationProvider                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              
------------------------------                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        
  src/client/common/application/debugService.ts
  src/client/extension.ts

languages.registerCodeActionsProvider
------------------------------
  src/client/extension.ts
  src/client/providers/codeActionProvider/main.ts

languages.registerCodeLensProvider
------------------------------
  src/client/activation/jedi.ts
  src/client/datascience/datascience.ts
  src/client/testing/codeLenses/main.ts

languages.registerCompletionItemProvider
------------------------------
  src/client/activation/jedi.ts
  src/client/common/application/languageService.ts

languages.registerDefinitionProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerDocumentFormattingEditProvider
------------------------------
  src/client/extension.ts

languages.registerDocumentRangeFormattingEditProvider
------------------------------
  src/client/extension.ts

languages.registerDocumentSymbolProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerHoverProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerOnTypeFormattingEditProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerReferenceProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerRenameProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerSignatureHelpProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerSignatureHelpProvider
------------------------------
  src/client/activation/jedi.ts

languages.registerWorkspaceSymbolProvider
------------------------------
  src/client/workspaceSymbols/main.ts

languages.setLanguageConfiguration
------------------------------
  src/client/extension.ts

window.registerTreeDataProvider
------------------------------
  src/client/datascience/liveshare/liveshareProxy.ts

total used: 21

NOT USED
  languages.registerCallHierarchyProvider
  languages.registerColorProvider
  languages.registerDeclarationProvider
  languages.registerDocumentHighlightProvider
  languages.registerDocumentLinkProvider
  languages.registerFoldingRangeProvider
  languages.registerImplementationProvider
  languages.registerSelectionRangeProvider
  languages.registerTypeDefinitionProvider
  tasks.registerTaskProvider
  window.registerUriHandler
  window.registerWebviewPanelSerializer
  workspace.registerFileSystemProvider
  workspace.registerTaskProvider
  workspace.registerTextDocumentContentProvider

total not used: 15

USED (by file)
src/client/activation/jedi.ts
------------------------------
  commands.registerCommand
  languages.registerCodeLensProvider
  languages.registerCompletionItemProvider
  languages.registerDefinitionProvider
  languages.registerDocumentSymbolProvider
  languages.registerHoverProvider
  languages.registerOnTypeFormattingEditProvider
  languages.registerReferenceProvider
  languages.registerRenameProvider
  languages.registerSignatureHelpProvider

src/client/common/application/commandManager.ts
------------------------------
  commands.registerCommand
  commands.registerTextEditorCommand

src/client/common/application/debugService.ts
------------------------------
  debug.registerDebugAdapterDescriptorFactory
  debug.registerDebugAdapterTrackerFactory
  debug.registerDebugConfigurationProvider

src/client/common/application/languageService.ts
------------------------------
  languages.registerCompletionItemProvider

src/client/datascience/datascience.ts
------------------------------
  languages.registerCodeLensProvider

src/client/datascience/liveshare/liveshareProxy.ts
------------------------------
  window.registerTreeDataProvider

src/client/extension.ts
------------------------------
  debug.registerDebugConfigurationProvider
  languages.registerCodeActionsProvider
  languages.registerDocumentFormattingEditProvider
  languages.registerDocumentRangeFormattingEditProvider
  languages.setLanguageConfiguration

src/client/providers/codeActionProvider/main.ts
------------------------------
  languages.registerCodeActionsProvider

src/client/providers/simpleRefactorProvider.ts
------------------------------
  commands.registerCommand
  commands.registerCommand

src/client/testing/codeLenses/main.ts
------------------------------
  languages.registerCodeLensProvider

src/client/workspaceSymbols/main.ts
------------------------------
  languages.registerWorkspaceSymbolProvider

total files using hooks: 11

Extra Information

script to analyze our usage of the VS Code API
# XXX Find all "known" hooks (in node_modules/@types/vscode/index.d.ts).
# XXX Find all other uses of the VS Code API.

import re
import textwrap


def _regex_join_options(opts, indent='    '):
    return f'\n{indent}|\n{indent}'.join(opts)


#######################################
# helpers for hooks

def find_known_hooks():
    # For now we hard-code the list.
    return [
        'tasks.registerTaskProvider',

        'commands.registerCommand',
        'commands.registerTextEditorCommand',

        'window.registerTreeDataProvider',  # <T>
        'window.registerUriHandler',
        'window.registerWebviewPanelSerializer',

        'workspace.registerFileSystemProvider',
        'workspace.registerTaskProvider',
        'workspace.registerTextDocumentContentProvider',

        'languages.registerCallHierarchyProvider',
        'languages.registerCodeActionsProvider',
        'languages.registerCodeLensProvider',
        'languages.registerColorProvider',
        'languages.registerCompletionItemProvider',
        'languages.registerDeclarationProvider',
        'languages.registerDefinitionProvider',
        'languages.registerDocumentFormattingEditProvider',
        'languages.registerDocumentHighlightProvider',
        'languages.registerDocumentLinkProvider',
        'languages.registerDocumentRangeFormattingEditProvider',
        'languages.registerDocumentSymbolProvider',
        'languages.registerFoldingRangeProvider',
        'languages.registerHoverProvider',
        'languages.registerImplementationProvider',
        'languages.registerOnTypeFormattingEditProvider',
        'languages.registerReferenceProvider',
        'languages.registerRenameProvider',
        'languages.registerSelectionRangeProvider',
        'languages.registerSignatureHelpProvider',
        'languages.registerSignatureHelpProvider',
        'languages.registerTypeDefinitionProvider',
        'languages.registerWorkspaceSymbolProvider',
        'languages.setLanguageConfiguration',

        'debug.registerDebugAdapterDescriptorFactory',
        'debug.registerDebugAdapterTrackerFactory',
        'debug.registerDebugConfigurationProvider',
    ]


def iter_hooks(srclines, *, known=None, alts=None):
    if not known:
        known = find_known_hooks()

    _hooks = (v.replace('.', '\.') for v in known + list(alts or ()))
    regex = re.compile(
        textwrap.dedent(rf'''
            ^
            .*? \b
            (
                {_regex_join_options(_hooks, '            ')}
             )
            \s* (?: < [^>]+ > \s* )?
            [(]
        '''),
        re.VERBOSE
    )
    for line in srclines:
        m = regex.match(line)
        if not m:
            continue
        hook, = m.groups()
        yield alts.get(hook, hook) if alts else hook


def find_all_hooks(filenames, *, known=None, alts=None):
    if not known:
        known = find_known_hooks()

    for filename in filenames or ():
        with open(filename) as srcfile:
            for hook in iter_hooks(srcfile, known=known, alts=alts):
                yield filename, hook


def analyze_found_hooks(results):
    hooks = {}
    files = {}
    for filename, hook in results:
        try:
            found = hooks[hook]
        except KeyError:
            found = hooks[hook] = []
        if filename not in found:
            found.append(filename)

        try:
            found = files[filename]
        except KeyError:
            found = files[filename] = []
        found.append(hook)
    return hooks, files


#######################################
# commands

# hooks

HOOKS_ALTS = {
    '.registerTreeDataProvider': 'window.registerTreeDataProvider',
}


def cmd_hooks(filenames, show=None):
    try:
        run_cmd = CMD_HOOKS_SHOW[show or 'summary']
    except KeyError:
        raise ValueError(f'unsupported --show {show!r}')

    if run_cmd is None:
        raise NotImplementedError(show)

    known = find_known_hooks()
    run_cmd(
        find_all_hooks(filenames, known=known, alts=HOOKS_ALTS),
        filenames=filenames,
        known=known,
    )


def cmd_hooks_raw(results, **ignored):
    total = 0
    for filename, hook in results:  # We do not sort.
        print(f'{filename:50} {hook}')
        total += 1
    print('----')
    print(f'total: {total}')
    print()


def cmd_hooks_by_hook(results, known, **ignored):
    hooks, _= analyze_found_hooks(results)

    for hook in sorted(hooks, key=lambda h: (-len(hooks[h]), h)):
        found = hooks.get(hook, ())
        if not found:
            continue

        print()
        print(f'{hook} ({len(hooks[hook])})')
        print('------------------------------')
        for filename in sorted(found):
            print(' ', filename)
    print()


def cmd_hooks_by_file(results, **ignored):
    _, files = analyze_found_hooks(results)

    for filename in sorted(files, key=lambda f: (-len(files[f]), f)):
        print()
        print(f'{filename} ({len(files[filename])})')
        print('------------------------------')
        for hook in sorted(files[filename]):
            print(' ', hook)
    print()


HOOKS_EXPECTED = [
    'commands.registerCommand',
    'commands.registerTextEditorCommand',
    'window.registerTreeDataProvider',
    'languages.registerCodeActionsProvider',
    'languages.registerCodeLensProvider',
    'languages.registerCompletionItemProvider',
    'languages.registerDefinitionProvider',
    'languages.registerDocumentFormattingEditProvider',
    'languages.registerDocumentRangeFormattingEditProvider',
    'languages.registerDocumentSymbolProvider',
    'languages.registerHoverProvider',
    'languages.registerOnTypeFormattingEditProvider',
    'languages.registerReferenceProvider',
    'languages.registerRenameProvider',
    'languages.registerSignatureHelpProvider',
    'languages.registerSignatureHelpProvider',
    'languages.registerWorkspaceSymbolProvider',
    'languages.setLanguageConfiguration',
    'debug.registerDebugAdapterDescriptorFactory',
    'debug.registerDebugAdapterTrackerFactory',
    'debug.registerDebugConfigurationProvider',
]


def cmd_hooks_summary(results, known, filenames, **ignored):
    single = len(filenames) == 1
    hooks, files = analyze_found_hooks(results)

    print()
    print(f'using {len(hooks)} hooks:')
    for hook in sorted(hooks, key=lambda h: (-len(hooks[h]), h)):
        if single:
            print(f'  ', hook)
        else:
            print(f'  {len(hooks[hook]):>2} {hook}')

    if len(filenames) > 100:  # an approximation of "all"
        missing = set(HOOKS_EXPECTED) - set(hooks)
        if missing:
            print()
            print(f'expected but not found ({len(missing)}):')
            for hook in sorted(missing):
                print('  ', hook)

    notused = set(known) - set(hooks)
    if notused:
        print()
        print(f'not used ({len(notused)}):')
        for hook in sorted(notused):
            print('  ', hook)

    if not single:
        print()
        print(f'found {len(files)} files using hooks:')
        for filename in sorted(files, key=lambda f: (-len(files[f]), f)):
            print(f'  {len(files[filename]):>2} {filename}')


CMD_HOOKS_SHOW = {
    'raw': cmd_hooks_raw,
    'hooks': cmd_hooks_by_hook,
    'files': cmd_hooks_by_file,
    'summary': cmd_hooks_summary,
}


# api

def cmd_api(filenames, show=None):
    try:
        run_cmd = CMD_API_SHOW[show or 'summary']
    except KeyError:
        raise ValueError(f'unsupported --show {show!r}')

    if run_cmd is None:
        raise NotImplementedError(show)
    run_cmd(filenames)


CMD_API_SHOW = {
    'raw': None,
    'hooks': None,
    'files': None,
    'summary': None,
}


# the registry

COMMANDS = {
    'hooks': cmd_hooks,
    'api': cmd_api,
}
COMMANDS_DEFAULT = 'hooks'

SHOW = {
    'hooks': CMD_HOOKS_SHOW,
    'api': CMD_API_SHOW,
}


#######################################
# the script

import sys


def parse_args(argv=sys.argv[1:], prog=sys.argv[0]):
    import argparse

    # Set up the "default" command.
    if argv and argv[0] not in COMMANDS:
        if '.' in argv[0] or argv[0].startswith('-'):
            # This doesn't guard against "--".
            if '-h' in argv or '--help' in argv:
                argv = ['--help']
            else:
                # Use a sensible default for now.
                argv.insert(0, COMMANDS_DEFAULT)

    parser = argparse.ArgumentParser(
        prog=prog,
    )
    subs = parser.add_subparsers(dest='cmd')

    for cmd in sorted(COMMANDS):
        sub = subs.add_parser(cmd)
        if cmd in SHOW:
            sub.add_argument(
                '--show', choices=sorted(SHOW[cmd]), default='summary',
            )
        sub.add_argument('filenames', nargs='+')

    args = parser.parse_args(argv)
    ns = vars(args)

    cmd = ns.pop('cmd')

    return cmd, ns


def main(cmd, **cmd_kwargs):
    try:
        run_cmd = COMMANDS[cmd]
    except KeyError:
        raise ValueError(f'unsupported --cmd {cmd!r}')

    run_cmd(**cmd_kwargs)


if __name__ == '__main__':
    cmd, kwargs = parse_args()
    main(cmd, **kwargs)