Skip to content

Commit 68441c1

Browse files
authored
🐛 Fixes microsoft#700 replace back slashes in fie paths with forward slashes (microsoft#707)
Fixes microsoft#700
1 parent 1cd98b2 commit 68441c1

File tree

10 files changed

+214
-159
lines changed

10 files changed

+214
-159
lines changed

src/client/common/extensions.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ interface String {
2020
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
2121
*/
2222
toCommandArgument(): string;
23+
/**
24+
* Appropriately formats a a file path so it can be used as an argument for a command in a shell.
25+
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
26+
*/
27+
fileToCommandArgument(): string;
2328
}
2429

2530
/**
@@ -47,5 +52,16 @@ String.prototype.toCommandArgument = function (this: string): string {
4752
if (!this) {
4853
return this;
4954
}
50-
return (this.indexOf(' ') > 0 && !this.startsWith('"') && !this.endsWith('"')) ? `"${this}"` : this.toString();
55+
return (this.indexOf(' ') >= 0 && !this.startsWith('"') && !this.endsWith('"')) ? `"${this}"` : this.toString();
56+
};
57+
58+
/**
59+
* Appropriately formats a a file path so it can be used as an argument for a command in a shell.
60+
* E.g. if an argument contains a space, then it will be enclosed within double quotes.
61+
*/
62+
String.prototype.fileToCommandArgument = function (this: string): string {
63+
if (!this) {
64+
return this;
65+
}
66+
return this.toCommandArgument().replace(/\\/g, '/');
5167
};

src/client/common/terminal/helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class TerminalHelper implements ITerminalHelper {
6868
public buildCommandForTerminal(terminalShellType: TerminalShellType, command: string, args: string[]) {
6969
const isPowershell = terminalShellType === TerminalShellType.powershell || terminalShellType === TerminalShellType.powershellCore;
7070
const commandPrefix = isPowershell ? '& ' : '';
71-
return `${commandPrefix}${command.toCommandArgument()} ${args.join(' ')}`.trim();
71+
return `${commandPrefix}${command.fileToCommandArgument()} ${args.join(' ')}`.trim();
7272
}
7373
public async getEnvironmentActivationCommands(terminalShellType: TerminalShellType, resource?: Uri): Promise<string[] | undefined> {
7474
const settings = this.serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings(resource);

src/client/terminals/codeExecution/djangoShellCodeExecution.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi
4040
const workspaceRoot = workspaceUri ? workspaceUri.uri.fsPath : defaultWorkspace;
4141
const managePyPath = workspaceRoot.length === 0 ? 'manage.py' : path.join(workspaceRoot, 'manage.py');
4242

43-
args.push(managePyPath.toCommandArgument());
43+
args.push(managePyPath.fileToCommandArgument());
4444
args.push('shell');
4545
return { command, args };
4646
}

src/client/terminals/codeExecution/terminalCodeExecution.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService {
3434
const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath;
3535
const launchArgs = pythonSettings.terminal.launchArgs;
3636

37-
await this.getTerminalService(file).sendCommand(command, launchArgs.concat(file.fsPath.toCommandArgument()));
37+
await this.getTerminalService(file).sendCommand(command, launchArgs.concat(file.fsPath.fileToCommandArgument()));
3838
}
3939

4040
public async execute(code: string, resource?: Uri): Promise<void> {
@@ -47,7 +47,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService {
4747
}
4848
public async initializeRepl(resource?: Uri) {
4949
if (this.replActive && await this.replActive!) {
50-
this._terminalService!.show();
50+
await this._terminalService!.show();
5151
return;
5252
}
5353
this.replActive = new Promise<boolean>(async resolve => {

src/test/common/extensions.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { expect } from 'chai';
2+
import '../../client/common/extensions';
3+
4+
// Defines a Mocha test suite to group tests of similar kind together
5+
suite('String Extensions', () => {
6+
test('Should return empty string for empty arg', () => {
7+
const argTotest = '';
8+
expect(argTotest.toCommandArgument()).to.be.equal('');
9+
});
10+
test('Should quote an empty space', () => {
11+
const argTotest = ' ';
12+
expect(argTotest.toCommandArgument()).to.be.equal('" "');
13+
});
14+
test('Should not quote command arguments without spaces', () => {
15+
const argTotest = 'one.two.three';
16+
expect(argTotest.toCommandArgument()).to.be.equal(argTotest);
17+
});
18+
test('Should quote command arguments with spaces', () => {
19+
const argTotest = 'one two three';
20+
expect(argTotest.toCommandArgument()).to.be.equal(`"${argTotest}"`);
21+
});
22+
test('Should return empty string for empty path', () => {
23+
const fileToTest = '';
24+
expect(fileToTest.fileToCommandArgument()).to.be.equal('');
25+
});
26+
test('Should not quote file argument without spaces', () => {
27+
const fileToTest = 'users/test/one';
28+
expect(fileToTest.fileToCommandArgument()).to.be.equal(fileToTest);
29+
});
30+
test('Should quote file argument with spaces', () => {
31+
const fileToTest = 'one two three';
32+
expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest}"`);
33+
});
34+
test('Should replace all back slashes with forward slashes (irrespective of OS)', () => {
35+
const fileToTest = 'c:\\users\\user\\conda\\scripts\\python.exe';
36+
expect(fileToTest.fileToCommandArgument()).to.be.equal(fileToTest.replace(/\\/g, '/'));
37+
});
38+
test('Should replace all back slashes with forward slashes (irrespective of OS) and quoted when file has spaces', () => {
39+
const fileToTest = 'c:\\users\\user namne\\conda path\\scripts\\python.exe';
40+
expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`);
41+
});
42+
});

src/test/common/terminals/activation.bash.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { expect } from 'chai';
55
import * as path from 'path';
66
import * as TypeMoq from 'typemoq';
77
import { EnumEx } from '../../../client/common/enumUtils';
8+
import '../../../client/common/extensions';
89
import { IFileSystem } from '../../../client/common/platform/types';
910
import { Bash } from '../../../client/common/terminal/environmentActivationProviders/bash';
1011
import { TerminalShellType } from '../../../client/common/terminal/types';
@@ -13,7 +14,7 @@ import { IServiceContainer } from '../../../client/ioc/types';
1314

1415
// tslint:disable-next-line:max-func-body-length
1516
suite('Terminal Environment Activation (bash)', () => {
16-
['usr/bin/python', 'usr/bin/env with spaces/env more/python'].forEach(pythonPath => {
17+
['usr/bin/python', 'usr/bin/env with spaces/env more/python', 'c:\\users\\windows paths\\conda\\python.exe'].forEach(pythonPath => {
1718
const hasSpaces = pythonPath.indexOf(' ') > 0;
1819
const suiteTitle = hasSpaces ? 'and there are spaces in the script file (pythonpath),' : 'and there are no spaces in the script file (pythonpath),';
1920
suite(suiteTitle, () => {
@@ -83,8 +84,7 @@ suite('Terminal Environment Activation (bash)', () => {
8384
// Ensure the path is quoted if it contains any spaces.
8485
// Ensure it contains the name of the environment as an argument to the script file.
8586

86-
const quotedScriptFile = pathToScriptFile.indexOf(' ') > 0 ? `"${pathToScriptFile}"` : pathToScriptFile;
87-
expect(command).to.be.deep.equal([`source ${quotedScriptFile}`.trim()], 'Invalid command');
87+
expect(command).to.be.deep.equal([`source ${pathToScriptFile.fileToCommandArgument()}`.trim()], 'Invalid command');
8888
} else {
8989
expect(command).to.be.equal(undefined, 'Command should be undefined');
9090
}

0 commit comments

Comments
 (0)