/********************************************************************************
 * Copyright (c) 2021 SAP SE or an SAP affiliate company and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the Eclipse
 * Public License v. 2.0 are satisfied: GNU General Public License, version 2
 * with the GNU Classpath Exception which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 ********************************************************************************/ import { inject , injectable } from 'inversify' ; import { KeybindingRegistry } from '../keybinding' ; import { Disposable , Command , CommandRegistry , CancellationToken } from '../../common' ; import { ContextKeyService } from '../context-key-service' ; import { CorePreferences } from '../core-preferences' ; import { QuickAccessContribution , QuickAccessProvider , QuickAccessRegistry } from './quick-access' ; import { filterItems , QuickPickItem , QuickPicks } from './quick-input-service' ; import { KeySequence } from '../keys' ; import { codiconArray } from '../widgets' ; export const quickCommand = { id : 'workbench.action.showCommands' } ; export const CLEAR_COMMAND_HISTORY = Command . toDefaultLocalizedCommand ( { id : 'clear.command.history' , label : 'Clear Command History' } ) ; @ injectable ( ) export class QuickCommandService implements QuickAccessContribution , QuickAccessProvider { static PREFIX = '>' ; @ inject ( ContextKeyService ) protected readonly contextKeyService : ContextKeyService ; @ inject ( CommandRegistry ) protected readonly commandRegistry : CommandRegistry ; @ inject ( CorePreferences ) protected readonly corePreferences : CorePreferences ; @ inject ( QuickAccessRegistry ) protected readonly quickAccessRegistry : QuickAccessRegistry ; @ inject ( KeybindingRegistry ) protected readonly keybindingRegistry : KeybindingRegistry ; readonly exemptedCommands : Command [ ] = [ CLEAR_COMMAND_HISTORY , ] ; private recentItems : QuickPickItem [ ] = [ ] ; private otherItems : QuickPickItem [ ] = [ ] ; registerQuickAccessProvider ( ) { this . quickAccessRegistry . registerQuickAccessProvider ( { getInstance : ( ) => this , prefix : QuickCommandService . PREFIX , placeholder : '' , helpEntries : [ { description : 'Quick Command' , needsEditor : false } ] } ) ; } reset ( ) { const { recent , other } = this . getCommands ( ) ; this . recentItems = [ ] ; this . otherItems = [ ] ; this . recentItems . push ( ... recent . map ( command => this . toItem ( command ) ) ) ; this . otherItems . push ( ... other . map ( command => this . toItem ( command ) ) ) ; } getPicks ( filter , token ) { const items = [ ] ; this . reset ( ) ; const recentItems = filterItems ( this . recentItems . slice ( ) , filter ) ; const otherItems = filterItems ( this . otherItems . slice ( ) , filter ) ; if ( recentItems . length > 0 ) { items . push ( { type : 'separator' , label : 'recently used' } , ... recentItems ) ; } if ( otherItems . length > 0 ) { if ( recentItems . length > 0 ) { items . push ( { type : 'separator' , label : 'other commands' } ) ; } items . push ( ... otherItems ) ; } return items ; } private toItem ( command ) { const label = ( command . category ) ? `${ command . category }: ` + command . label ! : command . label ! ; const iconClasses = this . getItemIconClasses ( command ) ; const activeElement = window . document . activeElement as HTMLElement ; const originalLabel = command . originalLabel || command . label ! ; const originalCategory = command . originalCategory || command . category ; let detail = originalCategory ? `${ originalCategory }: ${ originalLabel }` : originalLabel ; if ( label === detail ) { detail = undefined ; } return { label , detail , iconClasses , alwaysShow : ! ! this . commandRegistry . getActiveHandler ( command . id ) , keySequence : this . getKeybinding ( command ) , execute : ( ) => { activeElement . focus ( { preventScroll : true } ) ; this . commandRegistry . executeCommand ( command . id ) ; this . commandRegistry . addRecentCommand ( command ) ; } } ; } private getKeybinding ( command ) { const keybindings = this . keybindingRegistry . getKeybindingsForCommand ( command . id ) ; if ( ! keybindings || keybindings . length === 0 ) { return undefined ; } try { return this . keybindingRegistry . resolveKeybinding ( keybindings [ 0 ] ) ; } catch ( error ) { return undefined ; } } private getItemIconClasses ( command ) { const toggledHandler = this . commandRegistry . getToggledHandler ( command . id ) ; if ( toggledHandler ) { return codiconArray ( 'check' ) ; } return undefined ; } protected readonly contexts = new Map < string , string [ ] > ( ) ; pushCommandContext ( commandId , when ) { const contexts = this . contexts . get ( commandId ) || [ ] ; contexts . push ( when ) ; this . contexts . set ( commandId , contexts ) ; return Disposable . create ( ( ) => { const index = contexts . indexOf ( when ) ; if ( index !== - 1 ) { contexts . splice ( index , 1 ) ; } } ) ; } param commands returns protected getValidCommands ( raw ) { const valid = [ ] ; raw . forEach ( command => { if ( command . label ) { const contexts = this . contexts . get ( command . id ) ; if ( ! contexts || contexts . some ( when => this . contextKeyService . match ( when ) ) ) { valid . push ( command ) ; } } } ) ; return valid ; } returns protected getCommands ( ) { const recentCommands = this . commandRegistry . recent ; const allCommands = this . getValidCommands ( this . commandRegistry . commands ) ; const limit = this . corePreferences [ 'workbench.commandPalette.history' ] ; let rCommands = [ ] ; if ( limit > 0 ) { rCommands . push ( ... recentCommands . filter ( r => ! this . exemptedCommands . some ( c => Command . equals ( r , c ) ) && allCommands . some ( c => Command . equals ( r , c ) ) ) ) ; if ( rCommands . length > limit ) { rCommands = rCommands . slice ( 0 , limit ) ; } } const oCommands = allCommands . filter ( c => ! rCommands . some ( r => Command . equals ( r , c ) ) ) ; const recent = this . normalize ( rCommands ) ; const other = this . sort ( this . normalize ( oCommands ) ) ; return { recent , other } ; } param commands returns private normalize ( commands ) { return commands . filter ( ( a ) => a . label && ( this . commandRegistry . isVisible ( a . id ) && this . commandRegistry . isEnabled ( a . id ) ) ) ; } param commands returns private sort ( commands ) { return commands . sort ( ( a , b ) => Command . compareCommands ( a , b ) ) ; } }