@@ -3,7 +3,15 @@ import { promises as fs } from "fs"
33import yaml from "js-yaml"
44import * as os from "os"
55import * 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"
715
816const DEFAULT_SOCKET_PATH = path . join ( os . tmpdir ( ) , "vscode-ipc" )
917
@@ -31,53 +39,53 @@ export enum LogLevel {
3139
3240export class OptionalString extends Optional < string > { }
3341
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 {
5549 config ?: string
5650 auth ?: AuthType
5751 password ?: string
5852 "hashed-password" ?: string
5953 cert ?: OptionalString
6054 "cert-host" ?: string
6155 "cert-key" ?: string
62- "disable-telemetry" ?: boolean
6356 "disable-update-check" ?: boolean
6457 enable ?: string [ ]
6558 help ?: boolean
6659 host ?: string
60+ port ?: number
6761 json ?: boolean
6862 log ?: LogLevel
6963 open ?: boolean
70- port ?: number
7164 "bind-addr" ?: string
7265 socket ?: string
7366 version ?: boolean
74- force ?: boolean
75- "show-versions" ?: boolean
7667 "proxy-domain" ?: string [ ]
7768 "reuse-window" ?: boolean
7869 "new-window" ?: boolean
79-
70+ "ignore-last-opened" ?: boolean
8071 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
8189}
8290
8391interface Option < T > {
@@ -121,7 +129,7 @@ type Options<T> = {
121129 [ P in keyof T ] : Option < OptionType < T [ P ] > >
122130}
123131
124- const options : Options < Required < Args > > = {
132+ const options : Options < Required < UserProvidedArgs > > = {
125133 auth : { type : AuthType , description : "The type of authentication to use." } ,
126134 password : {
127135 type : "string" ,
@@ -178,12 +186,10 @@ const options: Options<Required<Args>> = {
178186 "user-data-dir" : { type : "string" , path : true , description : "Path to the user data directory." } ,
179187 "extensions-dir" : { type : "string" , path : true , description : "Path to the extensions directory." } ,
180188 "builtin-extensions-dir" : { type : "string" , path : true } ,
181- "extra-extensions-dir" : { type : "string[]" , path : true } ,
182- "extra-builtin-extensions-dir" : { type : "string[]" , path : true } ,
183189 "list-extensions" : { type : "boolean" , description : "List installed VS Code extensions." } ,
184190 force : { type : "boolean" , description : "Avoid prompts when installing VS Code extensions." } ,
185- "install-source" : { type : "string" } ,
186191 "locate-extension" : { type : "string[]" } ,
192+ category : { type : "string" } ,
187193 "install-extension" : {
188194 type : "string[]" ,
189195 description :
@@ -214,7 +220,6 @@ const options: Options<Required<Args>> = {
214220 description : "Force to open a file or folder in an already opened window." ,
215221 } ,
216222
217- locale : { type : "string" } ,
218223 log : { type : LogLevel } ,
219224 verbose : { type : "boolean" , short : "vvv" , description : "Enable verbose logging." } ,
220225
@@ -271,12 +276,16 @@ export function splitOnFirstEquals(str: string): string[] {
271276 return split
272277}
273278
279+ /**
280+ * Parse arguments into UserProvidedArgs. This should not go beyond checking
281+ * that arguments are valid types and have values when required.
282+ */
274283export const parse = (
275284 argv : string [ ] ,
276285 opts ?: {
277286 configFile ?: string
278287 } ,
279- ) : Args => {
288+ ) : UserProvidedArgs => {
280289 const error = ( msg : string ) : Error => {
281290 if ( opts ?. configFile ) {
282291 msg = `error reading ${ opts . configFile } : ${ msg } `
@@ -285,7 +294,7 @@ export const parse = (
285294 return new Error ( msg )
286295 }
287296
288- const args : Args = { _ : [ ] }
297+ const args : UserProvidedArgs = { }
289298 let ended = false
290299
291300 for ( let i = 0 ; i < argv . length ; ++ i ) {
@@ -299,17 +308,17 @@ export const parse = (
299308
300309 // Options start with a dash and require a value if non-boolean.
301310 if ( ! ended && arg . startsWith ( "-" ) ) {
302- let key : keyof Args | undefined
311+ let key : keyof UserProvidedArgs | undefined
303312 let value : string | undefined
304313 if ( arg . startsWith ( "--" ) ) {
305314 const split = splitOnFirstEquals ( arg . replace ( / ^ - - / , "" ) )
306- key = split [ 0 ] as keyof Args
315+ key = split [ 0 ] as keyof UserProvidedArgs
307316 value = split [ 1 ]
308317 } else {
309318 const short = arg . replace ( / ^ - / , "" )
310319 const pair = Object . entries ( options ) . find ( ( [ , v ] ) => v . short === short )
311320 if ( pair ) {
312- key = pair [ 0 ] as keyof Args
321+ key = pair [ 0 ] as keyof UserProvidedArgs
313322 }
314323 }
315324
@@ -384,6 +393,10 @@ export const parse = (
384393 }
385394
386395 // Everything else goes into _.
396+ if ( typeof args . _ === "undefined" ) {
397+ args . _ = [ ]
398+ }
399+
387400 args . _ . push ( arg )
388401 }
389402
@@ -397,6 +410,11 @@ export const parse = (
397410 return args
398411}
399412
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+ */
400418export interface DefaultedArgs extends ConfigArgs {
401419 auth : AuthType
402420 cert ?: {
@@ -410,14 +428,18 @@ export interface DefaultedArgs extends ConfigArgs {
410428 usingEnvHashedPassword : boolean
411429 "extensions-dir" : string
412430 "user-data-dir" : string
431+ /* Positional arguments. */
432+ _ : [ ]
433+ folder : string
434+ workspace : string
413435}
414436
415437/**
416438 * Take CLI and config arguments (optional) and return a single set of arguments
417439 * with the defaults set. Arguments from the CLI are prioritized over config
418440 * arguments.
419441 */
420- export async function setDefaults ( cliArgs : Args , configArgs ?: ConfigArgs ) : Promise < DefaultedArgs > {
442+ export async function setDefaults ( cliArgs : UserProvidedArgs , configArgs ?: ConfigArgs ) : Promise < DefaultedArgs > {
421443 const args = Object . assign ( { } , configArgs || { } , cliArgs )
422444
423445 if ( ! args [ "user-data-dir" ] ) {
@@ -472,7 +494,7 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
472494 args . auth = AuthType . Password
473495 }
474496
475- const addr = bindAddrFromAllSources ( configArgs || { _ : [ ] } , cliArgs )
497+ const addr = bindAddrFromAllSources ( configArgs || { } , cliArgs )
476498 args . host = addr . host
477499 args . port = addr . port
478500
@@ -513,8 +535,29 @@ export async function setDefaults(cliArgs: Args, configArgs?: ConfigArgs): Promi
513535 const proxyDomains = new Set ( ( args [ "proxy-domain" ] || [ ] ) . map ( ( d ) => d . replace ( / ^ \* \. / , "" ) ) )
514536 args [ "proxy-domain" ] = Array . from ( proxyDomains )
515537
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+
516557 return {
517558 ...args ,
559+ workspace,
560+ folder,
518561 usingEnvPassword,
519562 usingEnvHashedPassword,
520563 } as DefaultedArgs // TODO: Technically no guarantee this is fulfilled.
@@ -539,7 +582,7 @@ cert: false
539582`
540583}
541584
542- interface ConfigArgs extends Args {
585+ interface ConfigArgs extends UserProvidedArgs {
543586 config : string
544587}
545588
@@ -581,7 +624,7 @@ export async function readConfigFile(configPath?: string): Promise<ConfigArgs> {
581624 */
582625export function parseConfigFile ( configFile : string , configPath : string ) : ConfigArgs {
583626 if ( ! configFile ) {
584- return { _ : [ ] , config : configPath }
627+ return { config : configPath }
585628 }
586629
587630 const config = yaml . load ( configFile , {
@@ -628,7 +671,7 @@ interface Addr {
628671 * This function creates the bind address
629672 * using the CLI args.
630673 */
631- export function bindAddrFromArgs ( addr : Addr , args : Args ) : Addr {
674+ export function bindAddrFromArgs ( addr : Addr , args : UserProvidedArgs ) : Addr {
632675 addr = { ...addr }
633676 if ( args [ "bind-addr" ] ) {
634677 addr = parseBindAddr ( args [ "bind-addr" ] )
@@ -646,7 +689,7 @@ export function bindAddrFromArgs(addr: Addr, args: Args): Addr {
646689 return addr
647690}
648691
649- function bindAddrFromAllSources ( ...argsConfig : Args [ ] ) : Addr {
692+ function bindAddrFromAllSources ( ...argsConfig : UserProvidedArgs [ ] ) : Addr {
650693 let addr : Addr = {
651694 host : "localhost" ,
652695 port : 8080 ,
@@ -683,30 +726,34 @@ export async function readSocketPath(path: string): Promise<string | undefined>
683726/**
684727 * Determine if it looks like the user is trying to open a file or folder in an
685728 * 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.
687730 */
688- export const shouldOpenInExistingInstance = async ( args : Args ) : Promise < string | undefined > => {
731+ export const shouldOpenInExistingInstance = async ( args : UserProvidedArgs ) : Promise < string | undefined > => {
689732 // Always use the existing instance if we're running from VS Code's terminal.
690733 if ( process . env . VSCODE_IPC_HOOK_CLI ) {
734+ logger . debug ( "Found VSCODE_IPC_HOOK_CLI" )
691735 return process . env . VSCODE_IPC_HOOK_CLI
692736 }
693737
694738 // If these flags are set then assume the user is trying to open in an
695739 // existing instance since these flags have no effect otherwise.
696740 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
698742 } , 0 )
699743 if ( openInFlagCount > 0 ) {
744+ logger . debug ( "Found --reuse-window or --new-window" )
700745 return readSocketPath ( DEFAULT_SOCKET_PATH )
701746 }
702747
703748 // 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 ) {
708754 const socketPath = await readSocketPath ( DEFAULT_SOCKET_PATH )
709755 if ( socketPath && ( await canConnect ( socketPath ) ) ) {
756+ logger . debug ( "Found existing code-server socket" )
710757 return socketPath
711758 }
712759 }
0 commit comments