katashiro 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +184 -0
- package/src/commands/command-manager.ts +452 -0
- package/src/commands/index.ts +5 -0
- package/src/extension.ts +48 -0
- package/src/index.ts +36 -0
- package/src/katashiro-extension.ts +125 -0
- package/src/ui/index.ts +9 -0
- package/src/ui/output-channel-manager.ts +105 -0
- package/src/ui/status-bar-manager.ts +157 -0
- package/src/views/history-view-provider.ts +221 -0
- package/src/views/index.ts +15 -0
- package/src/views/knowledge-view-provider.ts +204 -0
- package/src/views/research-view-provider.ts +141 -0
- package/tests/mocks/vscode.ts +121 -0
- package/tests/unit/history-view-provider.test.ts +150 -0
- package/tests/unit/knowledge-view-provider.test.ts +120 -0
- package/tests/unit/output-channel-manager.test.ts +75 -0
- package/tests/unit/research-view-provider.test.ts +111 -0
- package/tests/unit/status-bar-manager.test.ts +101 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @nahisaho/katashiro-vscode-extension
|
|
3
|
+
* VS Code拡張機能
|
|
4
|
+
*
|
|
5
|
+
* @requirement REQ-VSCODE-001 ~ REQ-VSCODE-004
|
|
6
|
+
* @design DES-KATASHIRO-001 §2.3 VS Code Extension
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Extension entry points
|
|
10
|
+
export { activate, deactivate } from './extension.js';
|
|
11
|
+
|
|
12
|
+
// Extension controller
|
|
13
|
+
export { KatashiroExtension, type KatashiroConfig } from './katashiro-extension.js';
|
|
14
|
+
|
|
15
|
+
// Commands
|
|
16
|
+
export { CommandManager } from './commands/index.js';
|
|
17
|
+
|
|
18
|
+
// Views
|
|
19
|
+
export {
|
|
20
|
+
ResearchViewProvider,
|
|
21
|
+
ResearchItem,
|
|
22
|
+
KnowledgeViewProvider,
|
|
23
|
+
KnowledgeItem,
|
|
24
|
+
HistoryViewProvider,
|
|
25
|
+
HistoryItem,
|
|
26
|
+
type HistoryEntry,
|
|
27
|
+
type HistoryEntryType,
|
|
28
|
+
} from './views/index.js';
|
|
29
|
+
|
|
30
|
+
// UI
|
|
31
|
+
export {
|
|
32
|
+
OutputChannelManager,
|
|
33
|
+
StatusBarManager,
|
|
34
|
+
type LogLevel,
|
|
35
|
+
type StatusBarState,
|
|
36
|
+
} from './ui/index.js';
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KatashiroExtension - Main extension controller
|
|
3
|
+
*
|
|
4
|
+
* Manages commands, views, and integration with KATASHIRO packages
|
|
5
|
+
*
|
|
6
|
+
* @module katashiro
|
|
7
|
+
* @task TSK-070
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as vscode from 'vscode';
|
|
11
|
+
import { CommandManager } from './commands/command-manager.js';
|
|
12
|
+
import { ResearchViewProvider } from './views/research-view-provider.js';
|
|
13
|
+
import { KnowledgeViewProvider } from './views/knowledge-view-provider.js';
|
|
14
|
+
import { HistoryViewProvider } from './views/history-view-provider.js';
|
|
15
|
+
import { StatusBarManager } from './ui/status-bar-manager.js';
|
|
16
|
+
import { OutputChannelManager } from './ui/output-channel-manager.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extension configuration
|
|
20
|
+
*/
|
|
21
|
+
export interface KatashiroConfig {
|
|
22
|
+
searchEngine: 'duckduckgo' | 'google' | 'bing';
|
|
23
|
+
maxSearchResults: number;
|
|
24
|
+
outputFormat: 'markdown' | 'html' | 'json';
|
|
25
|
+
autoSaveKnowledge: boolean;
|
|
26
|
+
mcpServerPort: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* KatashiroExtension
|
|
31
|
+
*
|
|
32
|
+
* Main controller for the KATASHIRO VS Code extension
|
|
33
|
+
*/
|
|
34
|
+
export class KatashiroExtension {
|
|
35
|
+
private commandManager: CommandManager;
|
|
36
|
+
private researchViewProvider: ResearchViewProvider;
|
|
37
|
+
private knowledgeViewProvider: KnowledgeViewProvider;
|
|
38
|
+
private historyViewProvider: HistoryViewProvider;
|
|
39
|
+
private statusBarManager: StatusBarManager;
|
|
40
|
+
private outputChannelManager: OutputChannelManager;
|
|
41
|
+
|
|
42
|
+
constructor(private readonly context: vscode.ExtensionContext) {
|
|
43
|
+
// Initialize managers
|
|
44
|
+
this.outputChannelManager = new OutputChannelManager();
|
|
45
|
+
this.statusBarManager = new StatusBarManager();
|
|
46
|
+
this.commandManager = new CommandManager(
|
|
47
|
+
context,
|
|
48
|
+
this.outputChannelManager
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Initialize view providers
|
|
52
|
+
this.researchViewProvider = new ResearchViewProvider();
|
|
53
|
+
this.knowledgeViewProvider = new KnowledgeViewProvider();
|
|
54
|
+
this.historyViewProvider = new HistoryViewProvider();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Activate the extension
|
|
59
|
+
*/
|
|
60
|
+
async activate(): Promise<void> {
|
|
61
|
+
// Register commands
|
|
62
|
+
this.commandManager.registerAll();
|
|
63
|
+
|
|
64
|
+
// Register view providers
|
|
65
|
+
this.registerViewProviders();
|
|
66
|
+
|
|
67
|
+
// Initialize status bar
|
|
68
|
+
this.statusBarManager.show();
|
|
69
|
+
|
|
70
|
+
// Log activation
|
|
71
|
+
this.outputChannelManager.log('KATASHIRO extension activated');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Deactivate the extension
|
|
76
|
+
*/
|
|
77
|
+
async deactivate(): Promise<void> {
|
|
78
|
+
this.statusBarManager.dispose();
|
|
79
|
+
this.outputChannelManager.dispose();
|
|
80
|
+
this.outputChannelManager.log('KATASHIRO extension deactivated');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get current configuration
|
|
85
|
+
*/
|
|
86
|
+
getConfig(): KatashiroConfig {
|
|
87
|
+
const config = vscode.workspace.getConfiguration('katashiro');
|
|
88
|
+
return {
|
|
89
|
+
searchEngine: config.get('searchEngine', 'duckduckgo'),
|
|
90
|
+
maxSearchResults: config.get('maxSearchResults', 10),
|
|
91
|
+
outputFormat: config.get('outputFormat', 'markdown'),
|
|
92
|
+
autoSaveKnowledge: config.get('autoSaveKnowledge', true),
|
|
93
|
+
mcpServerPort: config.get('mcpServerPort', 3000),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Register view providers
|
|
99
|
+
*/
|
|
100
|
+
private registerViewProviders(): void {
|
|
101
|
+
// Research view
|
|
102
|
+
this.context.subscriptions.push(
|
|
103
|
+
vscode.window.registerTreeDataProvider(
|
|
104
|
+
'katashiro.research',
|
|
105
|
+
this.researchViewProvider
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Knowledge view
|
|
110
|
+
this.context.subscriptions.push(
|
|
111
|
+
vscode.window.registerTreeDataProvider(
|
|
112
|
+
'katashiro.knowledge',
|
|
113
|
+
this.knowledgeViewProvider
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// History view
|
|
118
|
+
this.context.subscriptions.push(
|
|
119
|
+
vscode.window.registerTreeDataProvider(
|
|
120
|
+
'katashiro.history',
|
|
121
|
+
this.historyViewProvider
|
|
122
|
+
)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OutputChannelManager - VS Code output channel management
|
|
3
|
+
*
|
|
4
|
+
* @module katashiro
|
|
5
|
+
* @task TSK-073
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as vscode from 'vscode';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Log level
|
|
12
|
+
*/
|
|
13
|
+
export type LogLevel = 'info' | 'warn' | 'error' | 'debug';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* OutputChannelManager
|
|
17
|
+
*
|
|
18
|
+
* Manages VS Code output channel for KATASHIRO
|
|
19
|
+
*/
|
|
20
|
+
export class OutputChannelManager {
|
|
21
|
+
private channel: vscode.OutputChannel;
|
|
22
|
+
private debugMode: boolean;
|
|
23
|
+
|
|
24
|
+
constructor(name = 'KATASHIRO') {
|
|
25
|
+
this.channel = vscode.window.createOutputChannel(name);
|
|
26
|
+
this.debugMode =
|
|
27
|
+
vscode.workspace.getConfiguration('katashiro').get('debug', false);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Log a message
|
|
32
|
+
*/
|
|
33
|
+
log(message: string, level: LogLevel = 'info'): void {
|
|
34
|
+
const timestamp = new Date().toISOString();
|
|
35
|
+
const prefix = this.getLevelPrefix(level);
|
|
36
|
+
this.channel.appendLine(`[${timestamp}] ${prefix} ${message}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Log info message
|
|
41
|
+
*/
|
|
42
|
+
info(message: string): void {
|
|
43
|
+
this.log(message, 'info');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Log warning message
|
|
48
|
+
*/
|
|
49
|
+
warn(message: string): void {
|
|
50
|
+
this.log(message, 'warn');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Log error message
|
|
55
|
+
*/
|
|
56
|
+
error(message: string): void {
|
|
57
|
+
this.log(message, 'error');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Log debug message (only if debug mode is enabled)
|
|
62
|
+
*/
|
|
63
|
+
debug(message: string): void {
|
|
64
|
+
if (this.debugMode) {
|
|
65
|
+
this.log(message, 'debug');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Show the output channel
|
|
71
|
+
*/
|
|
72
|
+
show(): void {
|
|
73
|
+
this.channel.show();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Clear the output channel
|
|
78
|
+
*/
|
|
79
|
+
clear(): void {
|
|
80
|
+
this.channel.clear();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Dispose the output channel
|
|
85
|
+
*/
|
|
86
|
+
dispose(): void {
|
|
87
|
+
this.channel.dispose();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get level prefix
|
|
92
|
+
*/
|
|
93
|
+
private getLevelPrefix(level: LogLevel): string {
|
|
94
|
+
switch (level) {
|
|
95
|
+
case 'info':
|
|
96
|
+
return 'INFO';
|
|
97
|
+
case 'warn':
|
|
98
|
+
return 'WARN';
|
|
99
|
+
case 'error':
|
|
100
|
+
return 'ERROR';
|
|
101
|
+
case 'debug':
|
|
102
|
+
return 'DEBUG';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatusBarManager - VS Code status bar management
|
|
3
|
+
*
|
|
4
|
+
* @module katashiro
|
|
5
|
+
* @task TSK-073
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as vscode from 'vscode';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Status bar state
|
|
12
|
+
*/
|
|
13
|
+
export type StatusBarState = 'idle' | 'working' | 'success' | 'error';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* StatusBarManager
|
|
17
|
+
*
|
|
18
|
+
* Manages VS Code status bar item for KATASHIRO
|
|
19
|
+
*/
|
|
20
|
+
export class StatusBarManager {
|
|
21
|
+
private statusBarItem: vscode.StatusBarItem;
|
|
22
|
+
private currentState: StatusBarState = 'idle';
|
|
23
|
+
private spinnerInterval: NodeJS.Timeout | null = null;
|
|
24
|
+
private spinnerFrames = ['$(sync~spin)', '$(loading~spin)'];
|
|
25
|
+
private spinnerIndex = 0;
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this.statusBarItem = vscode.window.createStatusBarItem(
|
|
29
|
+
vscode.StatusBarAlignment.Right,
|
|
30
|
+
100
|
|
31
|
+
);
|
|
32
|
+
this.statusBarItem.command = 'katashiro.webSearch';
|
|
33
|
+
this.setIdle();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Show the status bar item
|
|
38
|
+
*/
|
|
39
|
+
show(): void {
|
|
40
|
+
this.statusBarItem.show();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hide the status bar item
|
|
45
|
+
*/
|
|
46
|
+
hide(): void {
|
|
47
|
+
this.statusBarItem.hide();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Dispose the status bar item
|
|
52
|
+
*/
|
|
53
|
+
dispose(): void {
|
|
54
|
+
this.stopSpinner();
|
|
55
|
+
this.statusBarItem.dispose();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get current state
|
|
60
|
+
*/
|
|
61
|
+
getState(): StatusBarState {
|
|
62
|
+
return this.currentState;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Set idle state
|
|
67
|
+
*/
|
|
68
|
+
setIdle(message = 'KATASHIRO'): void {
|
|
69
|
+
this.stopSpinner();
|
|
70
|
+
this.currentState = 'idle';
|
|
71
|
+
this.statusBarItem.text = `$(search) ${message}`;
|
|
72
|
+
this.statusBarItem.tooltip = 'Click to search';
|
|
73
|
+
this.statusBarItem.backgroundColor = undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Set working state
|
|
78
|
+
*/
|
|
79
|
+
setWorking(message = 'Working...'): void {
|
|
80
|
+
this.currentState = 'working';
|
|
81
|
+
this.statusBarItem.tooltip = message;
|
|
82
|
+
this.startSpinner(message);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set success state
|
|
87
|
+
*/
|
|
88
|
+
setSuccess(message = 'Done', autoResetMs = 3000): void {
|
|
89
|
+
this.stopSpinner();
|
|
90
|
+
this.currentState = 'success';
|
|
91
|
+
this.statusBarItem.text = `$(check) ${message}`;
|
|
92
|
+
this.statusBarItem.tooltip = message;
|
|
93
|
+
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
94
|
+
'statusBarItem.prominentBackground'
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (autoResetMs > 0) {
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
if (this.currentState === 'success') {
|
|
100
|
+
this.setIdle();
|
|
101
|
+
}
|
|
102
|
+
}, autoResetMs);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Set error state
|
|
108
|
+
*/
|
|
109
|
+
setError(message = 'Error', autoResetMs = 5000): void {
|
|
110
|
+
this.stopSpinner();
|
|
111
|
+
this.currentState = 'error';
|
|
112
|
+
this.statusBarItem.text = `$(error) ${message}`;
|
|
113
|
+
this.statusBarItem.tooltip = message;
|
|
114
|
+
this.statusBarItem.backgroundColor = new vscode.ThemeColor(
|
|
115
|
+
'statusBarItem.errorBackground'
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
if (autoResetMs > 0) {
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
if (this.currentState === 'error') {
|
|
121
|
+
this.setIdle();
|
|
122
|
+
}
|
|
123
|
+
}, autoResetMs);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Start spinner animation
|
|
129
|
+
*/
|
|
130
|
+
private startSpinner(message: string): void {
|
|
131
|
+
this.stopSpinner();
|
|
132
|
+
this.updateSpinner(message);
|
|
133
|
+
this.spinnerInterval = setInterval(() => {
|
|
134
|
+
this.updateSpinner(message);
|
|
135
|
+
}, 500);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Update spinner frame
|
|
140
|
+
*/
|
|
141
|
+
private updateSpinner(message: string): void {
|
|
142
|
+
const frame = this.spinnerFrames[this.spinnerIndex];
|
|
143
|
+
this.statusBarItem.text = `${frame} ${message}`;
|
|
144
|
+
this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Stop spinner animation
|
|
149
|
+
*/
|
|
150
|
+
private stopSpinner(): void {
|
|
151
|
+
if (this.spinnerInterval) {
|
|
152
|
+
clearInterval(this.spinnerInterval);
|
|
153
|
+
this.spinnerInterval = null;
|
|
154
|
+
}
|
|
155
|
+
this.spinnerIndex = 0;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HistoryViewProvider - History panel tree view
|
|
3
|
+
*
|
|
4
|
+
* @module katashiro
|
|
5
|
+
* @task TSK-072
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as vscode from 'vscode';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* History entry type
|
|
12
|
+
*/
|
|
13
|
+
export type HistoryEntryType =
|
|
14
|
+
| 'search'
|
|
15
|
+
| 'analysis'
|
|
16
|
+
| 'summary'
|
|
17
|
+
| 'research'
|
|
18
|
+
| 'report';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* History entry
|
|
22
|
+
*/
|
|
23
|
+
export interface HistoryEntry {
|
|
24
|
+
id: string;
|
|
25
|
+
type: HistoryEntryType;
|
|
26
|
+
title: string;
|
|
27
|
+
timestamp: Date;
|
|
28
|
+
details?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* History item for tree view
|
|
33
|
+
*/
|
|
34
|
+
export class HistoryItem extends vscode.TreeItem {
|
|
35
|
+
constructor(
|
|
36
|
+
public readonly entry: HistoryEntry,
|
|
37
|
+
public readonly collapsibleState: vscode.TreeItemCollapsibleState
|
|
38
|
+
) {
|
|
39
|
+
super(entry.title, collapsibleState);
|
|
40
|
+
this.description = this.formatTime(entry.timestamp);
|
|
41
|
+
this.tooltip = `${entry.type}: ${entry.title}\n${entry.details || ''}`;
|
|
42
|
+
this.contextValue = entry.type;
|
|
43
|
+
|
|
44
|
+
// Set icon based on type
|
|
45
|
+
switch (entry.type) {
|
|
46
|
+
case 'search':
|
|
47
|
+
this.iconPath = new vscode.ThemeIcon('search');
|
|
48
|
+
break;
|
|
49
|
+
case 'analysis':
|
|
50
|
+
this.iconPath = new vscode.ThemeIcon('pulse');
|
|
51
|
+
break;
|
|
52
|
+
case 'summary':
|
|
53
|
+
this.iconPath = new vscode.ThemeIcon('note');
|
|
54
|
+
break;
|
|
55
|
+
case 'research':
|
|
56
|
+
this.iconPath = new vscode.ThemeIcon('book');
|
|
57
|
+
break;
|
|
58
|
+
case 'report':
|
|
59
|
+
this.iconPath = new vscode.ThemeIcon('file-text');
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Format timestamp
|
|
66
|
+
*/
|
|
67
|
+
private formatTime(date: Date): string {
|
|
68
|
+
const now = new Date();
|
|
69
|
+
const diffMs = now.getTime() - date.getTime();
|
|
70
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
71
|
+
|
|
72
|
+
if (diffMins < 1) return 'just now';
|
|
73
|
+
if (diffMins < 60) return `${diffMins}m ago`;
|
|
74
|
+
|
|
75
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
76
|
+
if (diffHours < 24) return `${diffHours}h ago`;
|
|
77
|
+
|
|
78
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
79
|
+
if (diffDays < 7) return `${diffDays}d ago`;
|
|
80
|
+
|
|
81
|
+
return date.toLocaleDateString();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* HistoryViewProvider
|
|
87
|
+
*
|
|
88
|
+
* Provides tree data for the History panel
|
|
89
|
+
*/
|
|
90
|
+
export class HistoryViewProvider
|
|
91
|
+
implements vscode.TreeDataProvider<HistoryItem>
|
|
92
|
+
{
|
|
93
|
+
private _onDidChangeTreeData = new vscode.EventEmitter<
|
|
94
|
+
HistoryItem | undefined | null | void
|
|
95
|
+
>();
|
|
96
|
+
readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
|
|
97
|
+
|
|
98
|
+
private history: HistoryEntry[] = [];
|
|
99
|
+
private maxEntries = 50;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Refresh the tree view
|
|
103
|
+
*/
|
|
104
|
+
refresh(): void {
|
|
105
|
+
this._onDidChangeTreeData.fire();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Add a history entry
|
|
110
|
+
*/
|
|
111
|
+
addEntry(
|
|
112
|
+
type: HistoryEntryType,
|
|
113
|
+
title: string,
|
|
114
|
+
details?: string
|
|
115
|
+
): HistoryEntry {
|
|
116
|
+
const entry: HistoryEntry = {
|
|
117
|
+
id: this.generateId(),
|
|
118
|
+
type,
|
|
119
|
+
title,
|
|
120
|
+
timestamp: new Date(),
|
|
121
|
+
details,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
this.history.unshift(entry);
|
|
125
|
+
|
|
126
|
+
// Limit history size
|
|
127
|
+
if (this.history.length > this.maxEntries) {
|
|
128
|
+
this.history = this.history.slice(0, this.maxEntries);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.refresh();
|
|
132
|
+
return entry;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Clear all history
|
|
137
|
+
*/
|
|
138
|
+
clear(): void {
|
|
139
|
+
this.history = [];
|
|
140
|
+
this.refresh();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get entry by ID
|
|
145
|
+
*/
|
|
146
|
+
getEntry(id: string): HistoryEntry | undefined {
|
|
147
|
+
return this.history.find((e) => e.id === id);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get all entries
|
|
152
|
+
*/
|
|
153
|
+
getAll(): HistoryEntry[] {
|
|
154
|
+
return [...this.history];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Get tree item
|
|
159
|
+
*/
|
|
160
|
+
getTreeItem(element: HistoryItem): vscode.TreeItem {
|
|
161
|
+
return element;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get children
|
|
166
|
+
*/
|
|
167
|
+
getChildren(element?: HistoryItem): Thenable<HistoryItem[]> {
|
|
168
|
+
if (element) {
|
|
169
|
+
// No children for history items
|
|
170
|
+
return Promise.resolve([]);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Root level - all history entries
|
|
174
|
+
if (this.history.length === 0) {
|
|
175
|
+
return Promise.resolve([]);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Group by date
|
|
179
|
+
const today: HistoryEntry[] = [];
|
|
180
|
+
const yesterday: HistoryEntry[] = [];
|
|
181
|
+
const thisWeek: HistoryEntry[] = [];
|
|
182
|
+
const older: HistoryEntry[] = [];
|
|
183
|
+
|
|
184
|
+
const now = new Date();
|
|
185
|
+
const todayStart = new Date(
|
|
186
|
+
now.getFullYear(),
|
|
187
|
+
now.getMonth(),
|
|
188
|
+
now.getDate()
|
|
189
|
+
);
|
|
190
|
+
const yesterdayStart = new Date(todayStart.getTime() - 86400000);
|
|
191
|
+
const weekStart = new Date(todayStart.getTime() - 7 * 86400000);
|
|
192
|
+
|
|
193
|
+
for (const entry of this.history) {
|
|
194
|
+
const time = entry.timestamp.getTime();
|
|
195
|
+
if (time >= todayStart.getTime()) {
|
|
196
|
+
today.push(entry);
|
|
197
|
+
} else if (time >= yesterdayStart.getTime()) {
|
|
198
|
+
yesterday.push(entry);
|
|
199
|
+
} else if (time >= weekStart.getTime()) {
|
|
200
|
+
thisWeek.push(entry);
|
|
201
|
+
} else {
|
|
202
|
+
older.push(entry);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Return all entries as flat list
|
|
207
|
+
return Promise.resolve(
|
|
208
|
+
this.history.map(
|
|
209
|
+
(entry) =>
|
|
210
|
+
new HistoryItem(entry, vscode.TreeItemCollapsibleState.None)
|
|
211
|
+
)
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate unique ID
|
|
217
|
+
*/
|
|
218
|
+
private generateId(): string {
|
|
219
|
+
return `history-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Views module exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { ResearchViewProvider, ResearchItem } from './research-view-provider.js';
|
|
6
|
+
export {
|
|
7
|
+
KnowledgeViewProvider,
|
|
8
|
+
KnowledgeItem,
|
|
9
|
+
} from './knowledge-view-provider.js';
|
|
10
|
+
export {
|
|
11
|
+
HistoryViewProvider,
|
|
12
|
+
HistoryItem,
|
|
13
|
+
type HistoryEntry,
|
|
14
|
+
type HistoryEntryType,
|
|
15
|
+
} from './history-view-provider.js';
|