Skip to content

Commit 9e0dc7e

Browse files
committed
Add New Swift File command
Add a command that creates a new Swift File. Add it as an option to the File > New File picker. Add it as an option when right clicking a folder in a Swift project context. Add a keybinding of Opt+S Opt+N (or Alt+S Alt+N) to create a new Swift file.
1 parent d131215 commit 9e0dc7e

File tree

4 files changed

+150
-0
lines changed

4 files changed

+150
-0
lines changed

package.json

+23
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@
7979
"title": "Create New Project...",
8080
"category": "Swift"
8181
},
82+
{
83+
"command": "swift.newFile",
84+
"title": "Create New Swift File...",
85+
"shortTitle": "Swift File",
86+
"category": "Swift"
87+
},
8288
{
8389
"command": "swift.updateDependencies",
8490
"title": "Update Package Dependencies",
@@ -600,6 +606,10 @@
600606
}
601607
],
602608
"keybindings": [
609+
{
610+
"command": "swift.newFile",
611+
"key": "Alt+S Alt+N"
612+
},
603613
{
604614
"command": "swift.insertFunctionComment",
605615
"key": "Alt+Ctrl+/",
@@ -620,6 +630,19 @@
620630
"when": "testId"
621631
}
622632
],
633+
"file/newFile": [
634+
{
635+
"command": "swift.newFile",
636+
"group": "file"
637+
}
638+
],
639+
"explorer/context": [
640+
{
641+
"command": "swift.newFile",
642+
"group": "swift",
643+
"when": "swift.isActivated"
644+
}
645+
],
623646
"commandPalette": [
624647
{
625648
"command": "swift.createNewProject",

src/commands.ts

+2
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { captureDiagnostics } from "./commands/captureDiagnostics";
3535
import { reindexProjectRequest } from "./sourcekit-lsp/lspExtensions";
3636
import { TestRunner, TestRunnerTestRunState } from "./TestExplorer/TestRunner";
3737
import { TestKind } from "./TestExplorer/TestKind";
38+
import { newSwiftFile } from "./commands/newFile";
3839

3940
/**
4041
* References:
@@ -884,6 +885,7 @@ export function registerToolchainCommands(
884885
*/
885886
export function register(ctx: WorkspaceContext): vscode.Disposable[] {
886887
return [
888+
vscode.commands.registerCommand("swift.newFile", uri => newSwiftFile(uri)),
887889
vscode.commands.registerCommand("swift.resolveDependencies", () =>
888890
resolveDependencies(ctx)
889891
),

src/commands/newFile.ts

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2021-2024 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import * as fs from "fs/promises";
16+
import * as path from "path";
17+
import * as vscode from "vscode";
18+
import { fileExists, pathExists } from "../utilities/filesystem";
19+
20+
const extension = "swift";
21+
const defaultFileName = `Untitled.${extension}`;
22+
23+
export async function newSwiftFile(
24+
uri?: vscode.Uri,
25+
isDirectory: (uri: vscode.Uri) => Promise<boolean> = async uri => {
26+
return (await vscode.workspace.fs.stat(uri)).type === vscode.FileType.Directory;
27+
}
28+
) {
29+
if (uri) {
30+
// Attempt to create the file at the given directory.
31+
const dir = (await isDirectory(uri)) ? uri.fsPath : path.dirname(uri.fsPath);
32+
const defaultName = path.join(dir, defaultFileName);
33+
const givenPath = await vscode.window.showInputBox({
34+
value: defaultName,
35+
valueSelection: [dir.length + 1, defaultName.length - extension.length - 1],
36+
prompt: "Enter a file path to be created",
37+
validateInput: validatePathValid,
38+
});
39+
if (!givenPath) {
40+
return;
41+
}
42+
const targetUri = vscode.Uri.file(givenPath);
43+
try {
44+
await fs.writeFile(targetUri.fsPath, "", "utf-8");
45+
const document = await vscode.workspace.openTextDocument(targetUri);
46+
await vscode.languages.setTextDocumentLanguage(document, "swift");
47+
await vscode.window.showTextDocument(document);
48+
} catch (err) {
49+
vscode.window.showErrorMessage(`Failed to create ${targetUri.fsPath}`);
50+
}
51+
} else {
52+
// If no path is supplied then open an untitled editor w/ Swift language type
53+
const document = await vscode.workspace.openTextDocument({
54+
language: "swift",
55+
});
56+
await vscode.window.showTextDocument(document);
57+
}
58+
}
59+
60+
async function validatePathValid(input: string) {
61+
const inputPath = vscode.Uri.file(input).fsPath;
62+
const filePathExists = await fileExists(inputPath);
63+
if (filePathExists) {
64+
return `Supplied path ${inputPath} already exists`;
65+
}
66+
67+
const inputDir = path.dirname(inputPath);
68+
const dirExists = await pathExists(inputDir);
69+
if (!dirExists) {
70+
return `Supplied directory ${inputDir} doesn't exist`;
71+
}
72+
73+
return undefined;
74+
}

test/suite/commands/NewFile.test.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the VS Code Swift open source project
4+
//
5+
// Copyright (c) 2023 the VS Code Swift project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
// import * as assert from "assert";
16+
import * as vscode from "vscode";
17+
import * as assert from "assert";
18+
import * as path from "path";
19+
import { anything, deepEqual, verify, when } from "ts-mockito";
20+
import { newSwiftFile } from "../../../src/commands/newFile";
21+
import { mockNamespace } from "../../unit-tests/MockUtils";
22+
import { TemporaryFolder } from "../../../src/utilities/tempFolder";
23+
import { fileExists } from "../../../src/utilities/filesystem";
24+
25+
suite("NewFile Command Test Suite", () => {
26+
const workspaceMock = mockNamespace(vscode, "workspace");
27+
const windowMock = mockNamespace(vscode, "window");
28+
const languagesMock = mockNamespace(vscode, "languages");
29+
30+
test("Creates a blank file if no URI is provided", async () => {
31+
await newSwiftFile(undefined);
32+
33+
verify(workspaceMock.openTextDocument(deepEqual({ language: "swift" }))).once();
34+
verify(windowMock.showTextDocument(anything())).once();
35+
});
36+
37+
test("Creates file at provided directory", async () => {
38+
const folder = await TemporaryFolder.create();
39+
const file = path.join(folder.path, "MyFile.swift");
40+
41+
when(windowMock.showInputBox(anything())).thenReturn(Promise.resolve(file));
42+
43+
await newSwiftFile(vscode.Uri.file(folder.path), () => Promise.resolve(true));
44+
45+
assert.ok(await fileExists(file));
46+
47+
verify(workspaceMock.openTextDocument(anything())).once();
48+
verify(languagesMock.setTextDocumentLanguage(anything(), "swift")).once();
49+
verify(windowMock.showTextDocument(anything())).once();
50+
});
51+
});

0 commit comments

Comments
 (0)