'use strict' ; import * as vscode from 'vscode' ; import * as path from 'path' ; import { ExtensionContext , TextDocumentContentProvider , EventEmitter , Event , Uri , ViewColumn } from 'vscode' ; import TelemetryReporter from 'vscode-extension-telemetry' ; interface IPackageInfo { name : string ; version : string ; aiKey : string ; } var telemetryReporter ; export function activate ( context ) { let packageInfo = getPackageInfo ( context ) ; telemetryReporter = packageInfo && new TelemetryReporter ( packageInfo . name , packageInfo . version , packageInfo . aiKey ) ; let provider = new MDDocumentContentProvider ( context ) ; let registration = vscode . workspace . registerTextDocumentContentProvider ( 'markdown' , provider ) ; 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 ) ; context . subscriptions . push ( d1 , d2 , d3 , registration ) ; vscode . workspace . onDidSaveTextDocument ( document => { if ( isMarkdownFile ( document ) ) { const uri = getMarkdownUri ( document . uri ) ; provider . update ( uri ) ; } } ) ; vscode . workspace . onDidChangeTextDocument ( event => { if ( isMarkdownFile ( event . document ) ) { const uri = getMarkdownUri ( event . document . uri ) ; provider . update ( uri ) ; } } ) ; vscode . workspace . onDidChangeConfiguration ( ( ) => { vscode . workspace . textDocuments . forEach ( document => { if ( document . uri . scheme === 'markdown' ) { provider . update ( document . uri ) ; } } ) ; } ) ; } function isMarkdownFile ( document ) { return document . languageId === 'markdown' && document . uri . scheme !== 'markdown' ; } function getMarkdownUri ( uri ) { return uri . with ( { scheme : 'markdown' , path : uri . path + '.rendered' , query : uri . toString ( ) } ) ; } function showPreview ( uri ? , sideBySide = false ) { let resource = uri ; if ( ! ( resource instanceof Uri ) ) { if ( vscode . window . activeTextEditor ) { resource = vscode . window . activeTextEditor . document . uri ; } } if ( ! ( resource instanceof Uri ) ) { if ( ! vscode . window . activeTextEditor ) { return vscode . commands . executeCommand ( 'markdown.showSource' ) ; } return ; } let thenable = vscode . commands . executeCommand ( 'vscode.previewHtml' , getMarkdownUri ( resource ) , getViewColumn ( sideBySide ) , `Preview '${ path . basename ( resource . fsPath ) }'` ) ; telemetryReporter . sendTelemetryEvent ( 'openPreview' , { where : sideBySide ? 'sideBySide' : 'inPlace' , how : ( uri instanceof Uri ) ? 'action' : 'pallete' } ) ; return thenable ; } function getViewColumn ( sideBySide ) { const active = vscode . window . activeTextEditor ; if ( ! active ) { return ViewColumn . One ; } if ( ! sideBySide ) { return active . viewColumn ; } switch ( active . viewColumn ) { case ViewColumn . One : return ViewColumn . Two ; case ViewColumn . Two : return ViewColumn . Three ; } return active . viewColumn ; } function showSource ( mdUri ) { if ( ! mdUri ) { return vscode . commands . executeCommand ( 'workbench.action.navigateBack' ) ; } const docUri = Uri . parse ( mdUri . query ) ; for ( let editor of vscode . window . visibleTextEditors ) { if ( editor . document . uri . toString ( ) === docUri . toString ( ) ) { return vscode . window . showTextDocument ( editor . document , editor . viewColumn ) ; } } return vscode . workspace . openTextDocument ( docUri ) . then ( doc => { return vscode . window . showTextDocument ( doc ) ; } ) ; } function getPackageInfo ( context ) { let extensionPackage = require ( context . asAbsolutePath ( './package.json' ) ) ; if ( extensionPackage ) { return { name : extensionPackage . name , version : extensionPackage . version , aiKey : extensionPackage . aiKey } ; } return null ; } interface IRenderer { render ( text ) : string ; } class MDDocumentContentProvider implements TextDocumentContentProvider { private _context : ExtensionContext ; private _onDidChange = new EventEmitter < Uri > ( ) ; private _waiting : boolean ; private _renderer : IRenderer ; constructor ( context ) { this . _context = context ; this . _waiting = false ; this . _renderer = this . createRenderer ( ) ; } private createRenderer ( ) { const hljs = require ( 'highlight.js' ) ; const mdnh = require ( 'markdown-it-named-headers' ) ; const md = require ( 'markdown-it' ) ( { html : true , highlight : function ( str , lang ) { 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 ; } private getMediaPath ( mediaFile ) { return this . _context . asAbsolutePath ( path . join ( 'media' , mediaFile ) ) ; } private isAbsolute ( p ) { return path . normalize ( p + '/' ) === path . normalize ( path . resolve ( p ) + '/' ) ; } private fixHref ( resource , href ) { if ( href ) { if ( Uri . parse ( href ) . scheme ) { return href ; } if ( this . isAbsolute ( href ) ) { return Uri . file ( href ) . toString ( ) ; } let rootPath = vscode . workspace . rootPath ; if ( rootPath ) { return Uri . file ( path . join ( rootPath , href ) ) . toString ( ) ; } return Uri . file ( path . join ( path . dirname ( resource . fsPath ) , href ) ) . toString ( ) ; } return href ; } private computeCustomStyleSheetIncludes ( uri ) { const styles = vscode . workspace . getConfiguration ( 'markdown' ) [ 'styles' ] ; if ( styles && Array . isArray ( styles ) && styles . length > 0 ) { return styles . map ( ( style ) => { return `<link rel="stylesheet" href="${ this . fixHref ( uri , style ) }" type="text/css" media="screen">` ; } ) ; } return [ ] ; } public provideTextDocumentContent ( uri ) { return vscode . workspace . openTextDocument ( Uri . parse ( uri . query ) ) . then ( document => { const head = [ ] . concat ( '<!DOCTYPE html>' , '<html>' , '<head>' , '<meta http-equiv="Content-type" content="text/html;charset=UTF-8">' , `<link rel="stylesheet" type="text/css" href="${ this . getMediaPath ( 'markdown.css' ) }" >` , `<link rel="stylesheet" type="text/css" href="${ this . getMediaPath ( 'tomorrow.css' ) }" >` , this . computeCustomStyleSheetIncludes ( uri ) , `<base href="${ document . uri . toString ( true ) }">` , '</head>' , '<body>' ) . join ( '\n' ) ; const body = this . _renderer . render ( document . getText ( ) ) ; const tail = [ '</body>' , '</html>' ] . join ( '\n' ) ; return head + body + tail ; } ) ; } get onDidChange ( ) : Event < Uri > { return this . _onDidChange . event ; } public update ( uri ) { if ( ! this . _waiting ) { this . _waiting = true ; setTimeout ( ( ) => { this . _waiting = false ; this . _onDidChange . fire ( uri ) ; } , 300 ) ; } } }