'use strict' ; import { window , workspace , ExtensionContext , TextEditor , TextDocument , TextEditorSelectionChangeEvent , Selection , Range , StatusBarAlignment , StatusBarItem , ThemeColor } from 'vscode' ; import Variable from './lib/variables/variable' ; import ColorUtil , { IDecoration , DocumentLine , LineExtraction } from './lib/util/color-util' ; import Queue from './lib/queue' ; import VariablesManager from './lib/variables/variables-manager' ; import CacheManager from './lib/cache-manager' ; import EditorManager from './lib/editor-manager' ; import * as globToRegexp from 'glob-to-regexp' ; import VariableDecoration from './lib/variables/variable-decoration' ; import { getColorizeConfig , ColorizeConfig } from './lib/colorize-config' ; import Listeners from './listeners' ; let config = { languages : [ ] , isHideCurrentLineDecorations : true , colorizedVariables : [ ] , colorizedColors : [ ] , filesToExcludes : [ ] , filesToIncludes : [ ] , inferedFilesToInclude : [ ] , searchVariables : false , decorationFn : null } ; class ColorizeContext { editor : TextEditor = null ; nbLine = 0 ; deco : Map < number , IDecoration [ ] > = new Map ( ) ; currentSelection : number [ ] = null ; statusBar : StatusBarItem ; constructor ( ) { this . statusBar = window . createStatusBarItem ( StatusBarAlignment . Right ) ; } updateStatusBar ( activated ) { const icon = activated ? '$(check)' : '$(circle-slash)' ; const color = activated ? undefined : new ThemeColor ( 'errorForeground' ) ; const hoverMessage = activated ? 'Colorize is activated for this file' : 'Colorize is not activated for this file' ; this . statusBar . text = `${ icon } Colorize` ; this . statusBar . color = color ; this . statusBar . tooltip = hoverMessage ; this . statusBar . show ( ) ; } } const q = new Queue ( ) ; async function initDecorations ( context ) { if ( ! context . editor ) { return ; } const text = context . editor . document . getText ( ) ; const fileLines = ColorUtil . textToFileLines ( text ) ; const lines = context . editor . visibleRanges . reduce ( ( acc , range ) => { return [ ... acc , ... fileLines . slice ( range . start . line , range . end . line + 2 ) ] ; } , [ ] ) ; await VariablesManager . findVariablesDeclarations ( context . editor . document . fileName , fileLines ) ; const variables = await VariablesManager . findVariables ( context . editor . document . fileName , lines ) ; const colors = await ColorUtil . findColors ( lines ) ; generateDecorations ( colors , variables , context . deco ) ; return EditorManager . decorate ( context . editor , context . deco , context . currentSelection ) ; } function updateContextDecorations ( decorations , context ) { const it = decorations . entries ( ) ; let tmp = it . next ( ) ; while ( ! tmp . done ) { const line = tmp . value [ 0 ] ; if ( context . deco . has ( line ) ) { context . deco . set ( line , context . deco . get ( line ) . concat ( decorations . get ( line ) ) ) ; } else { context . deco . set ( line , decorations . get ( line ) ) ; } tmp = it . next ( ) ; } } function removeDuplicateDecorations ( context ) { const it = context . deco . entries ( ) ; const m = new Map ( ) ; let tmp = it . next ( ) ; while ( ! tmp . done ) { const line = tmp . value [ 0 ] ; const decorations = tmp . value [ 1 ] ; let newDecorations = [ ] ; decorations . forEach ( ( deco ) => { deco . generateRange ( line ) ; const exist = newDecorations . findIndex ( ( _ ) => deco . currentRange . isEqual ( _ . currentRange ) ) ; if ( exist !== - 1 ) { newDecorations [ exist ] . dispose ( ) ; newDecorations = newDecorations . filter ( ( _ , i ) => i !== exist ) ; } newDecorations . push ( deco ) ; } ) ; m . set ( line , newDecorations ) ; tmp = it . next ( ) ; } context . deco = m ; } function updateDecorationMap ( map , line , decoration ) { if ( map . has ( line ) ) { map . set ( line , map . get ( line ) . concat ( [ decoration ] ) ) ; } else { map . set ( line , [ decoration ] ) ; } } function generateDecorations ( colors , variables , decorations ) { colors . map ( ( { line , colors } ) => colors . forEach ( ( color ) => { const decoration = ColorUtil . generateDecoration ( color , line , config . decorationFn ) ; updateDecorationMap ( decorations , line , decoration ) ; } ) ) ; variables . map ( ( { line , colors } ) => colors . forEach ( ( variable ) => { const decoration = VariablesManager . generateDecoration ( < Variable > variable , line , config . decorationFn ) ; updateDecorationMap ( decorations , line , decoration ) ; } ) ) ; return decorations ; } param { string } languageId returns { boolean } function isLanguageSupported ( languageId ) { return config . languages . indexOf ( languageId ) !== - 1 ; } param { string } fileName returns { boolean } function isIncludedFile ( fileName ) { return config . filesToIncludes . find ( ( globPattern ) => globToRegexp ( globPattern ) . test ( fileName ) ) !== undefined ; } param { TextDocument } document returns { boolean } function canColorize ( document ) { return isLanguageSupported ( document . languageId ) || isIncludedFile ( document . fileName ) ; } function handleTextSelectionChange ( event , cb ) { if ( ! config . isHideCurrentLineDecorations || event . textEditor !== extension . editor ) { return cb ( ) ; } if ( extension . currentSelection . length !== 0 ) { extension . currentSelection . forEach ( line => { const decorations = extension . deco . get ( line ) ; if ( decorations !== undefined ) { EditorManager . decorateOneLine ( extension . editor , decorations , line ) ; } } ) ; } extension . currentSelection = [ ] ; event . selections . forEach ( ( selection ) => { const decorations = extension . deco . get ( selection . active . line ) ; if ( decorations ) { decorations . forEach ( _ => _ . hide ( ) ) ; } } ) ; extension . currentSelection = event . selections . map ( ( selection ) => selection . active . line ) ; return cb ( ) ; } function handleCloseOpen ( document ) { q . push ( ( cb ) => { if ( extension . editor && extension . editor . document . fileName === document . fileName ) { CacheManager . saveDecorations ( document , extension . deco ) ; return cb ( ) ; } return cb ( ) ; } ) ; } async function colorize ( editor , cb ) { extension . editor = null ; extension . deco = new Map ( ) ; if ( ! editor || ! canColorize ( editor . document ) ) { extension . updateStatusBar ( false ) ; return cb ( ) ; } extension . updateStatusBar ( true ) ; extension . editor = editor ; extension . currentSelection = editor . selections . map ( ( selection ) => selection . active . line ) ; const deco = CacheManager . getCachedDecorations ( editor . document ) ; if ( deco ) { extension . deco = deco ; extension . nbLine = editor . document . lineCount ; EditorManager . decorate ( extension . editor , extension . deco , extension . currentSelection ) ; } else { extension . nbLine = editor . document . lineCount ; try { await initDecorations ( extension ) ; } finally { CacheManager . saveDecorations ( extension . editor . document , extension . deco ) ; } } return cb ( ) ; } function handleChangeActiveTextEditor ( editor ) { if ( extension . editor !== undefined && extension . editor !== null ) { extension . deco . forEach ( decorations => decorations . forEach ( deco => deco . hide ( ) ) ) ; CacheManager . saveDecorations ( extension . editor . document , extension . deco ) ; } getVisibleFileEditors ( ) . filter ( e => e !== editor ) . forEach ( e => { q . push ( cb => colorize ( e , cb ) ) ; } ) ; q . push ( cb => colorize ( editor , cb ) ) ; } function cleanDecorationList ( context , cb ) { const it = context . deco . entries ( ) ; let tmp = it . next ( ) ; while ( ! tmp . done ) { const line = tmp . value [ 0 ] ; const decorations = tmp . value [ 1 ] ; context . deco . set ( line , decorations . filter ( decoration => ! decoration . disposed ) ) ; tmp = it . next ( ) ; } return cb ( ) ; } function clearCache ( ) { extension . deco . clear ( ) ; extension . deco = new Map ( ) ; CacheManager . clearCache ( ) ; } function handleConfigurationChanged ( ) { const newConfig = getColorizeConfig ( ) ; clearCache ( ) ; ColorUtil . setupColorsExtractors ( newConfig . colorizedColors ) ; q . push ( async ( cb ) => { VariablesManager . setupVariablesExtractors ( newConfig . colorizedVariables ) ; if ( newConfig . searchVariables ) { await VariablesManager . getWorkspaceVariables ( newConfig . filesToIncludes . concat ( newConfig . inferedFilesToInclude ) , newConfig . filesToExcludes ) ; } return cb ( ) ; } ) ; config = newConfig ; colorizeVisibleTextEditors ( ) ; } function initEventListeners ( context ) { window . onDidChangeTextEditorSelection ( ( event ) => q . push ( ( cb ) => handleTextSelectionChange ( event , cb ) ) , null , context . subscriptions ) ; workspace . onDidCloseTextDocument ( handleCloseOpen , null , context . subscriptions ) ; workspace . onDidSaveTextDocument ( handleCloseOpen , null , context . subscriptions ) ; window . onDidChangeActiveTextEditor ( handleChangeActiveTextEditor , null , context . subscriptions ) ; workspace . onDidChangeConfiguration ( handleConfigurationChanged , null , context . subscriptions ) ; Listeners . setupEventListeners ( context ) ; } function getVisibleFileEditors ( ) { return window . visibleTextEditors . filter ( editor => editor . document . uri . scheme === 'file' ) ; } function colorizeVisibleTextEditors ( ) { extension . nbLine = 65 ; getVisibleFileEditors ( ) . forEach ( editor => { q . push ( cb => colorize ( editor , cb ) ) ; } ) ; } let extension ; export function activate ( context ) { extension = new ColorizeContext ( ) ; config = getColorizeConfig ( ) ; ColorUtil . setupColorsExtractors ( config . colorizedColors ) ; VariablesManager . setupVariablesExtractors ( config . colorizedVariables ) ; q . push ( async ( cb ) => { try { if ( config . searchVariables ) { await VariablesManager . getWorkspaceVariables ( config . filesToIncludes . concat ( config . inferedFilesToInclude ) , config . filesToExcludes ) ; } initEventListeners ( context ) ; } catch ( error ) { } return cb ( ) ; } ) ; colorizeVisibleTextEditors ( ) ; return extension ; } export function deactivate ( ) { extension . nbLine = null ; extension . editor = null ; extension . deco . clear ( ) ; extension . deco = null ; CacheManager . clearCache ( ) ; } export { canColorize , ColorizeContext , colorize , config , extension , q , updateContextDecorations , generateDecorations , removeDuplicateDecorations , cleanDecorationList } ;