Skip to content

Commit 241d70e

Browse files
committed
feat: add command to remove default lines from .cnfg files
Adds a new command 'Septic: Remove default lines' that removes all attribute lines set to their documented default values from the active .cnfg file. This reduces file size and makes configs easier to read and compare. Closes #320 Closes #934
1 parent 6db8f27 commit 241d70e

8 files changed

Lines changed: 366 additions & 0 deletions

File tree

packages/extension/client/src/commands.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,49 @@ export function registerCommandGetFunctions(
444444
});
445445
}
446446

447+
export function registerCommandRemoveDefaults(
448+
context: vscode.ExtensionContext,
449+
client: LanguageClient,
450+
) {
451+
vscode.commands.registerCommand("septic.removeDefaults", async () => {
452+
const editor = vscode.window.activeTextEditor;
453+
if (!editor) {
454+
vscode.window.showInformationMessage("No active editor.");
455+
return;
456+
}
457+
if (!editor.document.fileName.endsWith(".cnfg")) {
458+
vscode.window.showInformationMessage(
459+
"Active file is not a .cnfg file.",
460+
);
461+
return;
462+
}
463+
const uri = editor.document.uri.toString();
464+
const result = await client.sendRequest(protocol.removeDefaults, {
465+
uri,
466+
});
467+
if (!result) {
468+
vscode.window.showInformationMessage(
469+
"Unable to process file.",
470+
);
471+
return;
472+
}
473+
const currentText = editor.document.getText();
474+
if (result === currentText) {
475+
vscode.window.showInformationMessage(
476+
"No default lines found to remove.",
477+
);
478+
return;
479+
}
480+
const fullRange = new vscode.Range(
481+
editor.document.positionAt(0),
482+
editor.document.positionAt(currentText.length),
483+
);
484+
const edit = new vscode.WorkspaceEdit();
485+
edit.replace(editor.document.uri, fullRange, result);
486+
await vscode.workspace.applyEdit(edit);
487+
});
488+
}
489+
447490
export function registerCommands(
448491
context: vscode.ExtensionContext,
449492
client: LanguageClient,
@@ -464,4 +507,5 @@ export function registerCommands(
464507
registerCommandMakeConfig();
465508
registerCommandPlotModel();
466509
registerCommandGetFunctions(context, client);
510+
registerCommandRemoveDefaults(context, client);
467511
}

packages/extension/client/src/protocol.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ export const compareCnfg = new RequestType<
109109
unknown
110110
>("septic/compareCnfg");
111111

112+
export const removeDefaults = new RequestType<
113+
{ uri: string },
114+
string,
115+
unknown
116+
>("septic/removeDefaults");
117+
112118
export const contexts = new RequestType<object, string[], unknown>(
113119
"septic/contexts",
114120
);

packages/extension/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"command": "septic.getFunctions",
5050
"title": "Septic: Get functions in file"
5151
},
52+
{
53+
"command": "septic.removeDefaults",
54+
"title": "Septic: Remove default lines"
55+
},
5256
{
5357
"command": "septic.scgtree.addTemplate",
5458
"title": "Septic: Insert template below",

packages/extension/server/src/protocol.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ export const compareCnfg = new RequestType<
6969
unknown
7070
>("septic/compareCnfg");
7171

72+
export const removeDefaults = new RequestType<
73+
{ uri: string },
74+
string,
75+
unknown
76+
>("septic/removeDefaults");
77+
7278
export const contexts = new RequestType<object, string[], unknown>(
7379
"septic/contexts",
7480
);

packages/extension/server/src/server.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
SepticMetaInfoProvider,
3838
SepticCnfg,
3939
compareCnfgs,
40+
removeDefaultLines,
4041
} from "@equinor/septic-config-lib";
4142
import { getIgnorePatterns, getIgnoredCodes } from "./ignorePath";
4243
import { ContextManager } from "./contextManager";
@@ -180,6 +181,16 @@ connection.onRequest(protocol.compareCnfg, async (param) => {
180181
return compareCnfgs(prevCnfg, currentCnfg, param.settingsFile);
181182
});
182183

184+
connection.onRequest(protocol.removeDefaults, async (param) => {
185+
const cnfg: SepticCnfg | undefined = await langService.cnfgProvider.get(
186+
param.uri,
187+
);
188+
if (!cnfg) {
189+
return "";
190+
}
191+
return removeDefaultLines(cnfg);
192+
});
193+
183194
connection.onRequest(protocol.contexts, async () => {
184195
const contexts = scgContextManager.getAllContexts().map((val) => val.name);
185196
for (const uri of documentProvider.getAllDocumentUris()) {

packages/septic/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export {
2929
SepticDiagnosticCode,
3030
} from "./diagnostics";
3131
export { compareCnfgs } from "./compare";
32+
export { removeDefaultLines } from "./removeDefaults";
33+
export type { ObjectDocProvider } from "./removeDefaults";
3234
export { SepticCnfgFormatter } from "./formatter";
3335
export {
3436
SepticConfigProviderFs,
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Equinor ASA
3+
* Licensed under the MIT License. See LICENSE in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { SepticCnfg } from "./cnfg";
7+
import { SepticAttribute } from "./elements";
8+
import {
9+
SepticMetaInfoProvider,
10+
SepticAttributeDocumentation,
11+
ISepticObjectDocumentation,
12+
} from "./metaInfoProvider";
13+
14+
export interface ObjectDocProvider {
15+
getObjectDocumentation(
16+
objectType: string,
17+
): ISepticObjectDocumentation | undefined;
18+
}
19+
20+
interface OffsetRange {
21+
start: number;
22+
end: number;
23+
}
24+
25+
/**
26+
* Returns the text of a cnfg with all attributes set to their default values removed.
27+
*/
28+
export function removeDefaultLines(
29+
cnfg: SepticCnfg,
30+
provider?: ObjectDocProvider,
31+
): string {
32+
const metaInfo = provider ?? SepticMetaInfoProvider.getInstance();
33+
const text = cnfg.doc.getText();
34+
const rangesToRemove: OffsetRange[] = [];
35+
36+
for (const obj of cnfg.objects) {
37+
const objDoc = metaInfo.getObjectDocumentation(obj.type);
38+
if (!objDoc) {
39+
continue;
40+
}
41+
for (const attr of obj.attributes) {
42+
const attrDoc = objDoc.getAttribute(attr.key);
43+
if (!attrDoc || !attrDoc.default || attrDoc.default.length === 0) {
44+
continue;
45+
}
46+
if (isDefault(attr, attrDoc)) {
47+
const range = getAttributeLineRange(text, attr);
48+
if (range) {
49+
rangesToRemove.push(range);
50+
}
51+
}
52+
}
53+
}
54+
55+
if (rangesToRemove.length === 0) {
56+
return text;
57+
}
58+
59+
// Sort by start offset descending so removals don't shift earlier offsets
60+
rangesToRemove.sort((a, b) => b.start - a.start);
61+
62+
let result = text;
63+
for (const range of rangesToRemove) {
64+
result = result.slice(0, range.start) + result.slice(range.end);
65+
}
66+
return result;
67+
}
68+
69+
function isDefault(
70+
attr: SepticAttribute,
71+
attrDoc: SepticAttributeDocumentation,
72+
): boolean {
73+
const values = attr.getValues();
74+
const defaults: (string | number)[] = attrDoc.default;
75+
76+
if (values.length !== defaults.length) {
77+
return false;
78+
}
79+
80+
for (let i = 0; i < values.length; i++) {
81+
if (normalizeValue(values[i]!) !== normalizeValue(defaults[i]!)) {
82+
return false;
83+
}
84+
}
85+
return true;
86+
}
87+
88+
function normalizeValue(value: string | number): string {
89+
let str = String(value);
90+
// Remove surrounding quotes
91+
if (
92+
(str.startsWith('"') && str.endsWith('"')) ||
93+
(str.startsWith("'") && str.endsWith("'"))
94+
) {
95+
str = str.slice(1, -1);
96+
}
97+
return str.trim();
98+
}
99+
100+
/**
101+
* Returns the full line range (including trailing newline) that contains the attribute.
102+
* Expands to cover all lines the attribute spans.
103+
*/
104+
function getAttributeLineRange(
105+
text: string,
106+
attr: SepticAttribute,
107+
): OffsetRange | undefined {
108+
// Find start of line containing attr.start
109+
let lineStart = attr.start;
110+
while (lineStart > 0 && text[lineStart - 1] !== "\n") {
111+
lineStart--;
112+
}
113+
114+
// Find end of line containing attr.end
115+
let lineEnd = attr.end;
116+
while (lineEnd < text.length && text[lineEnd] !== "\n") {
117+
lineEnd++;
118+
}
119+
// Include the newline character
120+
if (lineEnd < text.length && text[lineEnd] === "\n") {
121+
lineEnd++;
122+
}
123+
124+
return { start: lineStart, end: lineEnd };
125+
}

0 commit comments

Comments
 (0)