Skip to content

Commit fb54502

Browse files
committed
make a function to detect the indentation of the currect file
1 parent 1845509 commit fb54502

File tree

9 files changed

+579
-36
lines changed

9 files changed

+579
-36
lines changed

language-server/.DS_Store

6 KB
Binary file not shown.

language-server/package-lock.json

Lines changed: 481 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

language-server/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
"keywords": [],
1717
"dependencies": {
1818
"chokidar": "^4.0.1",
19+
"detect-indent": "^7.0.1",
1920
"ignore": "^7.0.1",
2021
"jsonc-parser": "^3.3.1",
2122
"merge-anything": "^6.0.2",
23+
"vscode": "^1.1.37",
2224
"vscode-languageserver": "^9.0.1",
2325
"vscode-languageserver-textdocument": "^1.0.12",
2426
"vscode-uri": "^3.0.8"

language-server/src/.DS_Store

6 KB
Binary file not shown.

language-server/src/build-server.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export const buildServer = (connection) => {
5050
new GotoDefinitionFeature(server, schemas);
5151
new FindReferencesFeature(server, schemas);
5252
new HoverFeature(server, schemas);
53-
new ExtractSubSchemaToDefs(server, schemas);
53+
new ExtractSubSchemaToDefs(server, schemas, configuration);
5454

5555
// TODO: It's awkward that diagnostics needs a variable
5656
const diagnostics = new DiagnosticsFeature(server, [
6 KB
Binary file not shown.

language-server/src/features/codeAction/extractSubschema.js

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,37 @@ import {
55
import { getKeywordName } from "@hyperjump/json-schema/experimental";
66
import * as SchemaDocument from "../../model/schema-document.js";
77
import * as SchemaNode from "../../model/schema-node.js";
8-
import * as jsoncParser from "jsonc-parser";
8+
import { formatNewDef } from "../../util/util.js";
9+
910
/**
10-
* @import { Server } from "../../services/server.js"
11-
* @import { Schemas } from "../../services/schemas.js"
11+
* @import { Server } from "../../services/server.js";
12+
* @import { Schemas } from "../../services/schemas.js";
1213
* @import { CodeAction } from "vscode-languageserver";
14+
* @import { Configuration } from "../../services/configuration.js";
1315
*/
16+
17+
1418
export class ExtractSubSchemaToDefs {
1519
/**
1620
* @param {Server} server
1721
* @param {Schemas} schemas
22+
* @param {Configuration} configuration
1823
*/
19-
constructor(server, schemas) {
24+
constructor(server, schemas, configuration) {
2025
this.server = server;
2126
this.schemas = schemas;
27+
this.configuration = configuration;
2228
server.onInitialize(() => ({
2329
capabilities: {
2430
codeActionProvider: true
2531
}
2632
}));
2733

28-
// Helper function to format new def using jsonc-parser
29-
const formatNewDef = (/** @type {string} */ newDefText) => {
30-
try {
31-
/** @type {unknown} */
32-
const parsedDef = jsoncParser.parse(newDefText);
33-
return JSON.stringify(parsedDef, null, 2).replace(/\n/g, "\n ");
34-
} catch {
35-
return newDefText;
34+
server.onCodeAction(async ({ textDocument, range }) => {
35+
if (range.start.line === range.end.line && range.start.character === range.end.character) {
36+
return [];
3637
}
37-
};
3838

39-
server.onCodeAction(async ({ textDocument, range }) => {
4039
const uri = textDocument.uri;
4140
let schemaDocument = await schemas.getOpen(uri);
4241
if (!schemaDocument) {
@@ -48,35 +47,40 @@ export class ExtractSubSchemaToDefs {
4847
if (!node?.isSchema) {
4948
return [];
5049
}
50+
5151
let definitionsNode;
5252
for (const schemaNode of SchemaNode.allNodes(node.root)) {
5353
if (schemaNode.keywordUri === "https://json-schema.org/keyword/definitions") {
5454
definitionsNode = schemaNode;
5555
break;
5656
}
5757
}
58+
5859
let highestDefNumber = 0;
5960
if (definitionsNode) {
60-
const defsContent = schemaDocument.textDocument.getText().slice(
61-
definitionsNode.offset,
62-
definitionsNode.offset + definitionsNode.textLength
63-
);
64-
const defMatches = [...defsContent.matchAll(/"def(\d+)":/g)];
65-
defMatches.forEach((match) =>
66-
highestDefNumber = Math.max(highestDefNumber, parseInt(match[1], 10))
67-
);
61+
let defNodeKeys = SchemaNode.keys(definitionsNode);
62+
for (const key of defNodeKeys) {
63+
/** @type {string} */
64+
const keyValue = String(SchemaNode.value(key));
65+
/** @type RegExpMatchArray | null */
66+
const match = /^def(\d+)$/.exec(keyValue);
67+
if (match) {
68+
highestDefNumber = Math.max(parseInt(match[1], 10), highestDefNumber);
69+
}
70+
}
6871
}
6972
let newDefName = `def${highestDefNumber + 1}`;
73+
const settings = await this.configuration.get();
7074
const extractedDef = schemaDocument.textDocument.getText(range);
71-
const newFormattedDef = formatNewDef(extractedDef);
75+
const newFormattedDef = await formatNewDef(textDocument.uri, extractedDef, settings.tabSize, settings.insertSpaces, settings.detectIndentation);
7276
let defName = getKeywordName(
73-
/** @type {string} */ (node.root.dialectUri),
77+
/** @type {string} */ (node.root.dialectUri),
7478
"https://json-schema.org/keyword/definitions"
7579
);
7680

7781
/** @type {CodeAction} */
7882
const codeAction = {
79-
title: `Extract '${newDefName}' to $defs`,
83+
title: `Extract '${newDefName}' to ${defName}`,
8084
kind: CodeActionKind.RefactorExtract,
8185
edit: {
8286
documentChanges: [

language-server/src/services/configuration.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import ignore from "ignore";
1111
* @typedef {{
1212
* defaultDialect?: string;
1313
* schemaFilePatterns: string[];
14+
* tabSize: number;
15+
* insertSpaces: boolean;
16+
* detectIndentation: boolean;
1417
* }} DocumentSettings
1518
*/
1619

@@ -81,20 +84,37 @@ export class Configuration {
8184
});
8285
}
8386

84-
/** @type () => Promise<DocumentSettings> */
85-
async get() {
87+
/** @type (documentUri?: string) => Promise<DocumentSettings> */
88+
async get(documentUri) {
8689
if (!this.#settings) {
87-
/** @type unknown */
88-
const result = await this.#server.workspace.getConfiguration({
90+
/** @type {unknown} */
91+
const extensionSettings = await this.#server.workspace.getConfiguration({
8992
section: "jsonSchemaLanguageServer"
9093
});
91-
const settings = result ?? {};
92-
/** @type unknown */
93-
const fullSettings = { ...this.#defaultSettings, ...settings };
94+
/** @type {{ tabSize?: number; insertSpaces?: boolean; detectIndentation?: boolean }} */
95+
const editorSettings = /** @type {unknown} */ (
96+
await this.#server.workspace.getConfiguration({
97+
section: "editor",
98+
scopeUri: documentUri
99+
})
100+
) || {};
101+
const settings = extensionSettings ?? {};
102+
/** @type {{ tabSize?: number; insertSpaces?: boolean; detectIndentation?: boolean }} */
103+
const indentationSettings = {
104+
tabSize: editorSettings?.tabSize,
105+
insertSpaces: editorSettings?.insertSpaces,
106+
detectIndentation: editorSettings?.detectIndentation
107+
};
108+
109+
// Merge settings with proper priority
110+
const fullSettings = {
111+
...this.#defaultSettings,
112+
...settings,
113+
...indentationSettings
114+
};
94115
this.#settings = /** @type DocumentSettings */ (fullSettings);
95116
this.#matcher = undefined;
96117
}
97-
98118
return /** @type DocumentSettings */ (this.#settings);
99119
}
100120

language-server/src/util/util.js

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { getKeywordId, getKeywordName } from "@hyperjump/json-schema/experimental";
22
import { resolveIri as hyperjumpResolveIri } from "@hyperjump/uri";
3-
import { readdir } from "node:fs/promises";
3+
import { readdir, readFile } from "node:fs/promises";
44
import { join, relative } from "node:path";
55
import { URI } from "vscode-uri";
6-
6+
import detectIndent from "detect-indent";
7+
import * as jsoncParser from "jsonc-parser";
78
/**
89
* @import { SchemaNode as SchemaNodeType } from "../model/schema-node.js"
910
* @import { Ignore } from "ignore"
@@ -108,3 +109,38 @@ export const readDirRecursive = async function* (path, filter, cwd) {
108109
}
109110
}
110111
};
112+
113+
/** @type (uri: string, defaultTabSize: number, insertSpaces: boolean, detectIndentation: boolean) => Promise<{ type: 'tabs' | 'spaces', size: number }> */
114+
export const detectIndentationFromContent = async (uri, defaultTabSize, insertSpaces, detectIndentation) => {
115+
try {
116+
const filePath = URI.parse(uri).fsPath;
117+
const content = await readFile(filePath, "utf-8");
118+
const { amount } = detectIndent(content);
119+
120+
if (!detectIndentation) {
121+
return { type: "spaces", size: defaultTabSize };
122+
}
123+
124+
return insertSpaces
125+
? { type: "spaces", size: amount }
126+
: { type: "tabs", size: amount };
127+
} catch {
128+
return { type: "spaces", size: defaultTabSize };
129+
}
130+
};
131+
132+
/** @type (uri: string, newDefText: string, defaultTabSize: number, insertSpaces: boolean, detectIndentation: boolean) => Promise<string>} */
133+
export const formatNewDef = async (uri, newDefText, defaultTabSize, insertSpaces, detectIndentation) => {
134+
try {
135+
const detectedIndent = await detectIndentationFromContent(uri, defaultTabSize, insertSpaces, detectIndentation);
136+
137+
const edits = jsoncParser.format(newDefText, undefined, {
138+
insertSpaces: detectedIndent?.type === "spaces",
139+
tabSize: detectedIndent?.size ?? defaultTabSize
140+
});
141+
142+
return jsoncParser.applyEdits(newDefText, edits).replace(/\n/g, `\n${" ".repeat(detectedIndent?.size ?? defaultTabSize)}`);
143+
} catch {
144+
return newDefText;
145+
}
146+
};

0 commit comments

Comments
 (0)