Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,26 @@
"type": "boolean",
"default": false,
"description": "Automatically reveal a new Pi chat tab when the extension activates. Previously open Pi session tabs are always restored."
},
"pi-on-code.newChatPanelLocation": {
"type": "string",
"default": "panel",
"enum": ["panel", "splitPanel"],
"enumDescriptions": [
"Open brand-new Pi chats as tabs in the active editor group without splitting the editor area.",
"Open brand-new Pi chats in a new editor group split to the right of the active group."
],
"description": "Where brand-new Pi chats open in the editor."
},
"pi-on-code.chatPanelLocation": {
"type": "string",
"default": "splitPanel",
"enum": ["panel", "splitPanel"],
"enumDescriptions": [
"Open resumed/forked Pi chats as tabs in the active editor group without splitting the editor area.",
"Open resumed/forked Pi chats in a new editor group split to the right of the active group (historical default)."
],
"description": "Where resumed and forked Pi chats open in the editor."
}
}
},
Expand Down
17 changes: 17 additions & 0 deletions src/chat-panel-location.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Where a newly opened Pi chat panel is placed inside the VS Code window.
*
* - `"panel"` — the chat opens as a tab in the active editor group; the editor
* area is never split for it.
* - `"splitPanel"` — the chat opens in a new editor group split to the right
* of the active group (historical default behavior).
*/
export type ChatPanelLocation = "panel" | "splitPanel";

export const DEFAULT_CHAT_PANEL_LOCATION: ChatPanelLocation = "splitPanel";

export function parseChatPanelLocation(raw: unknown): ChatPanelLocation {
return raw === "panel" || raw === "splitPanel"
? raw
: DEFAULT_CHAT_PANEL_LOCATION;
}
15 changes: 12 additions & 3 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "./session-sidebar-provider.js";
import { initLogger, piLog, piWarn } from "./logger.js";
import { getWorkspaceCwd, getWorkspaceFolders, getWorkspaceRoot, getWorkspaceUri } from "./workspace-context.js";
import { parseChatPanelLocation } from "./chat-panel-location.js";
import { registerPhase3Commands } from "./phase3-commands.js";
import { registerPhase4Commands } from "./phase4-commands.js";
import type { SessionSummary } from "./types.js";
Expand Down Expand Up @@ -1127,7 +1128,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
): Promise<void> {
const newSw = createSessionWindow(context, { path: forkedPath }, false, cwd);
setActiveSession(newSw);
void newSw.webviewPanel.show();
void newSw.webviewPanel.show(chatShowColumn("chatPanelLocation"));
sessionTreeProvider?.refresh();

await initSessionInBackground(context, newSw, { openPath: forkedPath });
Expand Down Expand Up @@ -1373,7 +1374,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
title: summary?.name ?? summary?.firstMessage,
}, false, summary?.cwd ?? getWorkspaceCwd());
setActiveSession(sw);
await sw.webviewPanel.show();
await sw.webviewPanel.show(chatShowColumn("chatPanelLocation"));
setSessionResultUnread(sw, false);
sessionTreeProvider?.refresh();
void initSessionInBackground(context, sw, { openPath: resolved });
Expand Down Expand Up @@ -1946,6 +1947,14 @@ async function doUpdatePackage(source: string): Promise<void> {

// ── Add a new session window ──────────────────────────

/** Column for a chat opened under the given location setting key. */
function chatShowColumn(key: string): vscode.ViewColumn | undefined {
const location = parseChatPanelLocation(
vscode.workspace.getConfiguration("pi-on-code").get(key),
);
return location === "panel" ? vscode.ViewColumn.Active : undefined;
}

function addSession(context: vscode.ExtensionContext, cwd = getWorkspaceCwd()): void {
const existingDraft = findReusableDraft(sessions.filter((session) => session.cwd === cwd));
if (existingDraft) {
Expand All @@ -1962,7 +1971,7 @@ function addSession(context: vscode.ExtensionContext, cwd = getWorkspaceCwd()):
void saveOpenSessionPaths();
};
setActiveSession(sw);
void sw.webviewPanel.show();
void sw.webviewPanel.show(chatShowColumn("newChatPanelLocation"));
void initSessionInBackground(context, sw, { fresh: true });
}

Expand Down
55 changes: 55 additions & 0 deletions src/test/chat-panel-location.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as assert from "node:assert";
import { readFileSync } from "node:fs";
import {
DEFAULT_CHAT_PANEL_LOCATION,
parseChatPanelLocation,
} from "../chat-panel-location.js";

suite("Chat panel location", () => {
test("defaults to splitPanel for missing or invalid values", () => {
assert.strictEqual(DEFAULT_CHAT_PANEL_LOCATION, "splitPanel");
assert.strictEqual(parseChatPanelLocation(undefined), "splitPanel");
assert.strictEqual(parseChatPanelLocation("sidebar-left"), "splitPanel");
assert.strictEqual(parseChatPanelLocation(42), "splitPanel");
});

test("accepts the two documented locations", () => {
assert.strictEqual(parseChatPanelLocation("panel"), "panel");
assert.strictEqual(parseChatPanelLocation("splitPanel"), "splitPanel");
});

test("publishes new (panel) and resume/fork (splitPanel) properties", () => {
const manifest = JSON.parse(
readFileSync(new URL("../../package.json", import.meta.url), "utf8"),
) as {
contributes: {
configuration: {
properties: Record<string, {
default: unknown;
enum?: unknown[];
enumDescriptions?: unknown[];
}>;
};
};
};
const newProp = manifest.contributes.configuration.properties["pi-on-code.newChatPanelLocation"];
const resumeProp = manifest.contributes.configuration.properties["pi-on-code.chatPanelLocation"];
assert.ok(newProp, "pi-on-code.newChatPanelLocation property is missing");
assert.strictEqual(newProp.default, "panel");
assert.deepStrictEqual(newProp.enum, ["panel", "splitPanel"]);
assert.ok(resumeProp, "pi-on-code.chatPanelLocation property is missing");
assert.strictEqual(resumeProp.default, "splitPanel");
assert.deepStrictEqual(resumeProp.enum, ["panel", "splitPanel"]);
});

test("routes new chats through newChatPanelLocation and resumes/forks through chatPanelLocation", () => {
const extension = readFileSync(
new URL("../../src/extension.ts", import.meta.url),
"utf8",
);
assert.match(extension, /function chatShowColumn\(key: string\)/);
assert.match(extension, /sw\.webviewPanel\.show\(chatShowColumn\("newChatPanelLocation"\)\)/);
assert.match(extension, /newSw\.webviewPanel\.show\(chatShowColumn\("chatPanelLocation"\)\)/);
assert.match(extension, /await sw\.webviewPanel\.show\(chatShowColumn\("chatPanelLocation"\)\)/);
});
});
4 changes: 2 additions & 2 deletions src/webview-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class PiWebviewPanel {
private _onActivateCb: (() => void) | null = null;
get isActive(): boolean { return this.panel?.active === true; }

async show(): Promise<void> {
async show(column?: vscode.ViewColumn): Promise<void> {
if (this.panel) {
this.panel.reveal();
// Retained webviews can keep the compositor surface from the size they
Expand All @@ -115,7 +115,7 @@ export class PiWebviewPanel {
this.panel = vscode.window.createWebviewPanel(
"pi-chat-" + randomId,
"Pi on Code",
vscode.ViewColumn.Two,
column ?? vscode.ViewColumn.Two,
{
enableScripts: true,
retainContextWhenHidden: true,
Expand Down
Loading