@@ -17,11 +17,68 @@ import { normalizeDriveLetter } from 'vs/base/common/labels';
1717import { toSlashes } from 'vs/base/common/extpath' ;
1818import { FormattingOptions } from 'vs/base/common/jsonFormatter' ;
1919import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts' ;
20+ import { ILogService } from 'vs/platform/log/common/log' ;
21+ import { Event as CommonEvent } from 'vs/base/common/event' ;
2022
2123export const WORKSPACE_EXTENSION = 'code-workspace' ;
2224export const WORKSPACE_FILTER = [ { name : localize ( 'codeWorkspace' , "Code Workspace" ) , extensions : [ WORKSPACE_EXTENSION ] } ] ;
2325export const UNTITLED_WORKSPACE_NAME = 'workspace.json' ;
2426
27+ export const IWorkspacesService = createDecorator < IWorkspacesService > ( 'workspacesService' ) ;
28+
29+ export interface IWorkspacesService {
30+
31+ _serviceBrand : undefined ;
32+
33+ readonly onRecentlyOpenedChange : CommonEvent < void > ;
34+
35+ // Management
36+ enterWorkspace ( path : URI ) : Promise < IEnterWorkspaceResult | undefined > ;
37+ createUntitledWorkspace ( folders ?: IWorkspaceFolderCreationData [ ] , remoteAuthority ?: string ) : Promise < IWorkspaceIdentifier > ;
38+ deleteUntitledWorkspace ( workspace : IWorkspaceIdentifier ) : Promise < void > ;
39+ getWorkspaceIdentifier ( workspacePath : URI ) : Promise < IWorkspaceIdentifier > ;
40+
41+ // History
42+ addRecentlyOpened ( recents : IRecent [ ] ) : Promise < void > ;
43+ removeFromRecentlyOpened ( workspaces : URI [ ] ) : Promise < void > ;
44+ clearRecentlyOpened ( ) : Promise < void > ;
45+ getRecentlyOpened ( ) : Promise < IRecentlyOpened > ;
46+ }
47+
48+ export interface IRecentlyOpened {
49+ workspaces : Array < IRecentWorkspace | IRecentFolder > ;
50+ files : IRecentFile [ ] ;
51+ }
52+
53+ export type IRecent = IRecentWorkspace | IRecentFolder | IRecentFile ;
54+
55+ export interface IRecentWorkspace {
56+ workspace : IWorkspaceIdentifier ;
57+ label ?: string ;
58+ }
59+
60+ export interface IRecentFolder {
61+ folderUri : ISingleFolderWorkspaceIdentifier ;
62+ label ?: string ;
63+ }
64+
65+ export interface IRecentFile {
66+ fileUri : URI ;
67+ label ?: string ;
68+ }
69+
70+ export function isRecentWorkspace ( curr : IRecent ) : curr is IRecentWorkspace {
71+ return curr . hasOwnProperty ( 'workspace' ) ;
72+ }
73+
74+ export function isRecentFolder ( curr : IRecent ) : curr is IRecentFolder {
75+ return curr . hasOwnProperty ( 'folderUri' ) ;
76+ }
77+
78+ export function isRecentFile ( curr : IRecent ) : curr is IRecentFile {
79+ return curr . hasOwnProperty ( 'fileUri' ) ;
80+ }
81+
2582/**
2683 * A single folder workspace identifier is just the path to the folder.
2784 */
@@ -96,21 +153,6 @@ export interface IEnterWorkspaceResult {
96153 backupPath ?: string ;
97154}
98155
99- export const IWorkspacesService = createDecorator < IWorkspacesService > ( 'workspacesService' ) ;
100-
101- export interface IWorkspacesService {
102-
103- _serviceBrand : undefined ;
104-
105- enterWorkspace ( path : URI ) : Promise < IEnterWorkspaceResult | undefined > ;
106-
107- createUntitledWorkspace ( folders ?: IWorkspaceFolderCreationData [ ] , remoteAuthority ?: string ) : Promise < IWorkspaceIdentifier > ;
108-
109- deleteUntitledWorkspace ( workspace : IWorkspaceIdentifier ) : Promise < void > ;
110-
111- getWorkspaceIdentifier ( workspacePath : URI ) : Promise < IWorkspaceIdentifier > ;
112- }
113-
114156export function isSingleFolderWorkspaceIdentifier ( obj : any ) : obj is ISingleFolderWorkspaceIdentifier {
115157 return obj instanceof URI ;
116158}
@@ -267,3 +309,129 @@ export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolea
267309 }
268310 return true ;
269311}
312+
313+ //#region Workspace Storage
314+
315+ interface ISerializedRecentlyOpened {
316+ workspaces3 : Array < ISerializedWorkspace | string > ; // workspace or URI.toString() // added in 1.32
317+ workspaceLabels ?: Array < string | null > ; // added in 1.33
318+ files2 : string [ ] ; // files as URI.toString() // added in 1.32
319+ fileLabels ?: Array < string | null > ; // added in 1.33
320+ }
321+
322+ interface ILegacySerializedRecentlyOpened {
323+ workspaces2 : Array < ILegacySerializedWorkspace | string > ; // legacy, configPath as file path
324+ workspaces : Array < ILegacySerializedWorkspace | string | UriComponents > ; // legacy (UriComponents was also supported for a few insider builds)
325+ files : string [ ] ; // files as paths
326+ }
327+
328+ interface ISerializedWorkspace { id : string ; configURIPath : string ; }
329+ interface ILegacySerializedWorkspace { id : string ; configPath : string ; }
330+
331+ function isLegacySerializedWorkspace ( curr : any ) : curr is ILegacySerializedWorkspace {
332+ return typeof curr === 'object' && typeof curr [ 'id' ] === 'string' && typeof curr [ 'configPath' ] === 'string' ;
333+ }
334+
335+ function isUriComponents ( curr : any ) : curr is UriComponents {
336+ return curr && typeof curr [ 'path' ] === 'string' && typeof curr [ 'scheme' ] === 'string' ;
337+ }
338+
339+ export type RecentlyOpenedStorageData = object ;
340+
341+ export function restoreRecentlyOpened ( data : RecentlyOpenedStorageData | undefined , logService : ILogService ) : IRecentlyOpened {
342+ const result : IRecentlyOpened = { workspaces : [ ] , files : [ ] } ;
343+ if ( data ) {
344+ const restoreGracefully = function < T > ( entries : T [ ] , func : ( entry : T , index : number ) => void ) {
345+ for ( let i = 0 ; i < entries . length ; i ++ ) {
346+ try {
347+ func ( entries [ i ] , i ) ;
348+ } catch ( e ) {
349+ logService . warn ( `Error restoring recent entry ${ JSON . stringify ( entries [ i ] ) } : ${ e . toString ( ) } . Skip entry.` ) ;
350+ }
351+ }
352+ } ;
353+
354+ const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened ;
355+ if ( Array . isArray ( storedRecents . workspaces3 ) ) {
356+ restoreGracefully ( storedRecents . workspaces3 , ( workspace , i ) => {
357+ const label : string | undefined = ( Array . isArray ( storedRecents . workspaceLabels ) && storedRecents . workspaceLabels [ i ] ) || undefined ;
358+ if ( typeof workspace === 'object' && typeof workspace . id === 'string' && typeof workspace . configURIPath === 'string' ) {
359+ result . workspaces . push ( { label, workspace : { id : workspace . id , configPath : URI . parse ( workspace . configURIPath ) } } ) ;
360+ } else if ( typeof workspace === 'string' ) {
361+ result . workspaces . push ( { label, folderUri : URI . parse ( workspace ) } ) ;
362+ }
363+ } ) ;
364+ } else if ( Array . isArray ( storedRecents . workspaces2 ) ) {
365+ restoreGracefully ( storedRecents . workspaces2 , workspace => {
366+ if ( typeof workspace === 'object' && typeof workspace . id === 'string' && typeof workspace . configPath === 'string' ) {
367+ result . workspaces . push ( { workspace : { id : workspace . id , configPath : URI . file ( workspace . configPath ) } } ) ;
368+ } else if ( typeof workspace === 'string' ) {
369+ result . workspaces . push ( { folderUri : URI . parse ( workspace ) } ) ;
370+ }
371+ } ) ;
372+ } else if ( Array . isArray ( storedRecents . workspaces ) ) {
373+ // TODO@martin legacy support can be removed at some point (6 month?)
374+ // format of 1.25 and before
375+ restoreGracefully ( storedRecents . workspaces , workspace => {
376+ if ( typeof workspace === 'string' ) {
377+ result . workspaces . push ( { folderUri : URI . file ( workspace ) } ) ;
378+ } else if ( isLegacySerializedWorkspace ( workspace ) ) {
379+ result . workspaces . push ( { workspace : { id : workspace . id , configPath : URI . file ( workspace . configPath ) } } ) ;
380+ } else if ( isUriComponents ( workspace ) ) {
381+ // added by 1.26-insiders
382+ result . workspaces . push ( { folderUri : URI . revive ( < UriComponents > workspace ) } ) ;
383+ }
384+ } ) ;
385+ }
386+ if ( Array . isArray ( storedRecents . files2 ) ) {
387+ restoreGracefully ( storedRecents . files2 , ( file , i ) => {
388+ const label : string | undefined = ( Array . isArray ( storedRecents . fileLabels ) && storedRecents . fileLabels [ i ] ) || undefined ;
389+ if ( typeof file === 'string' ) {
390+ result . files . push ( { label, fileUri : URI . parse ( file ) } ) ;
391+ }
392+ } ) ;
393+ } else if ( Array . isArray ( storedRecents . files ) ) {
394+ restoreGracefully ( storedRecents . files , file => {
395+ if ( typeof file === 'string' ) {
396+ result . files . push ( { fileUri : URI . file ( file ) } ) ;
397+ }
398+ } ) ;
399+ }
400+ }
401+
402+ return result ;
403+ }
404+
405+ export function toStoreData ( recents : IRecentlyOpened ) : RecentlyOpenedStorageData {
406+ const serialized : ISerializedRecentlyOpened = { workspaces3 : [ ] , files2 : [ ] } ;
407+
408+ let hasLabel = false ;
409+ const workspaceLabels : ( string | null ) [ ] = [ ] ;
410+ for ( const recent of recents . workspaces ) {
411+ if ( isRecentFolder ( recent ) ) {
412+ serialized . workspaces3 . push ( recent . folderUri . toString ( ) ) ;
413+ } else {
414+ serialized . workspaces3 . push ( { id : recent . workspace . id , configURIPath : recent . workspace . configPath . toString ( ) } ) ;
415+ }
416+ workspaceLabels . push ( recent . label || null ) ;
417+ hasLabel = hasLabel || ! ! recent . label ;
418+ }
419+ if ( hasLabel ) {
420+ serialized . workspaceLabels = workspaceLabels ;
421+ }
422+
423+ hasLabel = false ;
424+ const fileLabels : ( string | null ) [ ] = [ ] ;
425+ for ( const recent of recents . files ) {
426+ serialized . files2 . push ( recent . fileUri . toString ( ) ) ;
427+ fileLabels . push ( recent . label || null ) ;
428+ hasLabel = hasLabel || ! ! recent . label ;
429+ }
430+ if ( hasLabel ) {
431+ serialized . fileLabels = fileLabels ;
432+ }
433+
434+ return serialized ;
435+ }
436+
437+ //#endregion
0 commit comments