@@ -20,10 +20,10 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2020import { ICommandService } from 'vs/platform/commands/common/commands' ;
2121import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
2222import { MenuItemAction , IMenuService } from 'vs/platform/actions/common/actions' ;
23- import { IAction , IActionViewItem , ActionRunner , Action } from 'vs/base/common/actions' ;
23+ import { IAction , IActionViewItem , ActionRunner , Action , RadioGroup } from 'vs/base/common/actions' ;
2424import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem' ;
2525import { SCMMenus } from './menus' ;
26- import { ActionBar , IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar' ;
26+ import { ActionBar , IActionViewItemProvider , Separator } from 'vs/base/browser/ui/actionbar/actionbar' ;
2727import { IThemeService , LIGHT , registerThemingParticipant , IFileIconTheme } from 'vs/platform/theme/common/themeService' ;
2828import { isSCMResource , isSCMResourceGroup , connectPrimaryMenuToInlineActionBar } from './util' ;
2929import { attachBadgeStyler } from 'vs/platform/theme/common/styler' ;
@@ -39,7 +39,7 @@ import { Iterable } from 'vs/base/common/iterator';
3939import { ICompressedTreeNode , ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel' ;
4040import { URI } from 'vs/base/common/uri' ;
4141import { FileKind } from 'vs/platform/files/common/files' ;
42- import { compareFileNames } from 'vs/base/common/comparers' ;
42+ import { compareFileNames , comparePaths } from 'vs/base/common/comparers' ;
4343import { FuzzyScore , createMatches , IMatch } from 'vs/base/common/filters' ;
4444import { IViewDescriptor , IViewDescriptorService } from 'vs/workbench/common/views' ;
4545import { localize } from 'vs/nls' ;
@@ -60,7 +60,7 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito
6060import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu' ;
6161import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors' ;
6262import * as platform from 'vs/base/common/platform' ;
63- import { format } from 'vs/base/common/strings' ;
63+ import { format , compare } from 'vs/base/common/strings' ;
6464import { inputPlaceholderForeground , inputValidationInfoBorder , inputValidationWarningBorder , inputValidationErrorBorder , inputValidationInfoBackground , inputValidationInfoForeground , inputValidationWarningBackground , inputValidationWarningForeground , inputValidationErrorBackground , inputValidationErrorForeground , inputBackground , inputForeground , inputBorder , focusBorder } from 'vs/platform/theme/common/colorRegistry' ;
6565import { SuggestController } from 'vs/editor/contrib/suggest/suggestController' ;
6666import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2' ;
@@ -74,6 +74,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
7474import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget' ;
7575import { IModeService } from 'vs/editor/common/services/modeService' ;
7676import { ILabelService } from 'vs/platform/label/common/label' ;
77+ import { ContextSubMenu } from 'vs/base/browser/contextmenu' ;
7778import { KeyCode } from 'vs/base/common/keyCodes' ;
7879import { DEFAULT_FONT_FAMILY } from 'vs/workbench/browser/style' ;
7980
@@ -373,14 +374,38 @@ export class SCMTreeSorter implements ITreeSorter<TreeElement> {
373374 constructor ( private viewModelProvider : ( ) => ViewModel ) { }
374375
375376 compare ( one : TreeElement , other : TreeElement ) : number {
376- if ( this . viewModel . mode === ViewModelMode . List ) {
377+ if ( isSCMResourceGroup ( one ) && isSCMResourceGroup ( other ) ) {
377378 return 0 ;
378379 }
379380
380- if ( isSCMResourceGroup ( one ) && isSCMResourceGroup ( other ) ) {
381- return 0 ;
381+ // List
382+ if ( this . viewModel . mode === ViewModelMode . List ) {
383+ // FileName
384+ if ( this . viewModel . sortKey === ViewModelSortKey . Name ) {
385+ const oneName = basename ( ( one as ISCMResource ) . sourceUri ) ;
386+ const otherName = basename ( ( other as ISCMResource ) . sourceUri ) ;
387+
388+ return compareFileNames ( oneName , otherName ) ;
389+ }
390+
391+ // Status
392+ if ( this . viewModel . sortKey === ViewModelSortKey . Status ) {
393+ const oneTooltip = ( one as ISCMResource ) . decorations . tooltip ?? '' ;
394+ const otherTooltip = ( other as ISCMResource ) . decorations . tooltip ?? '' ;
395+
396+ if ( oneTooltip !== otherTooltip ) {
397+ return compare ( oneTooltip , otherTooltip ) ;
398+ }
399+ }
400+
401+ // Path (default)
402+ const onePath = ( one as ISCMResource ) . sourceUri . fsPath ;
403+ const otherPath = ( other as ISCMResource ) . sourceUri . fsPath ;
404+
405+ return comparePaths ( onePath , otherPath ) ;
382406 }
383407
408+ // Tree
384409 const oneIsDirectory = ResourceTree . isResourceNode ( one ) ;
385410 const otherIsDirectory = ResourceTree . isResourceNode ( other ) ;
386411
@@ -498,6 +523,12 @@ const enum ViewModelMode {
498523 Tree = 'tree'
499524}
500525
526+ const enum ViewModelSortKey {
527+ Path ,
528+ Name ,
529+ Status
530+ }
531+
501532class ViewModel {
502533
503534 private readonly _onDidChangeMode = new Emitter < ViewModelMode > ( ) ;
@@ -521,6 +552,14 @@ class ViewModel {
521552 this . _onDidChangeMode . fire ( mode ) ;
522553 }
523554
555+ get sortKey ( ) : ViewModelSortKey { return this . _sortKey ; }
556+ set sortKey ( sortKey : ViewModelSortKey ) {
557+ if ( sortKey !== this . _sortKey ) {
558+ this . _sortKey = sortKey ;
559+ this . refresh ( ) ;
560+ }
561+ }
562+
524563 private items : IGroupItem [ ] = [ ] ;
525564 private visibilityDisposables = new DisposableStore ( ) ;
526565 private scrollTop : number | undefined ;
@@ -531,6 +570,7 @@ class ViewModel {
531570 private groups : ISequence < ISCMResourceGroup > ,
532571 private tree : WorkbenchCompressibleObjectTree < TreeElement , FuzzyScore > ,
533572 private _mode : ViewModelMode ,
573+ private _sortKey : ViewModelSortKey ,
534574 @IEditorService protected editorService : IEditorService ,
535575 @IConfigurationService protected configurationService : IConfigurationService ,
536576 ) { }
@@ -661,25 +701,107 @@ class ViewModel {
661701 }
662702}
663703
664- export class ToggleViewModeAction extends Action {
704+ class SCMViewSubMenuAction extends ContextSubMenu {
705+ constructor ( viewModel : ViewModel ) {
706+ super ( localize ( 'sortAction' , "View & Sort" ) ,
707+ [
708+ ...new RadioGroup ( [
709+ new SCMViewModeListAction ( viewModel ) ,
710+ new SCMViewModeTreeAction ( viewModel )
711+ ] ) . actions ,
712+ new Separator ( ) ,
713+ ...new RadioGroup ( [
714+ new SCMSortByNameAction ( viewModel ) ,
715+ new SCMSortByPathAction ( viewModel ) ,
716+ new SCMSortByStatusAction ( viewModel )
717+ ] ) . actions
718+ ]
719+ ) ;
720+ }
721+ }
665722
666- static readonly ID = 'workbench.scm.action.toggleViewMode' ;
667- static readonly LABEL = localize ( 'toggleViewMode' , "Toggle View Mode" ) ;
723+ abstract class SCMViewModeAction extends Action {
724+ constructor ( id : string , label : string , private viewModel : ViewModel , private viewMode : ViewModelMode ) {
725+ super ( id , label ) ;
668726
669- constructor ( private viewModel : ViewModel ) {
670- super ( ToggleViewModeAction . ID , ToggleViewModeAction . LABEL ) ;
727+ this . checked = this . viewModel . mode === this . viewMode ;
728+ }
671729
672- this . _register ( this . viewModel . onDidChangeMode ( this . onDidChangeMode , this ) ) ;
673- this . onDidChangeMode ( this . viewModel . mode ) ;
730+ async run ( ) : Promise < void > {
731+ if ( this . viewMode !== this . viewModel . mode ) {
732+ this . checked = ! this . checked ;
733+ this . viewModel . mode = this . viewMode ;
734+ }
735+ }
736+ }
737+
738+ class SCMViewModeListAction extends SCMViewModeAction {
739+ static readonly ID = 'workbench.scm.action.viewModeList' ;
740+ static readonly LABEL = localize ( 'viewModeList' , "View as List" ) ;
741+
742+ constructor ( viewModel : ViewModel ) {
743+ super ( SCMViewModeListAction . ID , SCMViewModeListAction . LABEL , viewModel , ViewModelMode . List ) ;
744+ }
745+ }
746+
747+ class SCMViewModeTreeAction extends SCMViewModeAction {
748+ static readonly ID = 'workbench.scm.action.viewModeTree' ;
749+ static readonly LABEL = localize ( 'viewModeTree' , "View as Tree" ) ;
750+
751+ constructor ( viewModel : ViewModel ) {
752+ super ( SCMViewModeTreeAction . ID , SCMViewModeTreeAction . LABEL , viewModel , ViewModelMode . Tree ) ;
753+ }
754+ }
755+
756+ abstract class SCMSortAction extends Action {
757+
758+ private readonly _listener : IDisposable ;
759+
760+ constructor ( id : string , label : string , private viewModel : ViewModel , private sortKey : ViewModelSortKey ) {
761+ super ( id , label ) ;
762+
763+ this . checked = this . sortKey === ViewModelSortKey . Path ;
764+ this . enabled = this . viewModel ?. mode === ViewModelMode . List ?? false ;
765+ this . _listener = viewModel ?. onDidChangeMode ( e => this . enabled = e === ViewModelMode . List ) ;
674766 }
675767
676768 async run ( ) : Promise < void > {
677- this . viewModel . mode = this . viewModel . mode === ViewModelMode . List ? ViewModelMode . Tree : ViewModelMode . List ;
769+ if ( this . sortKey !== this . viewModel . sortKey ) {
770+ this . checked = ! this . checked ;
771+ this . viewModel . sortKey = this . sortKey ;
772+ }
773+ }
774+
775+ dispose ( ) : void {
776+ this . _listener . dispose ( ) ;
777+ super . dispose ( ) ;
678778 }
779+ }
780+
781+ class SCMSortByNameAction extends SCMSortAction {
782+ static readonly ID = 'workbench.scm.action.sortByName' ;
783+ static readonly LABEL = localize ( 'sortByName' , "Sort by Name" ) ;
679784
680- private onDidChangeMode ( mode : ViewModelMode ) : void {
681- const iconClass = mode === ViewModelMode . List ? 'codicon-list-tree' : 'codicon-list-flat' ;
682- this . class = `scm-action toggle-view-mode ${ iconClass } ` ;
785+ constructor ( viewModel : ViewModel ) {
786+ super ( SCMSortByNameAction . ID , SCMSortByNameAction . LABEL , viewModel , ViewModelSortKey . Name ) ;
787+ }
788+ }
789+
790+ class SCMSortByPathAction extends SCMSortAction {
791+ static readonly ID = 'workbench.scm.action.sortByPath' ;
792+ static readonly LABEL = localize ( 'sortByPath' , "Sort by Path" ) ;
793+
794+ constructor ( viewModel : ViewModel ) {
795+ super ( SCMSortByPathAction . ID , SCMSortByPathAction . LABEL , viewModel , ViewModelSortKey . Path ) ;
796+ }
797+ }
798+
799+ class SCMSortByStatusAction extends SCMSortAction {
800+ static readonly ID = 'workbench.scm.action.sortByStatus' ;
801+ static readonly LABEL = localize ( 'sortByStatus' , "Sort by Status" ) ;
802+
803+ constructor ( viewModel : ViewModel ) {
804+ super ( SCMSortByStatusAction . ID , SCMSortByStatusAction . LABEL , viewModel , ViewModelSortKey . Status ) ;
683805 }
684806}
685807
@@ -697,7 +819,6 @@ export class RepositoryPane extends ViewPane {
697819 private viewModel ! : ViewModel ;
698820 private listLabels ! : ResourceLabels ;
699821 private menus : SCMMenus ;
700- private toggleViewModelModeAction : ToggleViewModeAction | undefined ;
701822 protected contextKeyService : IContextKeyService ;
702823 private commitTemplate = '' ;
703824
@@ -971,7 +1092,7 @@ export class RepositoryPane extends ViewPane {
9711092 }
9721093 }
9731094
974- this . viewModel = this . instantiationService . createInstance ( ViewModel , this . repository . provider . groups , this . tree , viewMode ) ;
1095+ this . viewModel = this . instantiationService . createInstance ( ViewModel , this . repository . provider . groups , this . tree , viewMode , ViewModelSortKey . Path ) ;
9751096 this . _register ( this . viewModel ) ;
9761097
9771098 addClass ( this . listContainer , 'file-icon-themable-tree' ) ;
@@ -981,9 +1102,6 @@ export class RepositoryPane extends ViewPane {
9811102 this . _register ( this . themeService . onDidFileIconThemeChange ( this . updateIndentStyles , this ) ) ;
9821103 this . _register ( this . viewModel . onDidChangeMode ( this . onDidChangeMode , this ) ) ;
9831104
984- this . toggleViewModelModeAction = new ToggleViewModeAction ( this . viewModel ) ;
985- this . _register ( this . toggleViewModelModeAction ) ;
986-
9871105 this . _register ( this . onDidChangeBodyVisibility ( this . _onDidChangeVisibility , this ) ) ;
9881106
9891107 this . updateActions ( ) ;
@@ -1066,19 +1184,22 @@ export class RepositoryPane extends ViewPane {
10661184 }
10671185
10681186 getActions ( ) : IAction [ ] {
1069- if ( this . toggleViewModelModeAction ) {
1070-
1071- return [
1072- this . toggleViewModelModeAction ,
1073- ...this . menus . getTitleActions ( )
1074- ] ;
1075- } else {
1076- return this . menus . getTitleActions ( ) ;
1077- }
1187+ return this . menus . getTitleActions ( ) ;
10781188 }
10791189
10801190 getSecondaryActions ( ) : IAction [ ] {
1081- return this . menus . getTitleSecondaryActions ( ) ;
1191+ if ( ! this . viewModel ) {
1192+ return [ ] ;
1193+ }
1194+
1195+ const result : IAction [ ] = [ new SCMViewSubMenuAction ( this . viewModel ) ] ;
1196+ const secondaryActions = this . menus . getTitleSecondaryActions ( ) ;
1197+
1198+ if ( secondaryActions . length > 0 ) {
1199+ result . push ( new Separator ( ) , ...secondaryActions ) ;
1200+ }
1201+
1202+ return result ;
10821203 }
10831204
10841205 getActionViewItem ( action : IAction ) : IActionViewItem | undefined {
0 commit comments