Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formatting on Enter #649

Merged
merged 98 commits into from
Feb 1, 2018
Merged
Show file tree
Hide file tree
Changes from 97 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
7675901
Basic tokenizer
Dec 1, 2017
eb42669
Fixed property names
Dec 1, 2017
2756974
Tests, round I
Dec 1, 2017
c2c1ced
Tests, round II
Dec 2, 2017
a108c96
merge master
Dec 3, 2017
14864a5
tokenizer test
Dec 4, 2017
0ed51d6
Remove temorary change
Dec 4, 2017
51b544c
Fix merge issue
Dec 4, 2017
3cd11e6
Merge conflict
Dec 4, 2017
82e0ad1
Merge conflict
Dec 4, 2017
9295c1a
Completion test
Dec 4, 2017
06eb1a5
Fix last line
Dec 4, 2017
e9db8e0
Fix javascript math
Dec 4, 2017
d12ca03
Merge master
Dec 5, 2017
d8ab041
Make test await for results
Dec 5, 2017
db75cd0
Add license headers
Dec 5, 2017
9ab2c47
Rename definitions to types
Dec 5, 2017
d587485
License headers
Dec 5, 2017
1da5e0a
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 5, 2017
7668cee
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 11, 2017
1ac4932
Fix typo in completion details (typo)
Dec 11, 2017
2aa5a6c
Fix hover test
Dec 12, 2017
5db31bd
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 12, 2017
560d2af
Russian translations
Dec 13, 2017
c71024d
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 13, 2017
31aa087
Update to better translation
Dec 13, 2017
593ae05
Fix typo
Dec 13, 2017
e6d69bb
#70 How to get all parameter info when filling in a function param list
Dec 13, 2017
b5a23d3
Fix #70 How to get all parameter info when filling in a function para…
Dec 14, 2017
cd200f7
Clean up
Dec 14, 2017
7c33228
Clean imports
Dec 14, 2017
c4a6b90
CR feedback
Dec 14, 2017
f85b848
Trim whitespace for test stability
Dec 14, 2017
37c210b
More tests
Dec 15, 2017
61a5650
Better handle no-parameters documentation
Dec 15, 2017
a10305e
Better handle ellipsis and Python3
Dec 15, 2017
bfcae78
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 15, 2017
42a5f79
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Dec 18, 2017
e4ba322
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Jan 8, 2018
7baec1a
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Jan 9, 2018
9cb43e7
#385 Auto-Indentation doesn't work after comment
Jan 9, 2018
5a9c3fd
#141 Auto indentation broken when return keyword involved
Jan 9, 2018
9800c4a
Undo changes
Jan 9, 2018
1e8244d
On type formatting
Jan 11, 2018
a45d2fd
Fix warnings
Jan 11, 2018
3205d33
Merge branch 'master' of https://github.com/Microsoft/vscode-python
Jan 11, 2018
de6a59c
Round I
Jan 11, 2018
c374a43
Round 2
Jan 12, 2018
d1f429b
Round 3
Jan 12, 2018
7eca943
Round 4
Jan 13, 2018
595f63c
Round 5
Jan 16, 2018
86e3e05
no message
Jan 16, 2018
c02bd84
Round 6
Jan 17, 2018
c15fdb2
Round 7
Jan 18, 2018
29833eb
Clean up targets and messages
Jan 18, 2018
5c729db
Settings propagation
Jan 18, 2018
a5aa108
Tests
Jan 18, 2018
7848d1b
Test warning
Jan 18, 2018
2da60af
Fix installer tests
Jan 18, 2018
372c855
Tests
Jan 18, 2018
3800516
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Jan 18, 2018
d3afee8
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Jan 19, 2018
2f361f5
Test fixes
Jan 19, 2018
384ca3a
Fix terminal service and tests async/await
Jan 19, 2018
4dc23fb
Fix mock setup
Jan 19, 2018
82e4d52
Test fix
Jan 19, 2018
8207953
Test async/await fix
Jan 19, 2018
8b701d2
Test fix + activate tslint on awaits
Jan 19, 2018
c5ece0a
Use command manager
Jan 19, 2018
5972c11
Work around updateSettings
Jan 19, 2018
11a4891
Multiroot fixes, partial
Jan 20, 2018
84562bf
More workarounds
Jan 20, 2018
b5c547a
Multiroot tests
Jan 21, 2018
ecb90a6
Fix installer test
Jan 21, 2018
cd79d03
Test fixes
Jan 21, 2018
27a4244
Disable prospector
Jan 22, 2018
34365a5
Enable dispose in all cases
Jan 22, 2018
9071ea8
Fix event firing
Jan 22, 2018
4e2c620
Min pylint options
Jan 22, 2018
c090581
Min checkers & pylintrc discovery
Jan 23, 2018
96fcba1
Fix Windows path in tests for Travis
Jan 23, 2018
25c7d0a
Fix Mac test
Jan 23, 2018
c824c3e
Merge branch 'master' of https://github.com/Microsoft/vscode-python i…
Jan 24, 2018
9de1d81
Merge branch 'lint' of https://github.com/MikhailArkhipov/vscode-pyth…
Jan 24, 2018
36e2fa9
Test fix
Jan 24, 2018
dfdca98
Work around VSC issue with formatting on save #624
Jan 24, 2018
96a81ca
Workaround test
Jan 25, 2018
368c173
Unused
Jan 25, 2018
3999c90
Merge master
Jan 25, 2018
ae81702
Old file
Jan 25, 2018
11780b3
Merge branch 'lint' into tokens
Jan 26, 2018
3561e62
Brace and colon handling
Jan 26, 2018
4d7a1d1
More tests
Jan 26, 2018
ac6894a
Don't format inside strings and comments
Jan 27, 2018
ed89d13
Provider tests
Jan 27, 2018
ffbcfb9
Merge master
Jan 29, 2018
cbc68b1
Remove duplicate code
Jan 29, 2018
7aee5ff
Merge master
Feb 1, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1588,6 +1588,7 @@
"tree-kill": "^1.1.0",
"typescript-char": "^0.0.0",
"uint64be": "^1.0.1",
"unicode": "^10.0.0",
"untildify": "^3.0.2",
"vscode-debugadapter": "^1.0.1",
"vscode-debugprotocol": "^1.0.1",
Expand Down
3 changes: 3 additions & 0 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import { StopWatch } from './telemetry/stopWatch';
import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry';
import { ICodeExecutionManager } from './terminals/types';
import { BlockFormatProviders } from './typeFormatters/blockFormatProvider';
import { OnEnterFormatter } from './typeFormatters/onEnterFormatter';
import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants';
import * as tests from './unittests/main';
import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry';
Expand Down Expand Up @@ -186,6 +187,8 @@ export async function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(new WorkspaceSymbols(serviceContainer));

context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':'));
context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n'));

// In case we have CR LF
const triggerCharacters: string[] = os.EOL.split('');
triggerCharacters.shift();
Expand Down
118 changes: 118 additions & 0 deletions src/client/formatters/lineFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// tslint:disable-next-line:import-name
import Char from 'typescript-char';
import { BraceCounter } from '../language/braceCounter';
import { TextBuilder } from '../language/textBuilder';
import { Tokenizer } from '../language/tokenizer';
import { ITextRangeCollection, IToken, TokenType } from '../language/types';

export class LineFormatter {
private builder: TextBuilder;
private tokens: ITextRangeCollection<IToken>;
private braceCounter: BraceCounter;
private text: string;

// tslint:disable-next-line:cyclomatic-complexity
public formatLine(text: string): string {
this.tokens = new Tokenizer().tokenize(text);
this.text = text;
this.builder = new TextBuilder();
this.braceCounter = new BraceCounter();

if (this.tokens.count === 0) {
return this.text;
}

const ws = this.text.substr(0, this.tokens.getItemAt(0).start);
if (ws.length > 0) {
this.builder.append(ws); // Preserve leading indentation
}

for (let i = 0; i < this.tokens.count; i += 1) {
const t = this.tokens.getItemAt(i);
const prev = i > 0 ? this.tokens.getItemAt(i - 1) : undefined;
const next = i < this.tokens.count - 1 ? this.tokens.getItemAt(i + 1) : undefined;

switch (t.type) {
case TokenType.Operator:
this.handleOperator(i);
break;

case TokenType.Comma:
this.builder.append(',');
if (next && !this.isCloseBraceType(next.type)) {
this.builder.softAppendSpace();
}
break;

case TokenType.Identifier:
if (!prev || (!this.isOpenBraceType(prev.type) && prev.type !== TokenType.Colon)) {
this.builder.softAppendSpace();
}
this.builder.append(this.text.substring(t.start, t.end));
break;

case TokenType.Colon:
// x: 1 if not in slice, x[1:y] if inside the slice
this.builder.append(':');
if (!this.braceCounter.isOpened(TokenType.OpenBracket) && (next && next.type !== TokenType.Colon)) {
// Not inside opened [[ ... ] sequence
this.builder.softAppendSpace();
}
break;

case TokenType.Comment:
// add space before in-line comment
if (prev) {
this.builder.softAppendSpace();
}
this.builder.append(this.text.substring(t.start, t.end));
break;

default:
this.handleOther(t);
break;
}
}
return this.builder.getText();
}

private handleOperator(index: number): void {
const t = this.tokens.getItemAt(index);
if (index >= 2 && t.length === 1 && this.text.charCodeAt(t.start) === Char.Equal) {
if (this.braceCounter.isOpened(TokenType.OpenBrace)) {
// Check if this is = in function arguments. If so, do not
// add spaces around it.
const prev = this.tokens.getItemAt(index - 1);
const prevPrev = this.tokens.getItemAt(index - 2);
if (prev.type === TokenType.Identifier &&
(prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace)) {
this.builder.append('=');
return;
}
}
}
this.builder.softAppendSpace();
this.builder.append(this.text.substring(t.start, t.end));
this.builder.softAppendSpace();
}

private handleOther(t: IToken): void {
if (this.isBraceType(t.type)) {
this.braceCounter.countBrace(t);
}
this.builder.append(this.text.substring(t.start, t.end));
}

private isOpenBraceType(type: TokenType): boolean {
return type === TokenType.OpenBrace || type === TokenType.OpenBracket || type === TokenType.OpenCurly;
}
private isCloseBraceType(type: TokenType): boolean {
return type === TokenType.CloseBrace || type === TokenType.CloseBracket || type === TokenType.CloseCurly;
}
private isBraceType(type: TokenType): boolean {
return this.isOpenBraceType(type) || this.isCloseBraceType(type);
}
}
71 changes: 71 additions & 0 deletions src/client/language/braceCounter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { IToken, TokenType } from './types';

class BracePair {
public readonly openBrace: TokenType;
public readonly closeBrace: TokenType;

constructor(openBrace: TokenType, closeBrace: TokenType) {
this.openBrace = openBrace;
this.closeBrace = closeBrace;
}
}

class Stack {
private store: IToken[] = [];
public push(val: IToken) {
this.store.push(val);
}
public pop(): IToken | undefined {
return this.store.pop();
}
public get length(): number {
return this.store.length;
}
}

export class BraceCounter {
private readonly bracePairs: BracePair[] = [
new BracePair(TokenType.OpenBrace, TokenType.CloseBrace),
new BracePair(TokenType.OpenBracket, TokenType.CloseBracket),
new BracePair(TokenType.OpenCurly, TokenType.CloseCurly)
];
private braceStacks: Stack[] = [new Stack(), new Stack(), new Stack()];

public get count(): number {
let c = 0;
for (const s of this.braceStacks) {
c += s.length;
}
return c;
}

public isOpened(type: TokenType): boolean {
for (let i = 0; i < this.bracePairs.length; i += 1) {
const pair = this.bracePairs[i];
if (pair.openBrace === type || pair.closeBrace === type) {
return this.braceStacks[i].length > 0;
}
}
return false;
}

public countBrace(brace: IToken): boolean {
for (let i = 0; i < this.bracePairs.length; i += 1) {
const pair = this.bracePairs[i];
if (pair.openBrace === brace.type) {
this.braceStacks[i].push(brace);
return true;
}
if (pair.closeBrace === brace.type) {
if (this.braceStacks[i].length > 0) {
this.braceStacks[i].pop();
}
return true;
}
}
return false;
}
}
5 changes: 3 additions & 2 deletions src/client/language/characterStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

// tslint:disable-next-line:import-name
import Char from 'typescript-char';
import { isLineBreak, isWhiteSpace } from './characters';
import { TextIterator } from './textIterator';
import { ICharacterStream, ITextIterator } from './types';

Expand Down Expand Up @@ -70,11 +71,11 @@ export class CharacterStream implements ICharacterStream {
}

public isAtWhiteSpace(): boolean {
return this.currentChar <= Char.Space || this.currentChar === 0x200B; // Unicode whitespace
return isWhiteSpace(this.currentChar);
}

public isAtLineBreak(): boolean {
return this.currentChar === Char.CarriageReturn || this.currentChar === Char.LineFeed;
return isLineBreak(this.currentChar);
}

public skipLineBreak(): void {
Expand Down
100 changes: 100 additions & 0 deletions src/client/language/characters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// tslint:disable-next-line:import-name
import Char from 'typescript-char';
import { getUnicodeCategory, UnicodeCategory } from './unicode';

export function isIdentifierStartChar(ch: number) {
switch (ch) {
// Underscore is explicitly allowed to start an identifier
case Char.Underscore:
return true;
// Characters with the Other_ID_Start property
case 0x1885:
case 0x1886:
case 0x2118:
case 0x212E:
case 0x309B:
case 0x309C:
return true;
default:
break;
}

const cat = getUnicodeCategory(ch);
switch (cat) {
// Supported categories for starting an identifier
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
case UnicodeCategory.LetterNumber:
return true;
default:
break;
}
return false;
}

export function isIdentifierChar(ch: number) {
if (isIdentifierStartChar(ch)) {
return true;
}

switch (ch) {
// Characters with the Other_ID_Continue property
case 0x00B7:
case 0x0387:
case 0x1369:
case 0x136A:
case 0x136B:
case 0x136C:
case 0x136D:
case 0x136E:
case 0x136F:
case 0x1370:
case 0x1371:
case 0x19DA:
return true;
default:
break;
}

switch (getUnicodeCategory(ch)) {
// Supported categories for continuing an identifier
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.DecimalDigitNumber:
case UnicodeCategory.ConnectorPunctuation:
return true;
default:
break;
}
return false;
}

export function isWhiteSpace(ch: number): boolean {
return ch <= Char.Space || ch === 0x200B; // Unicode whitespace
}

export function isLineBreak(ch: number): boolean {
return ch === Char.CarriageReturn || ch === Char.LineFeed;
}

export function isDecimal(ch: number): boolean {
return ch >= Char._0 && ch <= Char._9;
}

export function isHex(ch: number): boolean {
return isDecimal(ch) || (ch >= Char.a && ch <= Char.f) || (ch >= Char.A && ch <= Char.F);
}

export function isOctal(ch: number): boolean {
return ch >= Char._0 && ch <= Char._7;
}

export function isBinary(ch: number): boolean {
return ch === Char._0 || ch === Char._1;
}
41 changes: 41 additions & 0 deletions src/client/language/textBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

import { isWhiteSpace } from './characters';

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

export class TextBuilder {
private segments: string[] = [];

public getText(): string {
if (this.isLastWhiteSpace()) {
this.segments.pop();
}
return this.segments.join('');
}

public softAppendSpace(): void {
if (!this.isLastWhiteSpace() && this.segments.length > 0) {
this.segments.push(' ');
}
}

public append(text: string): void {
this.segments.push(text);
}

private isLastWhiteSpace(): boolean {
return this.segments.length > 0 && this.isWhitespace(this.segments[this.segments.length - 1]);
}

private isWhitespace(s: string): boolean {
for (let i = 0; i < s.length; i += 1) {
if (!isWhiteSpace(s.charCodeAt(i))) {
return false;
}
}
return true;
}
}
Loading