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
52 changes: 46 additions & 6 deletions src/app/settings/ClaudianSettingsStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import {
resolveEnvironmentSnippetScope,
} from '../../core/providers/providerEnvironment';
import type { VaultFileAdapter } from '../../core/storage/VaultFileAdapter';
import type {
ClaudianSettings,
EnvironmentScope,
EnvSnippet,
HiddenProviderCommands,
ProviderConfigMap,
import {
CHAT_VIEW_PLACEMENTS,
type ChatViewPlacement,
type ClaudianSettings,
type EnvironmentScope,
type EnvSnippet,
type HiddenProviderCommands,
type ProviderConfigMap,
} from '../../core/types/settings';
import {
getClaudeProviderSettings,
Expand Down Expand Up @@ -83,11 +85,43 @@ function stripLegacyFields(settings: Record<string, unknown>): Record<string, un
environmentVariables: _environmentVariables,
lastEnvHash: _lastEnvHash,
lastCodexEnvHash: _lastCodexEnvHash,
openInMainTab: _openInMainTab,
...cleaned
} = settings;
return cleaned;
}

function isChatViewPlacement(value: unknown): value is ChatViewPlacement {
return typeof value === 'string'
&& (CHAT_VIEW_PLACEMENTS as readonly string[]).includes(value);
}

function normalizeChatViewPlacement(
value: unknown,
legacyOpenInMainTab: unknown,
): ChatViewPlacement {
if (isChatViewPlacement(value)) {
return value;
}

if (typeof legacyOpenInMainTab === 'boolean') {
return legacyOpenInMainTab ? 'main-tab' : 'right-sidebar';
}

return DEFAULT_CLAUDIAN_SETTINGS.chatViewPlacement;
}

function shouldPersistChatViewPlacementMigration(
stored: Record<string, unknown>,
normalized: ChatViewPlacement,
): boolean {
return 'openInMainTab' in stored
|| (
'chatViewPlacement' in stored
&& stored.chatViewPlacement !== normalized
);
}

function normalizeProviderConfigs(value: unknown): ProviderConfigMap {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return {};
Expand Down Expand Up @@ -196,6 +230,10 @@ export class ClaudianSettingsStorage {
);
const envSnippets = normalizeEnvSnippets(stored.envSnippets);
const providerConfigs = normalizeProviderConfigs(stored.providerConfigs);
const chatViewPlacement = normalizeChatViewPlacement(
stored.chatViewPlacement,
stored.openInMainTab,
);
const legacyProviderSettings = {
...stored,
hiddenProviderCommands,
Expand All @@ -211,6 +249,7 @@ export class ClaudianSettingsStorage {
envSnippets,
hiddenProviderCommands,
providerConfigs,
chatViewPlacement,
};

const merged = {
Expand Down Expand Up @@ -239,6 +278,7 @@ export class ClaudianSettingsStorage {
|| 'allowedExportPaths' in stored
|| 'enableBlocklist' in stored
|| 'blockedCommands' in stored
|| shouldPersistChatViewPlacementMigration(stored, chatViewPlacement)
|| JSON.stringify(envSnippets) !== JSON.stringify(stored.envSnippets ?? [])
)
) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/settings/defaultSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const DEFAULT_CLAUDIAN_SETTINGS: ClaudianSettings = {
maxTabs: 3,
tabBarPosition: 'input',
enableAutoScroll: true,
openInMainTab: false,
chatViewPlacement: 'right-sidebar',

hiddenProviderCommands: getDefaultHiddenProviderCommands(),
};
11 changes: 10 additions & 1 deletion src/core/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ export interface KeyboardNavigationSettings {
/** Tab bar position setting. */
export type TabBarPosition = 'input' | 'header';

export const CHAT_VIEW_PLACEMENTS = [
'right-sidebar',
'left-sidebar',
'main-tab',
] as const;

/** Workspace location used when opening the Claudian chat view. */
export type ChatViewPlacement = typeof CHAT_VIEW_PLACEMENTS[number];

/** Result from instruction refinement agent query. */
export interface InstructionRefineResult {
success: boolean;
Expand Down Expand Up @@ -132,7 +141,7 @@ export interface ClaudianSettings {
maxTabs: number;
tabBarPosition: TabBarPosition;
enableAutoScroll: boolean;
openInMainTab: boolean;
chatViewPlacement: ChatViewPlacement;

// Provider command visibility
hiddenProviderCommands: HiddenProviderCommands;
Expand Down
20 changes: 12 additions & 8 deletions src/features/settings/ClaudianSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { ProviderRegistry } from '../../core/providers/ProviderRegistry';
import { ProviderWorkspaceRegistry } from '../../core/providers/ProviderWorkspaceRegistry';
import type { ProviderId } from '../../core/providers/types';
import type { ChatViewPlacement } from '../../core/types/settings';
import { getAvailableLocales, getLocaleDisplayName, setLocale, t } from '../../i18n/i18n';
import type { Locale, TranslationKey } from '../../i18n/types';
import type ClaudianPlugin from '../../main';
Expand Down Expand Up @@ -231,16 +232,19 @@ export class ClaudianSettingTab extends PluginSettingTab {
});

new Setting(container)
.setName(t('settings.openInMainTab.name'))
.setDesc(t('settings.openInMainTab.desc'))
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.openInMainTab)
.setName(t('settings.chatViewPlacement.name'))
.setDesc(t('settings.chatViewPlacement.desc'))
.addDropdown((dropdown) => {
dropdown
.addOption('right-sidebar', t('settings.chatViewPlacement.rightSidebar'))
.addOption('left-sidebar', t('settings.chatViewPlacement.leftSidebar'))
.addOption('main-tab', t('settings.chatViewPlacement.mainTab'))
.setValue(this.plugin.settings.chatViewPlacement)
.onChange(async (value) => {
this.plugin.settings.openInMainTab = value;
this.plugin.settings.chatViewPlacement = value as ChatViewPlacement;
await this.plugin.saveSettings();
})
);
});
});

new Setting(container)
.setName(t('settings.enableAutoScroll.name'))
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Automatisches Scrollen während Streaming",
"desc": "Automatisch nach unten scrollen, während Claude Antworten streamt. Deaktivieren, um oben zu bleiben und von Anfang an zu lesen."
},
"openInMainTab": {
"name": "Im Haupteditorbereich öffnen",
"desc": "Chat-Panel als Haupttab im zentralen Editorbereich statt in der rechten Seitenleiste öffnen"
"chatViewPlacement": {
"name": "Claudian öffnen in",
"desc": "Wählen Sie, wo das Chat-Panel beim Erstellen geöffnet wird",
"rightSidebar": "Rechte Seitenleiste",
"leftSidebar": "Linke Seitenleiste",
"mainTab": "Haupteditor-Tab"
},
"cliPath": {
"name": "Claude CLI-Pfad",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Auto-scroll during streaming",
"desc": "Automatically scroll to the bottom as Claude streams responses. Disable to stay at the top and read from the beginning."
},
"openInMainTab": {
"name": "Open in main editor area",
"desc": "Open chat panel as a main tab in the center editor area instead of the right sidebar"
"chatViewPlacement": {
"name": "Open Claudian in",
"desc": "Choose where the chat panel opens when it is created",
"rightSidebar": "Right sidebar",
"leftSidebar": "Left sidebar",
"mainTab": "Main editor tab"
},
"cliPath": {
"name": "Claude CLI path",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Desplazamiento automático durante streaming",
"desc": "Desplazarse automáticamente hacia abajo mientras Claude transmite respuestas. Desactivar para quedarse arriba y leer desde el principio."
},
"openInMainTab": {
"name": "Abrir en área de editor principal",
"desc": "Abrir el panel de chat como una pestaña principal en el área de editor central en lugar de la barra lateral derecha"
"chatViewPlacement": {
"name": "Abrir Claudian en",
"desc": "Elige dónde se abre el panel de chat cuando se crea",
"rightSidebar": "Barra lateral derecha",
"leftSidebar": "Barra lateral izquierda",
"mainTab": "Pestaña del editor principal"
},
"cliPath": {
"name": "Ruta CLI Claude",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Défilement automatique pendant le streaming",
"desc": "Défiler automatiquement vers le bas pendant que Claude diffuse les réponses. Désactiver pour rester en haut et lire depuis le début."
},
"openInMainTab": {
"name": "Ouvrir dans la zone d'éditeur principale",
"desc": "Ouvrir le panneau de chat comme un onglet principal dans la zone d'éditeur centrale au lieu de la barre latérale droite"
"chatViewPlacement": {
"name": "Ouvrir Claudian dans",
"desc": "Choisissez où le panneau de chat s'ouvre lors de sa création",
"rightSidebar": "Barre latérale droite",
"leftSidebar": "Barre latérale gauche",
"mainTab": "Onglet principal de l'éditeur"
},
"cliPath": {
"name": "Chemin CLI Claude",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "ストリーミング中の自動スクロール",
"desc": "Claudeが応答をストリーミングしている間、自動的に下にスクロールします。無効にすると上部に留まり、最初から読むことができます。"
},
"openInMainTab": {
"name": "メインエディタ領域で開く",
"desc": "チャットパネルを右サイドバーではなく、中央エディタ領域のメインタブとして開きます"
"chatViewPlacement": {
"name": "Claudian を開く場所",
"desc": "チャットパネルを作成するときに開く場所を選択します",
"rightSidebar": "右サイドバー",
"leftSidebar": "左サイドバー",
"mainTab": "メインエディタタブ"
},
"cliPath": {
"name": "Claude CLI パス",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "스트리밍 중 자동 스크롤",
"desc": "Claude가 응답을 스트리밍하는 동안 자동으로 아래로 스크롤합니다. 비활성화하면 상단에 머물러 처음부터 읽을 수 있습니다."
},
"openInMainTab": {
"name": "메인 편집기 영역에서 열기",
"desc": "채팅 패널을 오른쪽 사이드바가 아닌 중앙 편집기 영역의 메인 탭으로 엽니다"
"chatViewPlacement": {
"name": "Claudian 열 위치",
"desc": "채팅 패널이 생성될 때 열릴 위치를 선택합니다",
"rightSidebar": "오른쪽 사이드바",
"leftSidebar": "왼쪽 사이드바",
"mainTab": "메인 편집기 탭"
},
"cliPath": {
"name": "Claude CLI 경로",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Rolagem automática durante streaming",
"desc": "Rolar automaticamente para baixo enquanto o Claude transmite respostas. Desativar para ficar no topo e ler desde o início."
},
"openInMainTab": {
"name": "Abrir na área do editor principal",
"desc": "Abrir o painel de chat como uma aba principal na área do editor central em vez da barra lateral direita"
"chatViewPlacement": {
"name": "Abrir Claudian em",
"desc": "Escolha onde o painel de chat abre quando é criado",
"rightSidebar": "Barra lateral direita",
"leftSidebar": "Barra lateral esquerda",
"mainTab": "Aba do editor principal"
},
"cliPath": {
"name": "Caminho CLI Claude",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "Автопрокрутка во время потоковой передачи",
"desc": "Автоматически прокручивать вниз, пока Claude передает ответы. Отключите, чтобы оставаться наверху и читать с начала."
},
"openInMainTab": {
"name": "Открывать в основной области редактора",
"desc": "Открывать панель чата в виде основной вкладки в центральной области редактора вместо правой боковой панели"
"chatViewPlacement": {
"name": "Открывать Claudian в",
"desc": "Выберите, где открывается панель чата при создании",
"rightSidebar": "Правая боковая панель",
"leftSidebar": "Левая боковая панель",
"mainTab": "Основная вкладка редактора"
},
"cliPath": {
"name": "Путь к CLI Claude",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "流式传输时自动滚动",
"desc": "在 Claude 流式传输响应时自动滚动到底部。禁用后将停留在顶部,从头开始阅读。"
},
"openInMainTab": {
"name": "在主编辑器区域打开",
"desc": "在中央编辑器区域以主标签页形式打开聊天面板,而不是在右侧边栏"
"chatViewPlacement": {
"name": "Claudian 打开位置",
"desc": "选择新建聊天面板时的打开位置",
"rightSidebar": "右侧边栏",
"leftSidebar": "左侧边栏",
"mainTab": "主编辑器标签页"
},
"cliPath": {
"name": "Claude CLI 路径",
Expand Down
9 changes: 6 additions & 3 deletions src/i18n/locales/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,12 @@
"name": "串流傳輸時自動捲動",
"desc": "在 Claude 串流傳輸回應時自動捲動到底部。停用後將停留在頂部,從頭開始閱讀。"
},
"openInMainTab": {
"name": "在主編輯器區域開啟",
"desc": "在中央編輯器區域以主分頁形式開啟聊天面板,而不是在右側邊欄"
"chatViewPlacement": {
"name": "Claudian 開啟位置",
"desc": "選擇新建聊天面板時的開啟位置",
"rightSidebar": "右側邊欄",
"leftSidebar": "左側邊欄",
"mainTab": "主編輯器分頁"
},
"cliPath": {
"name": "Claude CLI 路徑",
Expand Down
7 changes: 5 additions & 2 deletions src/i18n/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,11 @@ export type TranslationKey =
| 'settings.tabBarPosition.header'
| 'settings.enableAutoScroll.name'
| 'settings.enableAutoScroll.desc'
| 'settings.openInMainTab.name'
| 'settings.openInMainTab.desc'
| 'settings.chatViewPlacement.name'
| 'settings.chatViewPlacement.desc'
| 'settings.chatViewPlacement.rightSidebar'
| 'settings.chatViewPlacement.leftSidebar'
| 'settings.chatViewPlacement.mainTab'
| 'settings.cliPath.name'
| 'settings.cliPath.desc'
| 'settings.cliPath.descWindows'
Expand Down
20 changes: 15 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ patchSetMaxListenersForElectron();

import './providers';

import type { Editor } from 'obsidian';
import type { Editor, WorkspaceLeaf } from 'obsidian';
import { MarkdownView, Notice, Plugin } from 'obsidian';

import { DEFAULT_CLAUDIAN_SETTINGS } from './app/settings/defaultSettings';
Expand All @@ -29,7 +29,7 @@ import type {
import {
VIEW_TYPE_CLAUDIAN,
} from './core/types';
import type { EnvironmentScope } from './core/types/settings';
import type { ChatViewPlacement, EnvironmentScope } from './core/types/settings';
import { ClaudianView } from './features/chat/ClaudianView';
import { type InlineEditContext, InlineEditModal } from './features/inline-edit/ui/InlineEditModal';
import { ClaudianSettingTab } from './features/settings/ClaudianSettings';
Expand Down Expand Up @@ -188,9 +188,7 @@ export default class ClaudianPlugin extends Plugin {
let leaf = workspace.getLeavesOfType(VIEW_TYPE_CLAUDIAN)[0];

if (!leaf) {
const newLeaf = this.settings.openInMainTab
? workspace.getLeaf('tab')
: workspace.getRightLeaf(false);
const newLeaf = this.getLeafForPlacement(this.settings.chatViewPlacement);
if (newLeaf) {
await newLeaf.setViewState({
type: VIEW_TYPE_CLAUDIAN,
Expand All @@ -205,6 +203,18 @@ export default class ClaudianPlugin extends Plugin {
}
}

private getLeafForPlacement(placement: ChatViewPlacement): WorkspaceLeaf | null {
const { workspace } = this.app;
switch (placement) {
case 'main-tab':
return workspace.getLeaf('tab');
case 'left-sidebar':
return workspace.getLeftLeaf(false);
case 'right-sidebar':
return workspace.getRightLeaf(false);
}
}

private canCreateNewTab(): boolean {
const view = this.getView();
const tabManager = view?.getTabManager();
Expand Down
6 changes: 6 additions & 0 deletions tests/__mocks__/obsidian.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ export class App {
getRightLeaf: jest.fn().mockReturnValue({
setViewState: jest.fn().mockResolvedValue(undefined),
}),
getLeftLeaf: jest.fn().mockReturnValue({
setViewState: jest.fn().mockResolvedValue(undefined),
}),
getLeaf: jest.fn().mockReturnValue({
setViewState: jest.fn().mockResolvedValue(undefined),
}),
revealLeaf: jest.fn(),
};
}
Expand Down
Loading
Loading