@@ -3,7 +3,15 @@ import { promises as fs } from "fs"
3
3
import yaml from "js-yaml"
4
4
import * as os from "os"
5
5
import * as path from "path"
6
- import { canConnect , generateCertificate , generatePassword , humanPath , paths , isNodeJSErrnoException } from "./util"
6
+ import {
7
+ canConnect ,
8
+ generateCertificate ,
9
+ generatePassword ,
10
+ humanPath ,
11
+ paths ,
12
+ isNodeJSErrnoException ,
13
+ isFile ,
14
+ } from "./util"
7
15
8
16
const DEFAULT_SOCKET_PATH = path . join ( os . tmpdir ( ) , "vscode-ipc" )
9
17
@@ -31,53 +39,53 @@ export enum LogLevel {
31
39
32
40
export class OptionalString extends Optional < string > { }
33
41
34
- export interface Args
35
- extends Pick <
36
- CodeServerLib . NativeParsedArgs ,
37
- | "_"
38
- | "user-data-dir"
39
- | "enable-proposed-api"
40
- | "extensions-dir"
41
- | "builtin-extensions-dir"
42
- | "extra-extensions-dir"
43
- | "extra-builtin-extensions-dir"
44
- | "ignore-last-opened"
45
- | "locale"
46
- | "log"
47
- | "verbose"
48
- | "install-source"
49
- | "list-extensions"
50
- | "install-extension"
51
- | "uninstall-extension"
52
- | "locate-extension"
53
- // | "telemetry"
54
- > {
42
+ /**
43
+ * Arguments that the user explicitly provided on the command line. All
44
+ * arguments must be optional.
45
+ *
46
+ * For arguments with defaults see DefaultedArgs.
47
+ */
48
+ export interface UserProvidedArgs {
55
49
config ?: string
56
50
auth ?: AuthType
57
51
password ?: string
58
52
"hashed-password" ?: string
59
53
cert ?: OptionalString
60
54
"cert-host" ?: string
61
55
"cert-key" ?: string
62
- "disable-telemetry" ?: boolean
63
56
"disable-update-check" ?: boolean
64
57
enable ?: string [ ]
65
58
help ?: boolean
66
59
host ?: string
60
+ port ?: number
67
61
json ?: boolean
68
62
log ?: LogLevel
69
63
open ?: boolean
70
- port ?: number
71
64
"bind-addr" ?: string
72
65
socket ?: string
73
66
version ?: boolean
74
- force ?: boolean
75
- "show-versions" ?: boolean
76
67
"proxy-domain" ?: string [ ]
77
68
"reuse-window" ?: boolean
78
69
"new-window" ?: boolean
79
-
70
+ "ignore-last-opened" ?: boolean
80
71
link ?: OptionalString
72
+ verbose ?: boolean
73
+ /* Positional arguments. */
74
+ _ ?: string [ ]
75
+
76
+ // VS Code flags.
77
+ "disable-telemetry" ?: boolean
78
+ force ?: boolean
79
+ "user-data-dir" ?: string
80
+ "enable-proposed-api" ?: string [ ]
81
+ "extensions-dir" ?: string
82
+ "builtin-extensions-dir" ?: string
83
+ "install-extension" ?: string [ ]
84
+ "uninstall-extension" ?: string [ ]
85
+ "list-extensions" ?: boolean
86
+ "locate-extension" ?: string [ ]
87
+ "show-versions" ?: boolean
88
+ category ?: string
81
89
}
82
90
83
91
interface Option < T > {
@@ -121,7 +129,7 @@ type Options<T> = {
121
129
[ P in keyof T ] : Option < OptionType < T [ P ] > >
122
130
}
123
131
124
- const options : Options < Required < Args > > = {
132
+ const options : Options < Required < UserProvidedArgs > > = {
125
133
auth : { type : AuthType , description : "The type of authentication to use." } ,
126
134
password : {
127
135
type : "string" ,
@@ -178,12 +186,10 @@ const options: Options<Required<Args>> = {
178
186
"user-data-dir" : { type : "string" , path : true , description : "Path to the user data directory." } ,
179
187
"extensions-dir" : { type : "string" , path : true , description : "Path to the extensions directory." } ,
180
188
"builtin-extensions-dir" : { type : "string" , path : true } ,
181
- "extra-extensions-dir" : { type : "string[]" , path : true } ,
182
- "extra-builtin-extensions-dir" : { type : "string[]" , path : true } ,
183
189
"list-extensions" : { type : "boolean" , description : "List installed VS Code extensions." } ,
184
190
force : { type : "boolean" , description : "Avoid prompts when installing VS Code extensions." } ,
185
- "install-source" : { type : "string" } ,
186
191
"locate-extension" : { type : "string[]" } ,
192
+ category : { type : "string" } ,
187
193
"install-extension" : {
188
194
type : "string[]" ,
189
195
description :
@@ -214,7 +220,6 @@ const options: Options<Required<Args>> = {
214
220
description : "Force to open a file or folder in an already opened window." ,
215
221
} ,
216
222
217
- locale : { type : "string" } ,
218
223
log : { type : LogLevel } ,
219
224
verbose : { type : "boolean" , short : "vvv" , description : "Enable verbose logging." } ,
220
225
@@ -271,12 +276,16 @@ export function splitOnFirstEquals(str: string): string[] {
271
276
return split
272
277
}
273
278
279
+ /**
280
+ * Parse arguments into UserProvidedArgs. This should not go beyond checking
281
+ * that arguments are valid types and have values when required.
282
+ */
274
283
export const parse = (
275
284
argv : string [ ] ,
276
285
opts ?: {
277
286
configFile ?: string
278
287
} ,
279
- ) : Args => {
288
+ ) : UserProvidedArgs => {
280
289
const error = ( msg : string ) : Error => {
281
290
if ( opts ?. configFile ) {
282
291
msg = `error reading ${ opts . configFile } : ${ msg } `
@@ -285,7 +294,7 @@ export const parse = (
285
294
return new Error ( msg )
286
295
}
287
296
288
- const args : Args = { _ : [ ] }
297
+ const args : UserProvidedArgs = { }
289
298
let ended = false
290
299
291
300
for ( let i = 0 ; i < argv . length ; ++ i ) {
@@ -299,17 +308,17 @@ export const parse = (
299
308
300
309
// Options start with a dash and require a value if non-boolean.
301
310
if ( ! ended && arg . startsWith ( "-" ) ) {
302
- let key : keyof Args | undefined
311
+ let key : keyof UserProvidedArgs | undefined
303
312
let value : string | undefined
304
313
if ( arg . startsWith ( "--" ) ) {
305
314
const split = splitOnFirstEquals ( arg . replace ( / ^ - - / , "" ) )
306
- key = split [ 0 ] as keyof Args
315
+ key = split [ 0 ] as keyof UserProvidedArgs
307
316
value = split [ 1 ]
308
317
} else {
309
318
const short = arg . replace ( / ^ - / , "" )
310
319
const pair = Object . entries ( options ) . find ( ( [ , v ] ) => v . short === short )
311
320
if ( pair ) {
312
- key = pair [ 0 ] as keyof Args
321
+ key = pair [ 0 ] as keyof UserProvidedArgs
313
322
}
314
323
}
315
324
@@ -384,6 +393,10 @@ export const parse = (
384
393
}
385
394
386
395
// Everything else goes into _.
396
+ if ( typeof args . _ === "undefined" ) {
397
+ args . _ = [ ]
398
+ }
399
+
387
400
args . _ . push ( arg )
388
401
}
389
402
@@ -397,6 +410,11 @@ export const parse = (
397
410
return args
398
411
}
399
412
413
+ /**
414
+ * User-provided arguments with defaults. The distinction between user-provided
415
+ * args and defaulted args exists so we can tell the difference between end
416
+ * values and what the user actually provided on the command line.
417
+ */
400
418
export interface DefaultedArgs extends ConfigArgs {
401
419
auth : AuthType
402
420
cert ?: {
@@ -410,14 +428,18 @@ export interface DefaultedArgs extends ConfigArgs {
410
428
usingEnvHashedPassword : boolean
411
429
"extensions-dir" : string
412
430
"user-data-dir" : string
431
+ /* Positional arguments. */
432
+ _ : [ ]
433
+ folder : string
434
+ workspace : string
413
435
}
414
436
415
437
/**
416
438
* Take CLI and config arguments (optional) and return a single set of arguments
417
439
* with the defaults set. Arguments from the CLI are prioritized over config
418
440
* arguments.
419
441
*/
420
- export async function setDefaults ( cliArgs : Args , configArgs ?: ConfigArgs ) : Promise < DefaultedArgs > {
442
+ export async function setDefaults ( cliArgs : UserProvidedArgs , configArgs ?: ConfigArgs ) : Promise < DefaultedArgs > {
421
443
const args = Object . assign ( { } , configArgs || { } , cliArgs )
422
444
423
445
if ( ! args [ "user-data-dir" ] ) {
@@ -472,7 +494,7 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
472
494
args . auth = AuthType . Password
473
495
}
474
496
475
- const addr = bindAddrFromAllSources ( configArgs || { _ : [ ] } , cliArgs )
497
+ const addr = bindAddrFromAllSources ( configArgs || { } , cliArgs )
476
498
args . host = addr . host
477
499
args . port = addr . port
478
500
@@ -513,8 +535,29 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
513
535
const proxyDomains = new Set ( ( args [ "proxy-domain" ] || [ ] ) . map ( ( d ) => d . replace ( / ^ \* \. / , "" ) ) )
514
536
args [ "proxy-domain" ] = Array . from ( proxyDomains )
515
537
538
+ if ( typeof args . _ === "undefined" ) {
539
+ args . _ = [ ]
540
+ }
541
+
542
+ let workspace = ""
543
+ let folder = ""
544
+ if ( args . _ . length ) {
545
+ const lastEntry = path . resolve ( process . cwd ( ) , args . _ [ args . _ . length - 1 ] )
546
+ const entryIsFile = await isFile ( lastEntry )
547
+
548
+ if ( entryIsFile && path . extname ( lastEntry ) === ".code-workspace" ) {
549
+ workspace = lastEntry
550
+ args . _ . pop ( )
551
+ } else if ( ! entryIsFile ) {
552
+ folder = lastEntry
553
+ args . _ . pop ( )
554
+ }
555
+ }
556
+
516
557
return {
517
558
...args ,
559
+ workspace,
560
+ folder,
518
561
usingEnvPassword,
519
562
usingEnvHashedPassword,
520
563
} as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
@@ -539,7 +582,7 @@ cert: false
539
582
`
540
583
}
541
584
542
- interface ConfigArgs extends Args {
585
+ interface ConfigArgs extends UserProvidedArgs {
543
586
config : string
544
587
}
545
588
@@ -581,7 +624,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
581
624
*/
582
625
export function parseConfigFile ( configFile : string , configPath : string ) : ConfigArgs {
583
626
if ( ! configFile ) {
584
- return { _ : [ ] , config : configPath }
627
+ return { config : configPath }
585
628
}
586
629
587
630
const config = yaml . load ( configFile , {
@@ -628,7 +671,7 @@ interface Addr {
628
671
* This function creates the bind address
629
672
* using the CLI args.
630
673
*/
631
- export function bindAddrFromArgs ( addr : Addr , args : Args ) : Addr {
674
+ export function bindAddrFromArgs ( addr : Addr , args : UserProvidedArgs ) : Addr {
632
675
addr = { ...addr }
633
676
if ( args [ "bind-addr" ] ) {
634
677
addr = parseBindAddr ( args [ "bind-addr" ] )
@@ -646,7 +689,7 @@ export function bindAddrFromArgs(addr: Addr, args: Args): Addr {
646
689
return addr
647
690
}
648
691
649
- function bindAddrFromAllSources ( ...argsConfig : Args [ ] ) : Addr {
692
+ function bindAddrFromAllSources ( ...argsConfig : UserProvidedArgs [ ] ) : Addr {
650
693
let addr : Addr = {
651
694
host : "localhost" ,
652
695
port : 8080 ,
@@ -683,30 +726,34 @@ export async function readSocketPath(path: string): Promise<string | undefined>
683
726
/**
684
727
* Determine if it looks like the user is trying to open a file or folder in an
685
728
* existing instance. The arguments here should be the arguments the user
686
- * explicitly passed on the command line, not defaults or the configuration.
729
+ * explicitly passed on the command line, *NOT DEFAULTS* or the configuration.
687
730
*/
688
- export const shouldOpenInExistingInstance = async ( args : Args ) : Promise < string | undefined > => {
731
+ export const shouldOpenInExistingInstance = async ( args : UserProvidedArgs ) : Promise < string | undefined > => {
689
732
// Always use the existing instance if we're running from VS Code's terminal.
690
733
if ( process . env . VSCODE_IPC_HOOK_CLI ) {
734
+ logger . debug ( "Found VSCODE_IPC_HOOK_CLI" )
691
735
return process . env . VSCODE_IPC_HOOK_CLI
692
736
}
693
737
694
738
// If these flags are set then assume the user is trying to open in an
695
739
// existing instance since these flags have no effect otherwise.
696
740
const openInFlagCount = [ "reuse-window" , "new-window" ] . reduce ( ( prev , cur ) => {
697
- return args [ cur as keyof Args ] ? prev + 1 : prev
741
+ return args [ cur as keyof UserProvidedArgs ] ? prev + 1 : prev
698
742
} , 0 )
699
743
if ( openInFlagCount > 0 ) {
744
+ logger . debug ( "Found --reuse-window or --new-window" )
700
745
return readSocketPath ( DEFAULT_SOCKET_PATH )
701
746
}
702
747
703
748
// It's possible the user is trying to spawn another instance of code-server.
704
- // Check if any unrelated flags are set (check against one because `_` always
705
- // exists), that a file or directory was passed, and that the socket is
706
- // active.
707
- if ( Object . keys ( args ) . length === 1 && args . _ . length > 0 ) {
749
+ // 1. Check if any unrelated flags are set (this should only run when
750
+ // code-server is invoked exactly like this: `code-server my-file`).
751
+ // 2. That a file or directory was passed.
752
+ // 3. That the socket is active.
753
+ if ( Object . keys ( args ) . length === 1 && typeof args . _ !== "undefined" && args . _ . length > 0 ) {
708
754
const socketPath = await readSocketPath ( DEFAULT_SOCKET_PATH )
709
755
if ( socketPath && ( await canConnect ( socketPath ) ) ) {
756
+ logger . debug ( "Found existing code-server socket" )
710
757
return socketPath
711
758
}
712
759
}
0 commit comments