Skip to content

Commit f80e2b1

Browse files
Add import and export command (#62)
* implement file sync * fmt * change command title * Change to not overwrite if set value is null * refactor: extract commands to new file * refactor * implement import and export command * remove sync command * fix * change the title of import command * extract common logic into new functions * add some comments * fmt * Split import command into global target and workspace target commands * add commands description to the description of configurationFilePath * add command descriptions on readme * add changeset * change command id to title in docs * Unify the capitalization of the title * fix typo Co-authored-by: Yosuke Ota <[email protected]> * fix typo Co-authored-by: Yosuke Ota <[email protected]> * rename handler building functions * fix checking of workspace folders * Add processing for when there are multiple TargetFolders * fix * fix * fix * fix * format --------- Co-authored-by: Yosuke Ota <[email protected]>
1 parent ca4c2be commit f80e2b1

File tree

5 files changed

+279
-13
lines changed

5 files changed

+279
-13
lines changed

.changeset/wise-timers-cheat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"uroborosql-fmt": minor
3+
---
4+
5+
Added commands to import/export configurations.

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,15 @@ If there is no configuration file, the default values are used.
7979
| [`convert_double_colon_cast`](https://github.com/future-architect/uroborosql-fmt/blob/main/docs/options/convert_double_colon_cast.md) | bool | Convert casts by `X::type` to the form `CAST(X AS type)`. | true |
8080
| [`unify_not_equal`](https://github.com/future-architect/uroborosql-fmt/blob/main/docs/options/unify_not_equal.md) | bool | Convert comparison operator `<>` to `!=` | true |
8181

82+
## Available Commands
83+
84+
| command title | description |
85+
| ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
86+
| Format SQL | You can trigger formatting by executing this command. |
87+
| Export workspace config to uroborosql-fmt config file | You can export workspace configurations to formatter's configuration file (specified in [uroborosql-fmt.configurationFilePath](https://github.com/future-architect/vscode-uroborosql-fmt/#:~:text=uroborosql%2Dfmt.configurationFilePath)). Only the non-null configurations in `settings.json` will overwrite those in `.uroborosqlfmtrc.json`. |
88+
| Import uroborosql-fmt config file config to workspace config | You can import formatter's configuration into workspace configuration. Configuration values that exist in `settings.json`, but not in `.uroborosqlfmtrc.json`, will be set to `null`. |
89+
| Import uroborosql-fmt config file config to global config | You can import formatter's configuration into global configuration. Configuration values that exist in `settings.json`, but not in `.uroborosqlfmtrc.json`, will be set to `null`. |
90+
8291
## License
8392

8493
[Business Source License 1.1](https://github.com/future-architect/vscode-uroborosql-fmt/blob/main/LICENSE)

client/src/command.ts

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
import path = require("path");
2+
import { objectToCamel, objectToSnake } from "ts-case-convert";
3+
import type { ObjectToSnake } from "ts-case-convert/lib/caseConvert";
4+
import {
5+
ConfigurationTarget,
6+
Uri,
7+
window,
8+
workspace,
9+
WorkspaceConfiguration,
10+
WorkspaceFolder,
11+
} from "vscode";
12+
import { ExecuteCommandRequest } from "vscode-languageclient";
13+
import { LanguageClient } from "vscode-languageclient/node";
14+
15+
const vsCodeConfigurationsObject = {
16+
configurationFilePath: "",
17+
debug: null,
18+
tabSize: null,
19+
complementAlias: null,
20+
trimBindParam: null,
21+
keywordCase: null,
22+
identifierCase: null,
23+
maxCharPerLine: null,
24+
complementOuterKeyword: null,
25+
complementColumnAsKeyword: null,
26+
removeTableAsKeyword: null,
27+
removeRedundantNest: null,
28+
complementSqlId: null,
29+
convertDoubleColonCast: null,
30+
unifyNotEqual: null,
31+
} satisfies ConfigurationRecord;
32+
33+
type ConfigurationRecord = {
34+
configurationFilePath: string;
35+
debug: boolean | null | undefined;
36+
tabSize: number | null | undefined;
37+
complementAlias: boolean | null | undefined;
38+
trimBindParam: boolean | null | undefined;
39+
keywordCase: string | null | undefined;
40+
identifierCase: string | null | undefined;
41+
maxCharPerLine: number | null | undefined;
42+
complementOuterKeyword: boolean | null | undefined;
43+
complementColumnAsKeyword: boolean | null | undefined;
44+
removeTableAsKeyword: boolean | null | undefined;
45+
removeRedundantNest: boolean | null | undefined;
46+
complementSqlId: boolean | null | undefined;
47+
convertDoubleColonCast: boolean | null | undefined;
48+
unifyNotEqual: boolean | null | undefined;
49+
};
50+
51+
// WorkspaceConfigurationを受け取り、フォーマッタで利用する設定のみのRecordにして返す
52+
const extractFormattingConfigurations = (
53+
workspaceConfig: WorkspaceConfiguration,
54+
): Partial<ConfigurationRecord> => {
55+
// uroborosql-fmtのVSCode拡張で有効な設定項目のうち、明示的に設定されているもののみを取得
56+
const config: Partial<ConfigurationRecord> = {};
57+
for (const key of Object.keys(vsCodeConfigurationsObject)) {
58+
const value = workspaceConfig.get(key);
59+
if (value != null) {
60+
config[key] = value;
61+
}
62+
}
63+
// configurationFilePath を除外
64+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
65+
const { configurationFilePath, ...restConfiguration } = config;
66+
67+
return restConfiguration;
68+
};
69+
70+
const isFileExists = async (uri: Uri): Promise<boolean> => {
71+
try {
72+
await workspace.fs.stat(uri);
73+
return true;
74+
} catch (_) {
75+
return false;
76+
}
77+
};
78+
79+
// uroborosql-fmt の設定ファイル名を取得する
80+
// ワークスペースの側で設定されている場合はその値を、設定されていない場合は ".uroborosqlfmtrc.json"を返す
81+
const getConfigFileName = (
82+
defaultName: string = ".uroborosqlfmtrc.json",
83+
): string => {
84+
// uroborosql-fmt の設定を取得
85+
const vsCodeConfig = workspace.getConfiguration("uroborosql-fmt");
86+
87+
// Default value of `uroborosql-fmt.configurationFilePath` is "".
88+
const vsCodeConfigPath: string = vsCodeConfig.get("configurationFilePath");
89+
return vsCodeConfigPath !== "" ? vsCodeConfigPath : defaultName;
90+
};
91+
92+
const getTargetFolder = (): WorkspaceFolder | undefined => {
93+
const folders = workspace.workspaceFolders;
94+
if (folders === undefined) {
95+
// ワークスペースとしてではなく、ファイルを直接開いている場合
96+
window.showErrorMessage(
97+
"Error: Open the folder before executing commands.",
98+
);
99+
return;
100+
} else if (folders.length == 0) {
101+
// ワークスペースにフォルダが一つも存在しない場合
102+
window.showErrorMessage(
103+
"Error: There is no folder in the workspace. To execute the command, at least one folder must be added to the workspace.",
104+
);
105+
return;
106+
}
107+
if (folders.length === 1) {
108+
return folders[0];
109+
}
110+
111+
// ワークスペースに複数のフォルダが存在する場合
112+
if (!window.activeTextEditor) {
113+
// activeTextEditorが存在しない場合
114+
return;
115+
}
116+
const activeEditorPath = window.activeTextEditor.document.uri.fsPath;
117+
118+
const matchingWorkspace = folders.find((wsFolder) => {
119+
const relative = path.relative(wsFolder.uri.fsPath, activeEditorPath);
120+
return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
121+
});
122+
123+
if (matchingWorkspace) {
124+
return matchingWorkspace;
125+
}
126+
127+
window.showErrorMessage(
128+
"Error: There are multiple folders in the workspace, and it could not be determined which folder's settings to target. Please select a file that belongs to the folder you want to target before executing the command.",
129+
);
130+
return;
131+
};
132+
133+
export const buildFormatFunction =
134+
(client: LanguageClient) => async (): Promise<void> => {
135+
const uri = window.activeTextEditor.document.uri;
136+
const version = window.activeTextEditor.document.version;
137+
const selections = window.activeTextEditor.selections;
138+
139+
await client.sendRequest(ExecuteCommandRequest.type, {
140+
command: "uroborosql-fmt.executeFormat",
141+
arguments: [uri, version, selections],
142+
});
143+
};
144+
145+
export const exportSettings = async (): Promise<void> => {
146+
const folder = getTargetFolder();
147+
if (!folder) {
148+
return;
149+
}
150+
151+
// 設定ファイルのURIを作成
152+
const configFile = Uri.joinPath(folder.uri, getConfigFileName());
153+
154+
// VSCode拡張側の設定を取得
155+
const vsCodeConfig = workspace.getConfiguration("uroborosql-fmt");
156+
const formattingConfig = extractFormattingConfigurations(vsCodeConfig);
157+
158+
// 設定ファイルの設定を取得
159+
let existingConfig: ObjectToSnake<Partial<ConfigurationRecord>>;
160+
if (await isFileExists(configFile)) {
161+
const file = await workspace.fs.readFile(configFile);
162+
existingConfig = JSON.parse(file.toString());
163+
} else {
164+
// ファイルが存在しなければ設定値なし
165+
existingConfig = {};
166+
}
167+
168+
// VSCode 側で明示的に設定したものだけを上書きする
169+
const merged = {
170+
...existingConfig,
171+
...objectToSnake(formattingConfig),
172+
};
173+
const content = JSON.stringify(merged, null, 2);
174+
175+
const blob: Uint8Array = Buffer.from(content);
176+
await workspace.fs.writeFile(configFile, blob);
177+
};
178+
179+
// uroborosqlfmtrc.json の設定を settings.json に反映
180+
export const buildImportSettingsFunction =
181+
(target: ConfigurationTarget) => async (): Promise<void> => {
182+
const folder = getTargetFolder();
183+
if (!folder) {
184+
return;
185+
}
186+
187+
// 設定ファイルのURIを作成
188+
const configFile = Uri.joinPath(folder.uri, getConfigFileName());
189+
190+
if (!(await isFileExists(configFile))) {
191+
window.showErrorMessage(
192+
`Error: Config File is not found: ${configFile.path}`,
193+
);
194+
return;
195+
}
196+
197+
const blob = await workspace.fs.readFile(configFile);
198+
const config: ObjectToSnake<ConfigurationRecord> = JSON.parse(
199+
blob.toString(),
200+
);
201+
202+
// VSCode 拡張の設定を取得
203+
const vsCodeConfig: WorkspaceConfiguration =
204+
workspace.getConfiguration("uroborosql-fmt");
205+
206+
// ワークスペース側で設定されている設定項目(そのうち `configurationFilePath` 以外のもの)をすべて null にする
207+
const vsCodeOptions = extractFormattingConfigurations(vsCodeConfig);
208+
await Promise.all(
209+
Object.keys(vsCodeOptions).map((key) =>
210+
vsCodeConfig.update(key, null, target),
211+
),
212+
);
213+
214+
// 設定ファイルの値で更新する
215+
await Promise.all(
216+
Object.entries(objectToCamel(config)).map(([key, value]) =>
217+
vsCodeConfig.update(key, value, target),
218+
),
219+
);
220+
};

client/src/extension.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,22 @@ import {
77
StatusBarAlignment,
88
ThemeColor,
99
StatusBarItem,
10+
ConfigurationTarget,
1011
} from "vscode";
1112

1213
import {
1314
LanguageClient,
1415
LanguageClientOptions,
1516
ServerOptions,
1617
TransportKind,
17-
ExecuteCommandRequest,
1818
} from "vscode-languageclient/node";
1919

20+
import {
21+
buildFormatFunction,
22+
exportSettings,
23+
buildImportSettingsFunction,
24+
} from "./command";
25+
2026
let client: LanguageClient;
2127

2228
//拡張機能を立ち上げたときに呼び出す関数
@@ -62,16 +68,28 @@ export function activate(context: ExtensionContext) {
6268
);
6369

6470
context.subscriptions.push(
65-
commands.registerCommand("uroborosql-fmt.uroborosql-format", async () => {
66-
const uri = window.activeTextEditor.document.uri;
67-
const version = window.activeTextEditor.document.version;
68-
const selections = window.activeTextEditor.selections;
69-
70-
await client.sendRequest(ExecuteCommandRequest.type, {
71-
command: "uroborosql-fmt.executeFormat",
72-
arguments: [uri, version, selections],
73-
});
74-
}),
71+
commands.registerCommand(
72+
"uroborosql-fmt.uroborosql-format",
73+
buildFormatFunction(client),
74+
),
75+
);
76+
77+
context.subscriptions.push(
78+
commands.registerCommand("uroborosql-fmt.export", exportSettings),
79+
);
80+
81+
context.subscriptions.push(
82+
commands.registerCommand(
83+
"uroborosql-fmt.import-to-global",
84+
buildImportSettingsFunction(ConfigurationTarget.Global),
85+
),
86+
);
87+
88+
context.subscriptions.push(
89+
commands.registerCommand(
90+
"uroborosql-fmt.import-to-workspace",
91+
buildImportSettingsFunction(ConfigurationTarget.Workspace),
92+
),
7593
);
7694

7795
// ステータスバーの作成と表示

package.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,29 @@
3030
"commands": [
3131
{
3232
"command": "uroborosql-fmt.uroborosql-format",
33-
"title": "format sql"
33+
"title": "Format SQL"
34+
},
35+
{
36+
"command": "uroborosql-fmt.import-to-global",
37+
"title": "Import uroborosql-fmt config file config to global config"
38+
},
39+
{
40+
"command": "uroborosql-fmt.import-to-workspace",
41+
"title": "Import uroborosql-fmt config file config to workspace config"
42+
},
43+
{
44+
"command": "uroborosql-fmt.export",
45+
"title": "Export workspace config to uroborosql-fmt config file"
3446
}
3547
],
3648
"configuration": {
3749
"title": "uroborosql-fmt",
3850
"properties": {
3951
"uroborosql-fmt.configurationFilePath": {
4052
"type": "string",
41-
"description": "The path of configuration file. File extension must be `.json`. If you don't specify the path and `./.uroborosqlfmtrc.json` exists, formatter will use `./.uroborosqlfmtrc.json`. If you doesn't specify and `.uroborosqlfmtrc.json` doesn't exist, formatter will use formatters default configurations."
53+
"markdownDescription": "The path of configuration file. File extension must be `.json`.\n\n- If you don't specify the path and `./.uroborosqlfmtrc.json` exists, formatter will use `./.uroborosqlfmtrc.json`.\n- If you doesn't specify and `.uroborosqlfmtrc.json` doesn't exist, formatter will use formatters default configurations.\n\nYou can use the commands listed below.\n- By executing `Import uroborosql-fmt config file config to workspace config` command, you can import formatter configurations into the workspace configuration.\n- By executing `Import uroborosql-fmt config file config to global config` command, you can import formatter configurations into the global configuration.\n- By executing `Export workspace config to uroborosql-fmt config file` command, you can export VSCode configurations to the formatter configuration file.",
54+
"pattern": "^$|.+\\.json$",
55+
"patternErrorMessage": "File extension must be `.json`."
4256
},
4357
"uroborosql-fmt.debug": {
4458
"type": [

0 commit comments

Comments
 (0)