Skip to content

Commit 0329b91

Browse files
committed
Change typing hint provision
1 parent a7c7e48 commit 0329b91

File tree

5 files changed

+139
-100
lines changed

5 files changed

+139
-100
lines changed

src/completionProvider.ts

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,19 @@ import { TypeHintSettings } from "./settings";
1717

1818
export abstract class CompletionProvider {
1919

20-
protected pushTypesToItems(typeNames: PythonType[], completionItems: CompletionItem[]) {
21-
for (const typeName of typeNames) {
22-
const item = new CompletionItem(this.labelFor(typeName), CompletionItemKind.TypeParameter);
23-
24-
// Add 999 to ensure they're sorted to the bottom of the CompletionList.
25-
item.sortText = `999${typeName}`;
20+
protected bottomOfListSortPrefix: number = 999;
21+
22+
protected pushHintsToItems(typeHints: string[], completionItems: CompletionItem[]) {
23+
const sortTextPrefix = this.bottomOfListSortPrefix.toString();
24+
for (const hint of typeHints) {
25+
const item = new CompletionItem(this.labelFor(hint), CompletionItemKind.TypeParameter);
26+
item.sortText = sortTextPrefix + hint;
2627
completionItems.push(item);
2728
}
2829
}
2930

30-
protected labelFor(typeName: string): string {
31-
return " " + typeName;
31+
protected labelFor(typeHint: string): string {
32+
return " " + typeHint;
3233
}
3334

3435
abstract async provideCompletionItems(
@@ -69,9 +70,20 @@ export class ParamHintCompletionProvider extends CompletionProvider implements C
6970
if (param) {
7071
try {
7172
this.pushEstimationsToItems(await provider.getTypeHints(param), items);
72-
} catch (error) {
73-
}
74-
this.pushTypesToItems(provider.getRemainingTypes(), items);
73+
} catch {
74+
}
75+
76+
const sortTextPrefix = (this.bottomOfListSortPrefix - 1).toString();
77+
for (const hint of provider.remainingTypeHints()) {
78+
let item = new CompletionItem(this.labelFor(hint), CompletionItemKind.TypeParameter);
79+
item.sortText = sortTextPrefix + hint;
80+
items.push(item);
81+
}
82+
83+
if (provider.typingImported) {
84+
this.pushHintsToItems(provider.remainingTypingHints(), items);
85+
}
86+
7587
return Promise.resolve(new CompletionList(items, false));
7688
}
7789
}
@@ -85,16 +97,8 @@ export class ParamHintCompletionProvider extends CompletionProvider implements C
8597
* @param precedingText The text before the active position.
8698
*/
8799
private getParam(precedingText: string): string | null {
88-
let param = "";
89-
90-
let i = precedingText.length - 1;
91-
let last = precedingText[i];
92-
while (last !== "," && last !== "(" && last !== "*" && i >= 0) {
93-
param = precedingText[i] + param;
94-
i--;
95-
last = precedingText[i];
96-
}
97-
param = param.trim();
100+
const split = precedingText.split(/[,(*]/);
101+
let param = split.length > 1 ? split[split.length - 1].trim() : precedingText;
98102
return !param || /[!:?/\\{}.+/=)'";@&£%¤|<>$^~¨ -]/.test(param) ? null : param;
99103
}
100104

@@ -117,7 +121,7 @@ export class ParamHintCompletionProvider extends CompletionProvider implements C
117121
private shouldProvideItems(precedingText: string, activePos: Position, doc: TextDocument): boolean {
118122

119123
if (activePos.character > 0 && !/#/.test(precedingText)) {
120-
let provide = new RegExp("^[ \t]*def [^(]+\\(", "m").test(precedingText);
124+
let provide = /^[ \t]*def /.test(precedingText);
121125

122126
if (!provide) {
123127
const nLinesToCheck = activePos.line > 4 ? 4 : activePos.line;
@@ -150,7 +154,7 @@ export class ReturnHintCompletionProvider extends CompletionProvider implements
150154
const line = doc.lineAt(pos);
151155

152156
if (this.shouldProvideItems(line, pos)) {
153-
this.pushTypesToItems(Object.values(PythonType), items);
157+
this.pushHintsToItems(Object.values(PythonType), items);
154158
}
155159
return Promise.resolve(new CompletionList(items, false));
156160
}

src/typeHintProvider.ts

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { TypeHintSettings } from "./settings";
1010
*/
1111
export class TypeHintProvider {
1212

13+
private doc: TextDocument;
14+
private settings: TypeHintSettings;
15+
private typingHintProvider: TypingHintProvider;
1316
private likelyTypes: PythonType[] = [
1417
PythonType.String,
1518
PythonType.List,
@@ -19,10 +22,9 @@ export class TypeHintProvider {
1922
PythonType.Tuple,
2023
PythonType.Float
2124
];
25+
private providedTypeHints: string[] = [];
2226
private typeContainer: DataTypeContainer = getDataTypeContainer();
23-
private typeNamesIncludedInResult: string[] = [];
24-
private doc: TextDocument;
25-
private settings: TypeHintSettings;
27+
private _typingImported = false;
2628

2729
/**
2830
* Constructs a new TypeHintProvider.
@@ -33,20 +35,24 @@ export class TypeHintProvider {
3335
constructor(doc: TextDocument, settings: TypeHintSettings) {
3436
this.doc = doc;
3537
this.settings = settings;
38+
this.typingHintProvider = new TypingHintProvider(this.typeContainer);
39+
}
40+
41+
public get typingImported() {
42+
return this._typingImported;
3643
}
3744

3845
/**
39-
* Provides type hints for a parameter.
46+
* Estimates a parameter's type and returns type hints for it.
47+
* The returned hints are ordered with the most likely type being first.
4048
*
4149
* @param param The parameter name.
42-
* @returns An array of type hints, ordered by estimation accuracy.
4350
*/
4451
public async getTypeHints(param: string): Promise<string[]> {
4552
const typeHints: string[] = [];
4653
const documentText = this.doc.getText();
4754

48-
const typingHintProvider = new TypingHintProvider(documentText, this.typeContainer);
49-
const typingSearch = typingHintProvider.containsTyping();
55+
const typingSearch = this.typingHintProvider.detectTypingImport(documentText);
5056

5157
const variableSearch = TypeSearch.variableWithSameName(param, documentText);
5258

@@ -59,7 +65,7 @@ export class TypeHintProvider {
5965
let typeName = this.getTypeParamEndsWith(param, "_");
6066
if (typeName) {
6167
this.add(typeName, typeHints);
62-
this.tryAddTypingHint(await typingSearch, typeName, typingHintProvider, typeHints);
68+
this.tryAddTypingHint(await typingSearch, typeName, typeHints);
6369
wsSearcher.cancel();
6470
await workspaceSearch;
6571
return typeHints;
@@ -69,35 +75,44 @@ export class TypeHintProvider {
6975

7076
if (searchResult !== null && !TypeSearch.invalidTernaryOperator(searchResult)) {
7177
this.add(searchResult.typeName, typeHints);
72-
this.tryAddTypingHints(await typingSearch, searchResult, typingHintProvider, typeHints);
78+
this.tryAddTypingHints(await typingSearch, searchResult, typeHints);
7379
this.tryAdd(this.typeGuessFor(param, typeHints), typeHints);
74-
} else if (typeName = this.getTypeParamEndsWith(param, "")) {
75-
this.add(typeName, typeHints);
76-
this.tryAddTypingHint(await typingSearch, typeName, typingHintProvider, typeHints);
7780
} else {
78-
this.tryAdd(this.typeGuessFor(param, typeHints), typeHints);
79-
}
81+
typeName = this.getTypeParamEndsWith(param, "");
82+
if (typeName) {
83+
this.add(typeName, typeHints);
84+
this.tryAddTypingHint(await typingSearch, typeName, typeHints);
85+
} else {
86+
this.tryAdd(this.typeGuessFor(param, typeHints), typeHints);
87+
}
88+
}
8089

8190
this.tryAdd(await workspaceSearch, typeHints);
8291
return typeHints;
8392
}
8493

8594
/**
86-
* Gets types that have not been provided as hints.
87-
*
88-
* @returns An array of types.
95+
* Returns hints for types that have not been provided yet.
8996
*/
90-
public getRemainingTypes(): PythonType[] {
97+
public remainingTypeHints(): PythonType[] {
9198
const result: PythonType[] = [];
9299

93100
for (const type of Object.values(this.typeContainer)) {
94-
if (this.typeNotIncluded(type.name)) {
101+
if (this.hintNotProvided(type.name)) {
95102
result.push(type.name);
96103
}
97104
}
105+
98106
return result;
99107
}
100108

109+
/**
110+
* Returns hints for the typing module that have not been provided yet.
111+
*/
112+
public remainingTypingHints(): string[] {
113+
return this.typingHintProvider.getRemainingHints();
114+
}
115+
101116
/**
102117
* If the param ends with a type name, the type is returned.
103118
* @param param The parameter name.
@@ -134,16 +149,16 @@ export class TypeHintProvider {
134149
}
135150

136151

137-
private add(typeName: string, typeHints: string[]) {
138-
typeName = typeName.trim();
139-
if (this.typeNotIncluded(typeName)) {
140-
typeHints.push(typeName);
141-
this.typeNamesIncludedInResult.push(typeName);
152+
private add(type: string, typeHints: string[]) {
153+
type = type.trim();
154+
if (this.hintNotProvided(type)) {
155+
typeHints.push(type);
156+
this.providedTypeHints.push(type);
142157
}
143158
}
144159

145-
private typeNotIncluded(type: string): boolean {
146-
return !this.typeNamesIncludedInResult.includes(type);
160+
private hintNotProvided(type: string): boolean {
161+
return !this.providedTypeHints.includes(type);
147162
}
148163

149164
private tryAdd(type: string | null, typeHints: string[]) {
@@ -155,11 +170,10 @@ export class TypeHintProvider {
155170
private tryAddTypingHints(
156171
typingFound: boolean,
157172
searchResult: VariableSearchResult | null,
158-
typingHintProvider: TypingHintProvider,
159173
typeHints: string[]
160174
) {
161175
if (typingFound) {
162-
const hints: string[] | null = typingHintProvider.getTypingHints(searchResult);
176+
const hints: string[] | null = this.typingHintProvider.getHints(searchResult);
163177
if (hints) {
164178
for (const hint of hints) {
165179
this.add(hint, typeHints);
@@ -168,14 +182,10 @@ export class TypeHintProvider {
168182
}
169183
}
170184

171-
private tryAddTypingHint(
172-
typingFound: boolean,
173-
typeName: string,
174-
typingHintProvider: TypingHintProvider,
175-
typeHints: string[]
176-
) {
177-
if (typingFound) {
178-
const typingHint = typingHintProvider.getTypingHint(typeName);
185+
private tryAddTypingHint(typingImported: boolean, typeName: string, typeHints: string[]) {
186+
this._typingImported = typingImported;
187+
if (typingImported) {
188+
const typingHint = this.typingHintProvider.getHint(typeName);
179189
if (typingHint) {
180190
this.add(typingHint, typeHints);
181191
}

src/typeSearch.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,8 @@ export class TypeSearch {
9696

9797
// Searching the import source document is not supported (yet?)
9898
if (!this.isImported(match[1], src.substr(match.index - match.length))) {
99-
100-
if (match = this.variableSearchRegExp(match[1]).exec(src)) {
99+
match = this.variableSearchRegExp(match[1]).exec(src);
100+
if (match) {
101101
const otherType = this.detectType(match[1]);
102102
return otherType
103103
? new VariableSearchResult(otherType, EstimationSource.ValueOfOtherVariable, valueAssignment)

src/typingHintProvider.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { TypeCategory, PythonType, DataTypeContainer } from "./python";
1+
import { TypeCategory, PythonType, DataTypeContainer, DataType } from "./python";
22
import { capitalized } from "./utils";
33
import { VariableSearchResult, TypeSearch } from "./typeSearch";
44

@@ -7,32 +7,31 @@ import { VariableSearchResult, TypeSearch } from "./typeSearch";
77
*/
88
export class TypingHintProvider {
99

10-
private docText: string;
1110
private typeContainer: DataTypeContainer;
1211
private fromTypingImport: boolean = false;
12+
private providedTypes: DataType[] = [];
1313
private typingPrefix: string = "typing";
1414

1515
/**
1616
* Constructs a new TypingHintProvider.
1717
*
18-
* @param docText The document text to search.
1918
* @param typeContainer A container with built-in Python types.
2019
*/
21-
constructor(docText: string, typeContainer: DataTypeContainer) {
22-
this.docText = docText;
20+
constructor(typeContainer: DataTypeContainer) {
2321
this.typeContainer = typeContainer;
2422
}
2523

2624
/**
27-
* Determines if this object's document contains a typing import.
25+
* Determines if a document contains a typing import.
2826
*
27+
* @param docText The document text to search.
2928
* @returns True if typing is imported.
3029
*/
31-
public async containsTyping(): Promise<boolean> {
30+
public async detectTypingImport(docText: string): Promise<boolean> {
3231
let m = new RegExp(
3332
`^[ \t]*from typing import +([A-Z][a-zA-Z0-9 ,]+)`,
3433
"m"
35-
).exec(this.docText);
34+
).exec(docText);
3635

3736
if (m) {
3837
this.fromTypingImport = true;
@@ -41,7 +40,7 @@ export class TypingHintProvider {
4140
m = new RegExp(
4241
`^[ \t]*(import +typing +as +([a-zA-Z_][a-zA-Z0-9_-]*)|import +typing)`,
4342
"m"
44-
).exec(this.docText);
43+
).exec(docText);
4544
if (m) {
4645
if (m[2]) {
4746
this.typingPrefix = m[2];
@@ -58,9 +57,10 @@ export class TypingHintProvider {
5857
* @param type A type name.
5958
* @returns A type hint without a closing bracket, for example 'List[ '.
6059
*/
61-
public getTypingHint(typeName: string): string | null {
60+
public getHint(typeName: string): string | null {
6261
const type = this.typeContainer[typeName];
6362
if (type.category === TypeCategory.Collection) {
63+
this.providedTypes.push(type);
6464
return this.toTypingString(type.name);
6565
}
6666
return null;
@@ -72,9 +72,12 @@ export class TypingHintProvider {
7272
* @param searchResult A search result to derive hints from.
7373
* @returns One or two type hints. For example, 'List[' and 'List[str]'.
7474
*/
75-
public getTypingHints(searchResult: VariableSearchResult | null): string[] | null {
75+
public getHints(searchResult: VariableSearchResult | null): string[] | null {
76+
7677
if (searchResult && searchResult.typeName in this.typeContainer) {
7778
const type = this.typeContainer[searchResult.typeName];
79+
this.providedTypes.push(type);
80+
7881
const result: string[] = [ this.toTypingString(type.name)];
7982
let label = result[0];
8083

@@ -120,6 +123,22 @@ export class TypingHintProvider {
120123
return null;
121124
}
122125

126+
/**
127+
* Get hints for collection types that have not been provided yet.
128+
*
129+
* @returns An array of types.
130+
*/
131+
public getRemainingHints(): string[] {
132+
const result: string[] = [];
133+
134+
for (const type of Object.values(this.typeContainer).filter(t => t.category === TypeCategory.Collection)) {
135+
if (!this.providedTypes.includes(type)) {
136+
result.push(this.toTypingString(type.name));
137+
}
138+
}
139+
return result;
140+
}
141+
123142
private toTypingString(typeName: string): string {
124143
const typingName = capitalized(typeName);
125144
return this.fromTypingImport ? `${typingName}[` : `${this.typingPrefix}.${typingName}[`;

0 commit comments

Comments
 (0)