Skip to content

Commit be51879

Browse files
committed
feat(tabs): add close left/right/other tab commands
1 parent 0006340 commit be51879

File tree

5 files changed

+147
-45
lines changed

5 files changed

+147
-45
lines changed

src/lib/commands.js

Lines changed: 99 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,54 +35,110 @@ import saveState from "./saveState";
3535
import appSettings from "./settings";
3636
import showFileInfo from "./showFileInfo";
3737

38+
function getTabCloseSelectionOptions() {
39+
return {
40+
unsavedWarning:
41+
strings["unsaved selected tabs warning"] ||
42+
"Some selected tabs are not saved. Choose what to do.",
43+
saveLabel: strings["save selected tabs"] || "Save selected tabs",
44+
closeLabel: strings["close selected tabs"] || "Close selected tabs",
45+
saveWarning:
46+
strings["save selected tabs warning"] ||
47+
"Are you sure you want to save and close the selected tabs?",
48+
closeWarning:
49+
strings["close selected tabs warning"] ||
50+
"Are you sure you want to close the selected tabs? You will lose the unsaved changes and this action cannot be reversed.",
51+
};
52+
}
53+
54+
function getTabsRelativeToActive(side) {
55+
const { files, activeFile } = editorManager;
56+
const activeIndex = files.indexOf(activeFile);
57+
58+
if (activeIndex === -1) return [];
59+
60+
switch (side) {
61+
case "left":
62+
return files.slice(0, activeIndex);
63+
case "right":
64+
return files.slice(activeIndex + 1);
65+
case "others":
66+
return files.filter((_, index) => index !== activeIndex);
67+
default:
68+
return [];
69+
}
70+
}
71+
72+
async function closeTabs(files, options = {}) {
73+
const closableFiles = files.filter((file) => file && !file.pinned);
74+
if (!closableFiles.length) return false;
75+
76+
const {
77+
unsavedWarning = strings["unsaved files warning"],
78+
saveLabel = strings["save all"],
79+
closeLabel = strings["close all"],
80+
saveWarning = strings["save all warning"],
81+
closeWarning = strings["close all warning"],
82+
} = options;
83+
84+
let save = false;
85+
const unsavedFiles = closableFiles.filter((file) => file.isUnsaved).length;
86+
if (unsavedFiles) {
87+
const confirmation = await confirm(strings["warning"], unsavedWarning);
88+
if (!confirmation) return false;
89+
90+
const option = await select(strings["select"], [
91+
["save", saveLabel],
92+
["close", closeLabel],
93+
["cancel", strings["cancel"]],
94+
]);
95+
if (option === "cancel") return false;
96+
97+
if (option === "save") {
98+
const doSave = await confirm(strings["warning"], saveWarning);
99+
if (!doSave) return false;
100+
save = true;
101+
} else {
102+
const doClose = await confirm(strings["warning"], closeWarning);
103+
if (!doClose) return false;
104+
}
105+
}
106+
107+
for (const file of [...closableFiles]) {
108+
if (save) {
109+
await file.save();
110+
}
111+
112+
await file.remove(true, { silentPinned: true });
113+
}
114+
115+
return true;
116+
}
117+
38118
export default {
39119
async "run-tests"() {
40120
await runAllTests();
41121
},
42122
async "close-all-tabs"() {
43-
const closableFiles = editorManager.files.filter((file) => !file.pinned);
44-
if (!closableFiles.length) return;
45-
46-
let save = false;
47-
const unsavedFiles = closableFiles.filter((file) => file.isUnsaved).length;
48-
if (unsavedFiles) {
49-
const confirmation = await confirm(
50-
strings["warning"],
51-
strings["unsaved files warning"],
52-
);
53-
if (!confirmation) return;
54-
const option = await select(strings["select"], [
55-
["save", strings["save all"]],
56-
["close", strings["close all"]],
57-
["cancel", strings["cancel"]],
58-
]);
59-
if (option === "cancel") return;
60-
61-
if (option === "save") {
62-
const doSave = await confirm(
63-
strings["warning"],
64-
strings["save all warning"],
65-
);
66-
if (!doSave) return;
67-
save = true;
68-
} else {
69-
const doClose = await confirm(
70-
strings["warning"],
71-
strings["close all warning"],
72-
);
73-
if (!doClose) return;
74-
}
75-
}
76-
77-
for (const file of [...closableFiles]) {
78-
if (save) {
79-
await file.save();
80-
await file.remove(true, { silentPinned: true });
81-
continue;
82-
}
83-
84-
await file.remove(true, { silentPinned: true });
85-
}
123+
await closeTabs(editorManager.files);
124+
},
125+
async "close-tabs-to-left"() {
126+
await closeTabs(
127+
getTabsRelativeToActive("left"),
128+
getTabCloseSelectionOptions(),
129+
);
130+
},
131+
async "close-tabs-to-right"() {
132+
await closeTabs(
133+
getTabsRelativeToActive("right"),
134+
getTabCloseSelectionOptions(),
135+
);
136+
},
137+
async "close-other-tabs"() {
138+
await closeTabs(
139+
getTabsRelativeToActive("others"),
140+
getTabCloseSelectionOptions(),
141+
);
86142
},
87143
async "save-all-changes"() {
88144
const doSave = await confirm(

src/lib/editorFile.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,14 +1011,15 @@ export default class EditorFile {
10111011
(file) => file.id !== this.id,
10121012
);
10131013
const { files, activeFile } = editorManager;
1014-
if (activeFile.id === this.id) {
1014+
const wasActive = activeFile?.id === this.id;
1015+
if (wasActive) {
10151016
editorManager.activeFile = null;
10161017
}
10171018
if (!files.length) {
10181019
Sidebar.hide();
10191020
editorManager.activeFile = null;
10201021
new EditorFile();
1021-
} else {
1022+
} else if (wasActive) {
10221023
files[files.length - 1].makeActive();
10231024
}
10241025
editorManager.onupdate("remove-file");

src/lib/keyBindings.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ const APP_BINDING_CONFIG = [
4343
action: "close-all-tabs",
4444
readOnly: false,
4545
},
46+
{
47+
name: "closeTabsToRight",
48+
description: "Close tabs to the right.",
49+
key: null,
50+
action: "close-tabs-to-right",
51+
readOnly: false,
52+
},
53+
{
54+
name: "closeTabsToLeft",
55+
description: "Close tabs to the left.",
56+
key: null,
57+
action: "close-tabs-to-left",
58+
readOnly: false,
59+
},
60+
{
61+
name: "closeOtherTabs",
62+
description: "Close other tabs.",
63+
key: null,
64+
action: "close-other-tabs",
65+
readOnly: false,
66+
},
4667
{
4768
name: "newFile",
4869
description: "Create new file",

src/main.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,18 @@ function createFileMenu({ top, bottom, toggler }) {
766766
? strings["unpin tab"] || "Unpin tab"
767767
: strings["pin tab"] || "Pin tab",
768768
toggle_pin_tab_icon: file.pinned ? "icon pin-off" : "icon pin",
769+
close_tabs_to_right_text:
770+
strings["close tabs to right"] ||
771+
strings["close right"] ||
772+
"Close Right",
773+
close_tabs_to_left_text:
774+
strings["close tabs to left"] ||
775+
strings["close left"] ||
776+
"Close Left",
777+
close_other_tabs_text:
778+
strings["close other tabs"] ||
779+
strings["close others"] ||
780+
"Close Others",
769781
// Use CodeMirror mode stored on EditorFile (set in setMode)
770782
file_mode: isEditorFile ? file.currentMode || "" : "",
771783
file_encoding: isEditorFile ? encoding : "",

src/views/file-menu.hbs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@
7373
<span class="text">{{toggle_pin_tab_text}}</span>
7474
<span class="{{toggle_pin_tab_icon}}"></span>
7575
</li>
76+
<li action="close-tabs-to-right">
77+
<span class="text">{{close_tabs_to_right_text}}</span>
78+
<span class="icon last_page"></span>
79+
</li>
80+
<li action="close-tabs-to-left">
81+
<span class="text">{{close_tabs_to_left_text}}</span>
82+
<span class="icon first_page"></span>
83+
</li>
84+
<li action="close-other-tabs">
85+
<span class="text">{{close_other_tabs_text}}</span>
86+
<span class="icon compare_arrows"></span>
87+
</li>
7688

7789
{{#is_editor}}
7890
<hr>

0 commit comments

Comments
 (0)