Skip to content

Commit 64a73c4

Browse files
committed
[WIP] Refactoring to change strings into block scalars
TODO: - [ ] Translations (I think the French one is probably wrong) - [ ] Handle leading spaces correctly (leading spaces are ignored, so we need another way to preserve them) - [ ] Tests, especially for edge cases Fixes #1119 Signed-off-by: David Thompson <[email protected]>
1 parent 86a61da commit 64a73c4

File tree

5 files changed

+192
-4
lines changed

5 files changed

+192
-4
lines changed

.vscode/tasks.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
{
77
"label": "watch typescript",
88
"type": "shell",
9-
"command": "yarn run watch",
9+
"command": "npm run watch",
1010
"presentation": {
1111
"reveal": "never"
1212
},

l10n/bundle.l10n.fr.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,7 @@
5252
"flowStyleMapForbidden": "Le mappage de style de flux est interdit",
5353
"flowStyleSeqForbidden": "La séquence de style Flow est interdite",
5454
"unUsedAnchor": "Ancre inutilisée '{0}'",
55-
"unUsedAlias": "Alias ​​non résolu '{0}'"
55+
"unUsedAlias": "Alias non résolu '{0}'",
56+
"convertToFoldedBlockString": "Convertir la chaîne en style de bloc plie",
57+
"convertToLiteralBlockString": "Convertir la chaîne en style de bloc literale"
5658
}

l10n/bundle.l10n.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,7 @@
5252
"flowStyleMapForbidden": "Flow style mapping is forbidden",
5353
"flowStyleSeqForbidden": "Flow style sequence is forbidden",
5454
"unUsedAnchor": "Unused anchor \"{0}\"",
55-
"unUsedAlias": "Unresolved alias \"{0}\""
55+
"unUsedAlias": "Unresolved alias \"{0}\"",
56+
"convertToFoldedBlockString": "Convert string to folded block string",
57+
"convertToLiteralBlockString": "Convert string to literal block string"
5658
}

src/languageservice/services/yamlCodeActions.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ import { LanguageSettings } from '../yamlLanguageService';
2222
import { YAML_SOURCE } from '../parser/jsonParser07';
2323
import { getFirstNonWhitespaceCharacterAfterOffset } from '../utils/strings';
2424
import { matchOffsetToDocument } from '../utils/arrUtils';
25-
import { CST, isMap, isSeq, YAMLMap } from 'yaml';
25+
import { CST, isMap, isScalar, isSeq, Scalar, visit, YAMLMap } from 'yaml';
2626
import { yamlDocumentsCache } from '../parser/yaml-documents';
2727
import { FlowStyleRewriter } from '../utils/flow-style-rewriter';
2828
import { ASTNode } from '../jsonASTTypes';
2929
import * as _ from 'lodash';
3030
import { SourceToken } from 'yaml/dist/parse/cst';
3131
import { ErrorCode } from 'vscode-json-languageservice';
3232
import * as l10n from '@vscode/l10n';
33+
import { BlockStringRewriter } from '../utils/block-string-rewriter';
3334

3435
interface YamlDiagnosticData {
3536
schemaUri: string[];
@@ -57,6 +58,7 @@ export class YamlCodeActions {
5758
result.push(...this.getTabToSpaceConverting(params.context.diagnostics, document));
5859
result.push(...this.getUnusedAnchorsDelete(params.context.diagnostics, document));
5960
result.push(...this.getConvertToBlockStyleActions(params.context.diagnostics, document));
61+
result.push(...this.getConvertStringToBlockStyleActions(params.context.diagnostics, document));
6062
result.push(...this.getKeyOrderActions(params.context.diagnostics, document));
6163
result.push(...this.getQuickFixForPropertyOrValueMismatch(params.context.diagnostics, document));
6264

@@ -243,6 +245,50 @@ export class YamlCodeActions {
243245
return results;
244246
}
245247

248+
private getConvertStringToBlockStyleActions(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
249+
const yamlDocument = yamlDocumentsCache.getYamlDocument(document);
250+
251+
const results: CodeAction[] = [];
252+
for (const singleYamlDocument of yamlDocument.documents) {
253+
const matchingNodes: Scalar<string>[] = [];
254+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
255+
visit(singleYamlDocument.internalDocument, (key, node, path) => {
256+
if (isScalar(node)) {
257+
if (node.type === 'PLAIN' || node.type === 'QUOTE_DOUBLE' || node.type === 'QUOTE_SINGLE') {
258+
if (typeof node.value === 'string' && (node.value.indexOf('\n') >= 0 || node.value.length > 120)) {
259+
matchingNodes.push(<Scalar<string>>node);
260+
}
261+
}
262+
}
263+
});
264+
for (const node of matchingNodes) {
265+
const range = Range.create(document.positionAt(node.range[0]), document.positionAt(node.range[2]));
266+
const rewriter = new BlockStringRewriter(this.indentation);
267+
const foldedBlockScalar = rewriter.writeFoldedBlockScalar(node);
268+
if (foldedBlockScalar !== null) {
269+
results.push(
270+
CodeAction.create(
271+
l10n.t('convertToFoldedBlockString'),
272+
createWorkspaceEdit(document.uri, [TextEdit.replace(range, foldedBlockScalar)]),
273+
CodeActionKind.Refactor
274+
)
275+
);
276+
}
277+
const literalBlockScalar = rewriter.writeLiteralBlockScalar(node);
278+
if (literalBlockScalar !== null) {
279+
results.push(
280+
CodeAction.create(
281+
l10n.t('convertToLiteralBlockString'),
282+
createWorkspaceEdit(document.uri, [TextEdit.replace(range, literalBlockScalar)]),
283+
CodeActionKind.Refactor
284+
)
285+
);
286+
}
287+
}
288+
}
289+
return results;
290+
}
291+
246292
private getKeyOrderActions(diagnostics: Diagnostic[], document: TextDocument): CodeAction[] {
247293
const results: CodeAction[] = [];
248294
for (const diagnostic of diagnostics) {
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { CST, Scalar } from 'yaml';
2+
3+
export class BlockStringRewriter {
4+
constructor(private readonly indentation: string) {}
5+
6+
public writeFoldedBlockScalar(node: Scalar<string>): string | null {
7+
if (node.type !== 'PLAIN' && node.type !== 'QUOTE_DOUBLE' && node.type !== 'QUOTE_SINGLE') {
8+
return null;
9+
}
10+
11+
const stringContent = node.value;
12+
13+
const lines: string[] = [];
14+
let remainder = stringContent;
15+
while (remainder.length > 120) {
16+
const location = remainder.indexOf('\n') >= 0 ? Math.min(remainder.indexOf('\n'), 120) : 120;
17+
if (remainder.charAt(0) === '\n') {
18+
remainder = remainder.substring(1);
19+
} else {
20+
const head = remainder.substring(0, location);
21+
lines.push(head);
22+
remainder = remainder.substring(location);
23+
}
24+
}
25+
lines.push(remainder);
26+
27+
const newProps: CST.Token[] = lines.flatMap((line) => {
28+
if (line === '\n') {
29+
// newlines can be represented as two newlines in folded blocks
30+
return [
31+
{
32+
type: 'newline',
33+
indent: 0,
34+
offset: node.srcToken.offset,
35+
source: '\n',
36+
},
37+
{
38+
type: 'newline',
39+
indent: 0,
40+
offset: node.srcToken.offset,
41+
source: '\n',
42+
},
43+
];
44+
}
45+
return [
46+
{
47+
type: 'newline',
48+
indent: 0,
49+
offset: node.srcToken.offset,
50+
source: '\n',
51+
},
52+
{
53+
type: 'space',
54+
indent: 0,
55+
offset: node.srcToken.offset,
56+
source: this.indentation,
57+
},
58+
{
59+
type: 'scalar',
60+
indent: 0,
61+
offset: node.srcToken.offset,
62+
source: line,
63+
},
64+
];
65+
});
66+
67+
newProps.unshift({
68+
type: 'block-scalar-header',
69+
source: '>',
70+
offset: node.srcToken.offset,
71+
indent: 0,
72+
});
73+
74+
const blockString: CST.BlockScalar = {
75+
type: 'block-scalar',
76+
offset: node.srcToken.offset,
77+
indent: 0,
78+
source: '',
79+
props: newProps,
80+
};
81+
82+
return CST.stringify(blockString as CST.Token);
83+
}
84+
85+
public writeLiteralBlockScalar(node: Scalar<string>): string | null {
86+
if (node.type !== 'PLAIN' && node.type !== 'QUOTE_DOUBLE' && node.type !== 'QUOTE_SINGLE') {
87+
return null;
88+
}
89+
90+
const stringContent = node.value;
91+
// I don't think it's worth it
92+
if (stringContent.indexOf('\n') < 0) {
93+
return null;
94+
}
95+
96+
const lines: string[] = stringContent.split('\n');
97+
98+
const newProps: CST.Token[] = lines.flatMap((line) => {
99+
return [
100+
{
101+
type: 'newline',
102+
indent: 0,
103+
offset: node.srcToken.offset,
104+
source: '\n',
105+
},
106+
{
107+
type: 'space',
108+
indent: 0,
109+
offset: node.srcToken.offset,
110+
source: this.indentation,
111+
},
112+
{
113+
type: 'scalar',
114+
indent: 0,
115+
offset: node.srcToken.offset,
116+
source: line,
117+
},
118+
];
119+
});
120+
121+
newProps.unshift({
122+
type: 'block-scalar-header',
123+
source: '|',
124+
offset: node.srcToken.offset,
125+
indent: 0,
126+
});
127+
128+
const blockString: CST.BlockScalar = {
129+
type: 'block-scalar',
130+
offset: node.srcToken.offset,
131+
indent: 0,
132+
source: '',
133+
props: newProps,
134+
};
135+
136+
return CST.stringify(blockString as CST.Token);
137+
}
138+
}

0 commit comments

Comments
 (0)