forked from vscode-kubernetes-tools/vscode-kubernetes-tools
-
Notifications
You must be signed in to change notification settings - Fork 0
/
helm.completionProvider.ts
142 lines (128 loc) · 5.34 KB
/
helm.completionProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import * as vscode from 'vscode';
import { FuncMap } from './helm.funcmap';
import * as logger from './logger';
import * as YAML from 'yamljs';
import * as exec from './helm.exec';
import * as path from 'path';
import * as _ from 'lodash';
import { existsSync } from 'fs';
export class HelmTemplateCompletionProvider implements vscode.CompletionItemProvider {
private valuesMatcher = new RegExp('\\s+\\.Values\\.([a-zA-Z0-9\\._-]+)?$');
private funcmap = new FuncMap();
// TODO: On focus, rebuild the values.yaml cache
private valuesCache;
public constructor() {
// The extension activates on things like 'Kubernetes tree visible',
// which can occur on any project (not just projects containing k8s
// manifests or Helm charts). So we don't want the mere initialisation
// of the completion provider to trigger an error message if there are
// no charts - this will actually be the *probable* case.
this.refreshValues({ warnIfNoCharts: false });
}
public refreshValues(options: exec.PickChartUIOptions) {
const ed = vscode.window.activeTextEditor;
if (!ed) {
return;
}
const self = this;
exec.pickChartForFile(ed.document.fileName, options, (f) => {
const valsYaml = path.join(f, "values.yaml");
if (!existsSync(valsYaml)) {
return;
}
try {
self.valuesCache = YAML.load(valsYaml);
} catch (err) {
logger.helm.log(err.message);
return;
}
});
}
public provideCompletionItems(doc: vscode.TextDocument, pos: vscode.Position) {
// If the preceding character is a '.', we kick it into dot resolution mode.
// Otherwise, we go with function completion.
const wordPos = doc.getWordRangeAtPosition(pos);
const word = doc.getText(wordPos);
const line = doc.lineAt(pos.line).text;
const lineUntil = line.substr(0, wordPos.start.character);
if (lineUntil.endsWith(".")) {
return this.dotCompletionItems(doc, pos, word, lineUntil);
}
return new vscode.CompletionList((new FuncMap).all());
}
dotCompletionItems(doc: vscode.TextDocument, pos: vscode.Position, word: string, lineUntil: string): vscode.CompletionItem[] {
if (lineUntil.endsWith(" .")) {
return this.funcmap.helmVals();
} else if (lineUntil.endsWith(".Release.")) {
return this.funcmap.releaseVals();
} else if (lineUntil.endsWith(".Chart.")) {
return this.funcmap.chartVals();
} else if (lineUntil.endsWith(".Files.")) {
return this.funcmap.filesVals();
} else if (lineUntil.endsWith(".Capabilities.")) {
return this.funcmap.capabilitiesVals();
} else if (lineUntil.endsWith(".Values.")) {
if (!_.isPlainObject(this.valuesCache)) {
return;
}
const keys = _.keys(this.valuesCache);
const res = [];
keys.forEach((key) => {
res.push(this.funcmap.v(key, ".Values."+key, "In values.yaml: " + this.valuesCache[key]));
});
return res;
} else {
// If we get here, we inspect the string to see if we are at some point in a
// .Values.SOMETHING. expansion. We recurse through the values file to see
// if there are any autocomplete options there.
let res;
try {
res = this.valuesMatcher.exec(lineUntil);
} catch (err) {
logger.helm.log(err.message);
return [];
}
// If this does not match the valuesMatcher (Not a .Values.SOMETHING...) then
// we return right away.
if (!res || res.length === 0) {
return [];
}
if (res[1].length === 0 ) {
// This is probably impossible. It would match '.Values.', but that is
// matched by a previous condition.
return [];
}
// If we get here, we've got .Values.SOMETHING..., and we want to walk that
// tree to see what suggestions we can give based on the contents of the
// current values.yaml file.
const parts = res[1].split(".");
let cache = this.valuesCache;
for (const cur of parts) {
if (cur.length === 0) {
// We hit the trailing dot.
break;
}
if (!cache[cur]) {
// The key does not exist. User has typed something not in values.yaml
return [];
}
cache = cache[cur];
}
if (!cache) {
return [];
}
const k = [];
_.keys(cache).forEach((item) => {
// Build help text for each suggestion we found.
k.push(this.v(item, res[0] + item, "In values.yaml: " + cache[item]));
});
return k;
}
}
v(name: string, use: string, doc: string): vscode.CompletionItem {
const i = new vscode.CompletionItem(name, vscode.CompletionItemKind.Constant);
i.detail = use;
i.documentation = doc;
return i;
}
}