diff --git a/internal/server/web/static/app.css b/internal/server/web/static/app.css
index 49b5995..1a0bc4f 100644
--- a/internal/server/web/static/app.css
+++ b/internal/server/web/static/app.css
@@ -333,7 +333,11 @@ body.multi .tabs, body.multi-hint .tabs { max-height:48px; opacity:1; padding-to
.tab.on { background:var(--panel); color:var(--text); }
.tab.enter, .tab.leave { max-width:0; opacity:0; padding:0; }
.tab.dragging { opacity:.3; }
-.tab-title { flex:1; overflow:hidden; text-overflow:ellipsis; min-width:0; }
+.tab-title { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
+/* What a tab holds, just before its name. */
+.tab-kinds { display:inline-flex; gap:3px; flex:none; color:var(--muted); opacity:.75; }
+.tab.on .tab-kinds { color:var(--text); opacity:.6; }
+.tab-kinds svg { display:block; }
.tab-edit { width:auto; min-width:60px; max-width:150px; height:24px; padding:2px 6px; font-size:13px; }
.tab-x, .pane-x { flex:none; width:20px; height:20px; padding:0; display:inline-flex; align-items:center; justify-content:center;
background:transparent; border:0; border-radius:5px; color:var(--muted); opacity:.6; transition:opacity .15s, background .15s, color .15s; }
diff --git a/internal/server/web/static/workspace.js b/internal/server/web/static/workspace.js
index 3129952..797eb49 100644
--- a/internal/server/web/static/workspace.js
+++ b/internal/server/web/static/workspace.js
@@ -22,6 +22,10 @@
const tabs = [];
let active = null, activePane = null;
const tabOf = s => tabs.find(t => t.panes.includes(s));
+ const KIND_ICON = {
+ term: '',
+ files: ''
+ };
function makeTab(panes, index) {
const t = { title: null, panes: panes, layout: panes.length === 1 ? '1' : 'h', c: 0.5, r: 0.5, gutters: [] };
@@ -31,7 +35,7 @@
panes.forEach(s => t.view.appendChild(s.el));
t.el = document.createElement('div');
t.el.className = 'tab enter';
- t.el.innerHTML = '' +
+ t.el.innerHTML = '' +
'';
t.el.addEventListener('click', ev => { if (!ev.target.closest('.tab-x, input') && !dragJustEnded) activate(t); });
t.el.querySelector('.tab-x').onclick = ev => { ev.stopPropagation(); closeTab(t); };
@@ -64,7 +68,8 @@
function fitTabs() {
ruler.font = W.font + 'px ' + getComputedStyle(document.body).fontFamily;
const live = tabs;
- const want = live.map(t => Math.min(W.max, Math.max(W.base, Math.ceil(ruler.measureText(tabTitle(t)).width) + CHROME + (t.panes.some(p => p.ssh) ? 46 : 0))));
+ const want = live.map(t => Math.min(W.max, Math.max(W.base, Math.ceil(ruler.measureText(tabTitle(t)).width) + CHROME + (t.panes.some(p => p.ssh) ? 46 : 0) +
+ 18 * new Set(t.panes.map(p => /files$/.test(p.kind))).size)));
const strip = $('tabs');
const room = strip.clientWidth - parseFloat(getComputedStyle(strip).paddingLeft) * 2 - addBtn.offsetWidth - 4 * (live.length + 1);
let cap = W.max;
@@ -83,6 +88,14 @@
function renderTab(t) {
t.el.querySelector('.tab-title').textContent = tabTitle(t);
+ // Before the name, what the tab holds: a terminal, files, or both.
+ const kinds = [...new Set(t.panes.map(p => /files$/.test(p.kind) ? 'files' : 'term'))];
+ const k = t.el.querySelector('.tab-kinds');
+ if (k.dataset.k !== kinds.join()) {
+ k.dataset.k = kinds.join();
+ k.innerHTML = kinds.map(x => KIND_ICON[x]).join('');
+ k.title = kinds.map(x => x === 'files' ? 'файлы' : 'терминал').join(' и ');
+ }
const ssh = t.panes.some(s => s.ssh);
if (ssh !== !!t.el.querySelector('.badge-ssh')) {
if (ssh) t.el.querySelector('.dot').insertAdjacentHTML('afterend', 'SSH');