session rename: double-click to rename sessions in tree

This commit is contained in:
Levi Neely 2026-08-01 15:47:23 +02:00
parent 2267c57b54
commit 63cf13b891
3 changed files with 147 additions and 20 deletions

View File

@ -146,6 +146,36 @@ ApplicationWindow {
ollie.switchAgent(parentId, agentId) ollie.switchAgent(parentId, agentId)
} }
// Rename tracking
property int renamingIndex: -1
property string renamingText: ""
function startRename(index) {
var item = sessionTreeModel.get(index)
if (item.type === "session") {
renamingIndex = index
renamingText = item.displayName
}
}
function commitRename() {
if (renamingIndex < 0) return
var item = sessionTreeModel.get(renamingIndex)
var newName = renamingText.trim()
if (newName !== "" && newName !== item.id) {
if (ollie.renameSession(item.id, newName)) {
sessionTreeModel.set(renamingIndex, { id: newName, displayName: newName })
}
}
renamingIndex = -1
renamingText = ""
}
function cancelRename() {
renamingIndex = -1
renamingText = ""
}
Connections { Connections {
target: ollie target: ollie
function onSessionsChanged() { function onSessionsChanged() {
@ -500,27 +530,95 @@ ApplicationWindow {
} }
visible: height > 0 visible: height > 0
// Click disambiguation: timer-based single vs double click
property int _clickCount: 0
Timer {
id: clickTimer
interval: 300
repeat: false
onTriggered: {
parent._clickCount = 0
if (type === "session" && index !== renamingIndex) {
toggleExpand(index)
} else if (type === "agent") {
activateAgent(parentId, agentId)
}
}
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton
hoverEnabled: false
onClicked: function() {
if (renamingIndex >= 0) return
parent._clickCount++
if (parent._clickCount === 1) {
clickTimer.start()
} else if (parent._clickCount === 2) {
clickTimer.stop()
parent._clickCount = 0
if (type === "session") {
startRename(index)
}
}
}
}
contentItem: RowLayout { contentItem: RowLayout {
anchors.fill: parent anchors.fill: parent
spacing: 0 spacing: 0
// Indent + expand/collapse indicator // Indent + expand/collapse indicator
Label { Item {
Layout.preferredWidth: (level || 0) * 16 + 16 Layout.preferredWidth: (level || 0) * 16 + 16
text: type === "session" ? (expanded ? "▼" : "▶") : "" Layout.preferredHeight: 28
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.7 : 10
color: theme.textColor Label {
opacity: 0.6 anchors.centerIn: parent
verticalAlignment: Text.AlignVCenter text: type === "session" ? (expanded ? "▼" : "▶") : ""
font.pixelSize: theme.fontSize > 0 ? theme.fontSize * 0.7 : 10
color: theme.textColor
opacity: 0.6
}
} }
// Name // Name area
Label { Item {
Layout.fillWidth: true Layout.fillWidth: true
text: displayName || "" Layout.preferredHeight: 28
color: theme.textColor
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize TextField {
verticalAlignment: Text.AlignVCenter anchors.fill: parent
text: (index === renamingIndex) ? renamingText : ""
visible: index === renamingIndex
verticalAlignment: Text.AlignVCenter
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
color: theme.textColor
background: Rectangle {
color: theme.baseColor
border.color: theme.highlightColor
radius: 2
}
Keys.onReturnPressed: commitRename()
Keys.onEscapePressed: cancelRename()
onAccepted: commitRename()
onVisibleChanged: {
if (visible) {
forceActiveFocus()
selectAll()
}
}
}
Label {
anchors.fill: parent
text: displayName || ""
color: theme.textColor
font.pointSize: theme.fontSize > 0 ? theme.fontSize : Qt.application.font.pointSize
verticalAlignment: Text.AlignVCenter
visible: index !== renamingIndex
}
} }
// State dot // State dot
@ -538,14 +636,6 @@ ApplicationWindow {
} }
} }
onClicked: {
if (type === "session") {
toggleExpand(index)
} else if (type === "agent") {
activateAgent(parentId, agentId)
}
}
highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId) highlighted: (type === "agent" && agentId === ollie.activeAgentId && parentId === ollie.activeSessionId)
function parentSessionExpanded(pid) { function parentSessionExpanded(pid) {

View File

@ -271,6 +271,42 @@ bool Ollie9pClient::createSession(const QString &cwd, const QString &name, const
return true; return true;
} }
bool Ollie9pClient::renameSession(const QString &sessionId, const QString &newName)
{
if (sessionId.isEmpty() || newName.isEmpty()) return false;
// Use Twstat to rename the session directory.
// We need to open the directory and write its stat.
// 9p doesn't have a direct "mv" command, but we can use stat/rdwr
// to read the current stat, modify the name, and write it back.
// However, plan9port's 9p doesn't support wstat directly.
//
// Alternative: use our own ollie-9p which has a mv command.
QString bin = QStandardPaths::findExecutable("ollie-9p");
if (bin.isEmpty()) {
qDebug() << "renameSession: ollie-9p not found, cannot rename";
return false;
}
QProcess proc;
proc.start(bin, {"-a", serverAddr(), "mv", "session/" + sessionId, "session/" + newName});
proc.waitForFinished(5000);
if (proc.exitCode() != 0) {
qDebug() << "renameSession failed:" << QString::fromUtf8(proc.readAllStandardError()).trimmed();
return false;
}
// Update active session ID if we renamed the active session
if (m_activeSessionId == sessionId) {
m_activeSessionId = newName;
emit activeSessionIdChanged();
}
refreshSessions();
return true;
}
// --- Root data loading (lazy) --- // --- Root data loading (lazy) ---
void Ollie9pClient::ensureRootDataLoaded() void Ollie9pClient::ensureRootDataLoaded()

View File

@ -65,6 +65,7 @@ public:
Q_INVOKABLE void loadRootAgents(); Q_INVOKABLE void loadRootAgents();
Q_INVOKABLE QStringList getAvailableModels(const QString &backend) const; Q_INVOKABLE QStringList getAvailableModels(const QString &backend) const;
Q_INVOKABLE bool createSession(const QString &cwd, const QString &name, const QString &backend, const QString &model, const QString &agent, const QString &remote); Q_INVOKABLE bool createSession(const QString &cwd, const QString &name, const QString &backend, const QString &model, const QString &agent, const QString &remote);
Q_INVOKABLE bool renameSession(const QString &sessionId, const QString &newName);
signals: signals:
void sessionsChanged(); void sessionsChanged();