Change:
It's very hard to navigate a large markdown file, such as our release notes. Outline information would be very helpful, let's do this!
See

Discussion:
"Languages" ], "activationEvents": [ <ins> "onLanguage:markdown", </ins> "onCommand:markdown.showPreview", "onCommand:markdown.showPreviewToSide", "onCommand:markdown.showSource"

var telemetryReporter: TelemetryReporter | null; <ins> interface IToken { type: string; map: [number, number]; } interface MarkdownIt { render(text: string): string; parse(text: string): IToken[]; } class MarkdownEngine { private _markdownIt: MarkdownIt | undefined; private get markdownIt(): MarkdownIt { if (!this._markdownIt) { const hljs = require('highlight.js'); const mdnh = require('markdown-it-named-headers'); const md = require('markdown-it')({ html: true, highlight: (str: string, lang: string) => { if (lang && hljs.getLanguage(lang)) { try { return `<pre class="hljs"><code><div>${hljs.highlight(lang, str, true).value}</div></code></pre>`; } catch (error) { } } return `<pre class="hljs"><code><div>${md.utils.escapeHtml(str)}</div></code></pre>`; } }).use(mdnh, {}); this._markdownIt = md as MarkdownIt; } return this._markdownIt; } render(text: string): string { return this.markdownIt.render(text); } parse(text: string): IToken[] { return this.markdownIt.parse(text); } } const FrontMatterRegex = /^---s*(.|s)*?---s*/; </ins> export function activate(context: vscode.ExtensionContext) { let packageInfo = getPackageInfo(context); telemetryReporter = packageInfo && new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey); <del> let provider = new MDDocumentContentProvider(context); let registration = vscode.workspace.registerTextDocumentContentProvider('markdown', provider); </del> <ins> const engine = new MarkdownEngine(); const contentProvider = new MDDocumentContentProvider(engine, context); const contentProviderRegistration = vscode.workspace.registerTextDocumentContentProvider('markdown', contentProvider); const symbolsProvider = new MDDocumentSymbolProvider(engine); const symbolsProviderRegistration = vscode.languages.registerDocumentSymbolProvider({ language: 'markdown' }, symbolsProvider); </ins> let d1 = vscode.commands.registerCommand('markdown.showPreview', showPreview); let d2 = vscode.commands.registerCommand('markdown.showPreviewToSide', uri => showPreview(uri, true)); let d3 = vscode.commands.registerCommand('markdown.showSource', showSource); <del> context.subscriptions.push(d1, d2, d3, registration); </del> <ins> context.subscriptions.push(d1, d2, d3, contentProviderRegistration, symbolsProviderRegistration); </ins> vscode.workspace.onDidSaveTextDocument(document => { if (isMarkdownFile(document)) { const uri = getMarkdownUri(document.uri); <del> provider.update(uri); </del> <ins> contentProvider.update(uri); </ins> } }); vscode.workspace.onDidChangeTextDocument(event => { if (isMarkdownFile(event.document)) { const uri = getMarkdownUri(event.document.uri); <del> provider.update(uri); </del> <ins> contentProvider.update(uri); </ins> } });

vscode.workspace.textDocuments.forEach(document => { if (document.uri.scheme === 'markdown') { // update all generated md documents <del> provider.update(document.uri); </del> <ins> contentProvider.update(document.uri); </ins> } }); });

return null; } <del> interface IRenderer { render(text: string): string; } </del> class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { private _context: vscode.ExtensionContext; private _onDidChange = new vscode.EventEmitter<vscode.Uri>(); private _waiting: boolean; <del> private _renderer: IRenderer; constructor(context: vscode.ExtensionContext) { </del> <ins> constructor(private engine: MarkdownEngine, context: vscode.ExtensionContext) { </ins> this._context = context; this._waiting = false; <del> this._renderer = this.createRenderer(); } private createRenderer(): IRenderer { const hljs = require('highlight.js'); const mdnh = require('markdown-it-named-headers'); const md = require('markdown-it')({ html: true, highlight: (str: string, lang: string) => { if (lang && hljs.getLanguage(lang)) { try { return `<pre class="hljs"><code><div>${hljs.highlight(lang, str, true).value}</div></code></pre>`; } catch (error) { } } return `<pre class="hljs"><code><div>${md.utils.escapeHtml(str)}</div></code></pre>`; } }).use(mdnh, {}); return md; </del> } private getMediaPath(mediaFile: string): string {

'</head>', `<body class="${scrollBeyondLastLine ? 'scrollBeyondLastLine' : ''}">` ).join('n'); <del> const body = this._renderer.render(this.getDocumentContentForPreview(document)); </del> <ins> const previewFrontMatter = vscode.workspace.getConfiguration('markdown')['previewFrontMatter']; const text = document.getText(); const contents = previewFrontMatter === 'hide' ? text.replace(FrontMatterRegex, '') : text; const body = this.engine.render(contents); </ins> const tail = [ '</body>',

}, 300); } } <ins> } class MDDocumentSymbolProvider implements vscode.DocumentSymbolProvider { </ins> <del> private getDocumentContentForPreview(document: vscode.TextDocument): string { const content = document.getText(); const previewFrontMatter = vscode.workspace.getConfiguration('markdown')['previewFrontMatter']; if (previewFrontMatter === 'hide') { return content.replace(/^-{3}[ t]*(rn|n)(.|rn|n)*?(rn|n)-{3}[ t]*(rn|n)/, ''); </del> <ins> constructor(private engine: MarkdownEngine) { } provideDocumentSymbols(document: vscode.TextDocument): vscode.ProviderResult<vscode.SymbolInformation[]> { let offset = 0; let text = document.getText(); const frontMatterMatch = FrontMatterRegex.exec(text); if (frontMatterMatch) { const frontMatter = frontMatterMatch[0]; offset = frontMatter.split(/rn|n|r/g).length - 1; text = text.substr(frontMatter.length); </ins> } <del> return content; </del> <ins> const tokens = this.engine.parse(text); const headings = tokens.filter(token => token.type === 'heading_open'); const symbols = headings.map(heading => { const lineNumber = heading.map[0]; const line = document.lineAt(lineNumber + offset); const location = new vscode.Location(document.uri, line.range); // # Header        => 'Header' // ## Header ##    => 'Header' // ## Header ####  => 'Header' // Header ##       => 'Header ##' // ========= const text = line.text.replace(/^s*(#)+s*(.*?)s*1*$/, '$2'); return new vscode.SymbolInformation(text, vscode.SymbolKind.Module, '', location); }); return symbols; </ins> } }  No newline at end of file