thatgfsj-code 0.2.1 → 0.2.2
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/CHANGELOG.md +131 -0
- package/DEVELOPMENT.md +286 -0
- package/README.md +131 -30
- package/dist/core/ai-engine.d.ts +17 -0
- package/dist/core/ai-engine.d.ts.map +1 -1
- package/dist/core/ai-engine.js +23 -7
- package/dist/core/ai-engine.js.map +1 -1
- package/dist/index.js +18 -27
- package/dist/index.js.map +1 -1
- package/dist/repl/input.d.ts +8 -0
- package/dist/repl/input.d.ts.map +1 -1
- package/dist/repl/input.js +12 -0
- package/dist/repl/input.js.map +1 -1
- package/dist/repl/loop.d.ts +56 -0
- package/dist/repl/loop.d.ts.map +1 -1
- package/dist/repl/loop.js +333 -4
- package/dist/repl/loop.js.map +1 -1
- package/dist/repl/output.d.ts.map +1 -1
- package/dist/repl/output.js +3 -1
- package/dist/repl/output.js.map +1 -1
- package/dist/repl/welcome.js +1 -1
- package/dist/repl/welcome.js.map +1 -1
- package/docs/API_KEY_GUIDE.md +6 -0
- package/docs/FAQ.md +25 -3
- package/package.json +35 -3
- package/.patches/0001-fix-repl-replace-readline-with-inquirer-input-for-ke.patch +0 -279
- package/.patches/0002-fix-repl-stream-AI-output-directly-to-stdout-so-term.patch +0 -564
- package/.patches/0003-fix-session-break-the-hallucination-loop-in-assistan.patch +0 -194
- package/.patches/0004-chore-release-bump-version-to-0.2.1.patch +0 -24
- package/ROADMAP.md +0 -107
- package/src/agent/core.ts +0 -179
- package/src/agent/index.ts +0 -8
- package/src/agent/intent.ts +0 -181
- package/src/agent/streaming.ts +0 -132
- package/src/core/ai-engine.ts +0 -437
- package/src/core/cli.ts +0 -171
- package/src/core/config.ts +0 -147
- package/src/core/context-compactor.ts +0 -245
- package/src/core/hooks.ts +0 -196
- package/src/core/permissions.ts +0 -308
- package/src/core/session.ts +0 -165
- package/src/core/skills.ts +0 -208
- package/src/core/state.ts +0 -120
- package/src/core/subagent.ts +0 -195
- package/src/core/system-prompt.ts +0 -163
- package/src/core/tool-registry.ts +0 -157
- package/src/core/types.ts +0 -280
- package/src/index.ts +0 -544
- package/src/mcp/client.ts +0 -330
- package/src/repl/index.ts +0 -8
- package/src/repl/input.ts +0 -139
- package/src/repl/loop.ts +0 -280
- package/src/repl/output.ts +0 -222
- package/src/repl/welcome.ts +0 -296
- package/src/tools/file.ts +0 -117
- package/src/tools/git.ts +0 -132
- package/src/tools/index.ts +0 -48
- package/src/tools/search.ts +0 -263
- package/src/tools/shell.ts +0 -181
- package/src/utils/diff-preview.ts +0 -202
- package/src/utils/index.ts +0 -8
- package/src/utils/memory.ts +0 -223
- package/src/utils/project-context.ts +0 -207
- package/tsconfig.json +0 -19
package/docs/FAQ.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# 常见问题 FAQ
|
|
2
2
|
|
|
3
|
+
> 贡献者请阅读 [`DEVELOPMENT.md`](../DEVELOPMENT.md),版本变更见
|
|
4
|
+
> [`CHANGELOG.md`](../CHANGELOG.md)。
|
|
5
|
+
|
|
3
6
|
## 目录
|
|
4
7
|
|
|
5
8
|
1. [安装问题](#1-安装问题)
|
|
6
9
|
2. [配置问题](#2-配置问题)
|
|
7
10
|
3. [使用问题](#3-使用问题)
|
|
8
|
-
4. [
|
|
11
|
+
4. [Provider 问题](#4-provider-问题)
|
|
9
12
|
5. [其他问题](#5-其他问题)
|
|
10
13
|
|
|
11
14
|
---
|
|
@@ -32,9 +35,10 @@
|
|
|
32
35
|
|
|
33
36
|
### Q: 如何配置 API Key?
|
|
34
37
|
|
|
35
|
-
**A**:
|
|
38
|
+
**A**: 三种方式任选其一:
|
|
36
39
|
1. 交互式配置(推荐):运行 `gfcode init`
|
|
37
|
-
2.
|
|
40
|
+
2. 环境变量:设置对应的环境变量(详见 [API_KEY_GUIDE.md](./API_KEY_GUIDE.md))
|
|
41
|
+
3. 手动编辑 `~/.thatgfsj/config.json`
|
|
38
42
|
|
|
39
43
|
### Q: 配置文件在哪里?
|
|
40
44
|
|
|
@@ -148,12 +152,30 @@ gfcode --version
|
|
|
148
152
|
|
|
149
153
|
### Q: 如何更新到最新版本?
|
|
150
154
|
|
|
155
|
+
**A**:
|
|
151
156
|
```bash
|
|
157
|
+
# 源码安装
|
|
152
158
|
git pull
|
|
153
159
|
npm install
|
|
154
160
|
npm run build
|
|
161
|
+
|
|
162
|
+
# 全局 npm 包
|
|
163
|
+
npm install -g thatgfsj-code@latest
|
|
155
164
|
```
|
|
156
165
|
|
|
166
|
+
### Q: 启动瞬间报 `ReferenceError: require is not defined`?
|
|
167
|
+
|
|
168
|
+
**A**: 这是 0.2.1 之前的 bug,已经在 **0.2.2** 修复(用 `createRequire(import.meta.url)`)。
|
|
169
|
+
如果你看到这个错,升级到最新版本即可:
|
|
170
|
+
```bash
|
|
171
|
+
npm install -g thatgfsj-code@latest
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Q: REPL 中 Ctrl+C 不能退出?
|
|
175
|
+
|
|
176
|
+
**A**: 0.2.2 起,空输入连续按两次 Ctrl+C 即可退出。0.2.1 之前该路径只会打
|
|
177
|
+
warning,不会退出;升级版本解决。
|
|
178
|
+
|
|
157
179
|
### Q: 遇到其他错误怎么办?
|
|
158
180
|
|
|
159
181
|
**A**:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thatgfsj-code",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Thatgfsj Code - Your AI Coding Assistant",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,16 +12,48 @@
|
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
14
|
"dev": "tsc && node dist/index.js",
|
|
15
|
-
"link": "npm link"
|
|
15
|
+
"link": "npm link",
|
|
16
|
+
"test": "node --test tests/*.test.js",
|
|
17
|
+
"test:silent": "node --test --test-reporter=dot tests/*.test.js",
|
|
18
|
+
"prepublishOnly": "npm run test && npm run build"
|
|
16
19
|
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"DEVELOPMENT.md",
|
|
24
|
+
"CHANGELOG.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"docs",
|
|
27
|
+
"install.sh",
|
|
28
|
+
"install.ps1",
|
|
29
|
+
"install.bat"
|
|
30
|
+
],
|
|
17
31
|
"keywords": [
|
|
18
32
|
"ai",
|
|
19
33
|
"cli",
|
|
20
34
|
"coding",
|
|
21
|
-
"assistant"
|
|
35
|
+
"assistant",
|
|
36
|
+
"anthropic",
|
|
37
|
+
"openai",
|
|
38
|
+
"siliconflow",
|
|
39
|
+
"minimax",
|
|
40
|
+
"kimi",
|
|
41
|
+
"deepseek",
|
|
42
|
+
"ollama"
|
|
22
43
|
],
|
|
23
44
|
"author": "Thatgfsj",
|
|
24
45
|
"license": "MIT",
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/Thatgfsj/thatgfsj-code.git"
|
|
52
|
+
},
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/Thatgfsj/thatgfsj-code/issues"
|
|
55
|
+
},
|
|
56
|
+
"homepage": "https://github.com/Thatgfsj/thatgfsj-code#readme",
|
|
25
57
|
"dependencies": {
|
|
26
58
|
"chalk": "^5.3.0",
|
|
27
59
|
"commander": "^11.1.0",
|
|
@@ -1,279 +0,0 @@
|
|
|
1
|
-
From e51c23c167f946a6db211b0ea0a344502c6c7427 Mon Sep 17 00:00:00 2001
|
|
2
|
-
From: Thatgfsj <thatgfsj@users.noreply.github.com>
|
|
3
|
-
Date: Mon, 6 Jul 2026 02:33:34 +0800
|
|
4
|
-
Subject: [PATCH 1/4] fix(repl): replace readline with @inquirer/input for
|
|
5
|
-
keypad arrow key support
|
|
6
|
-
|
|
7
|
-
Node readline.createInterface does not emit keypress events for the
|
|
8
|
-
numeric keypad's arrow keys on Windows terminals, so users could not
|
|
9
|
-
move the cursor with the arrows or edit within the input line.
|
|
10
|
-
|
|
11
|
-
REPLInput now wraps @inquirer/input with prefill='editable', which uses
|
|
12
|
-
raw mode and standard keypress events for both keypad and main-cluster
|
|
13
|
-
arrow keys, plus Home/End/Ctrl+A/Ctrl+E for in-line editing.
|
|
14
|
-
---
|
|
15
|
-
src/repl/input.ts | 209 +++++++++++++++++++++-------------------------
|
|
16
|
-
1 file changed, 94 insertions(+), 115 deletions(-)
|
|
17
|
-
|
|
18
|
-
diff --git a/src/repl/input.ts b/src/repl/input.ts
|
|
19
|
-
index a30a92a..9fac346 100644
|
|
20
|
-
--- a/src/repl/input.ts
|
|
21
|
-
+++ b/src/repl/input.ts
|
|
22
|
-
@@ -1,160 +1,139 @@
|
|
23
|
-
/**
|
|
24
|
-
* REPL Input Handler
|
|
25
|
-
- * Handles multiline input, history, and Ctrl+C
|
|
26
|
-
+ *
|
|
27
|
-
+ * 基于 @inquirer/input 实现,支持:
|
|
28
|
-
+ * - 方向键(含数字小键盘方向键)行内移动 / 跳转词首尾
|
|
29
|
-
+ * - Home / End / Backspace / Delete 行内编辑
|
|
30
|
-
+ * - Ctrl+A / Ctrl+E 跳到行首/行尾(emacs 风格)
|
|
31
|
-
+ * - Ctrl+C 中断当前输入(不清空整个会话),连续按两次空输入退出
|
|
32
|
-
+ * - 命令历史:本地维护 history 数组,通过 default 预填上一条
|
|
33
|
-
+ *
|
|
34
|
-
+ * 不再使用 Node 内置 readline 的 rl.question()——它在 Windows 终端下
|
|
35
|
-
+ * 对小键盘方向键的 ANSI 转义序列不友好,会导致光标无法移动 (Bug #1)。
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
-import readline from 'readline';
|
|
39
|
-
+import input from '@inquirer/input';
|
|
40
|
-
import chalk from 'chalk';
|
|
41
|
-
|
|
42
|
-
+const MAX_HISTORY = 200;
|
|
43
|
-
+
|
|
44
|
-
+export type PromptResult =
|
|
45
|
-
+ | { kind: 'value'; value: string } // 用户正常输入并提交(Enter)
|
|
46
|
-
+ | { kind: 'cancelled' }; // Ctrl+C 中断当前输入
|
|
47
|
-
+
|
|
48
|
-
export class REPLInput {
|
|
49
|
-
- private rl: readline.Interface;
|
|
50
|
-
private history: string[] = [];
|
|
51
|
-
- private historyIndex: number = -1;
|
|
52
|
-
- private currentInput: string = '';
|
|
53
|
-
- private multilineMode: boolean = false;
|
|
54
|
-
- private multilineBuffer: string[] = [];
|
|
55
|
-
-
|
|
56
|
-
- constructor() {
|
|
57
|
-
- this.rl = readline.createInterface({
|
|
58
|
-
- input: process.stdin,
|
|
59
|
-
- output: process.stdout,
|
|
60
|
-
- completer: this.completer.bind(this),
|
|
61
|
-
- historySize: 100,
|
|
62
|
-
- tabSize: 4
|
|
63
|
-
- });
|
|
64
|
-
+ private historyIndex: number = -1; // -1 表示"未在历史中浏览"
|
|
65
|
-
+ private currentDraft: string = ''; // 离开历史时保留用户原始草稿
|
|
66
|
-
+ private defaultPrefix: string = chalk.green('\n> ');
|
|
67
|
-
+ private consecutiveCancels: number = 0; // 连续空输入 + Ctrl+C 计数,达到阈值退出
|
|
68
|
-
|
|
69
|
-
- // Handle Ctrl+C
|
|
70
|
-
- this.rl.on('SIGINT', () => {
|
|
71
|
-
- if (this.currentInput.length > 0 || this.multilineBuffer.length > 0) {
|
|
72
|
-
- // Clear current input
|
|
73
|
-
- this.currentInput = '';
|
|
74
|
-
- this.multilineBuffer = [];
|
|
75
|
-
- this.multilineMode = false;
|
|
76
|
-
- process.stdout.write('\n');
|
|
77
|
-
- this.rl.prompt();
|
|
78
|
-
- } else {
|
|
79
|
-
- // Exit REPL
|
|
80
|
-
- process.stdout.write(chalk.gray('\n\n👋 Exiting...\n'));
|
|
81
|
-
- process.exit(0);
|
|
82
|
-
+ /**
|
|
83
|
-
+ * Ask the user for input. Returns either a value or 'cancelled'.
|
|
84
|
-
+ * Loop decides what to do with cancellation (usually: continue session,
|
|
85
|
-
+ * unless user has cancelled an already-empty input twice in a row).
|
|
86
|
-
+ */
|
|
87
|
-
+ async prompt(prefix?: string, abortSignal?: AbortSignal): Promise<PromptResult> {
|
|
88
|
-
+ const prefill = this.computePrefill();
|
|
89
|
-
+ const previousDraft = prefill.kind === 'history' ? prefill.value : '';
|
|
90
|
-
+
|
|
91
|
-
+ // 用 abortSignal 绑到 inquirer:外面 Ctrl+C / 主进程事件可以取消
|
|
92
|
-
+ const value = await input({
|
|
93
|
-
+ message: prefix ?? this.defaultPrefix,
|
|
94
|
-
+ prefill: 'editable', // 关键:启用方向键 + 行内编辑,包括小键盘
|
|
95
|
-
+ default: previousDraft, // 历史预填
|
|
96
|
-
+ // Ctrl+C 由 @inquirer/input 抛出一个 symbol-like 错误,我们捕获为 cancelled
|
|
97
|
-
+ validate: () => true,
|
|
98
|
-
+ }, abortSignal ? { signal: abortSignal } : undefined).catch((err: any) => {
|
|
99
|
-
+ // 区分真实错误与用户取消;inquirer 用 ExitPromptError (name: 'ExitPromptError')
|
|
100
|
-
+ if (err && (err.name === 'ExitPromptError' || err.message?.includes('User force closed'))) {
|
|
101
|
-
+ return null;
|
|
102
|
-
}
|
|
103
|
-
+ throw err;
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
- // Track history navigation
|
|
107
|
-
- this.rl.on('line', (line) => {
|
|
108
|
-
- if (line.trim()) {
|
|
109
|
-
- this.history.push(line);
|
|
110
|
-
- if (this.history.length > 100) {
|
|
111
|
-
- this.history.shift();
|
|
112
|
-
- }
|
|
113
|
-
+ if (value === null || value === undefined) {
|
|
114
|
-
+ this.consecutiveCancels++;
|
|
115
|
-
+ return { kind: 'cancelled' };
|
|
116
|
-
+ }
|
|
117
|
-
+
|
|
118
|
-
+ const trimmed = value.trim();
|
|
119
|
-
+ if (trimmed.length > 0) {
|
|
120
|
-
+ // 避免把连续重复的内容都压入历史
|
|
121
|
-
+ if (this.history[this.history.length - 1] !== trimmed) {
|
|
122
|
-
+ this.history.push(trimmed);
|
|
123
|
-
+ if (this.history.length > MAX_HISTORY) this.history.shift();
|
|
124
|
-
}
|
|
125
|
-
- this.historyIndex = this.history.length;
|
|
126
|
-
- });
|
|
127
|
-
- }
|
|
128
|
-
+ }
|
|
129
|
-
|
|
130
|
-
- /**
|
|
131
|
-
- * Prompt for input
|
|
132
|
-
- */
|
|
133
|
-
- async prompt(prefix: string = chalk.green('\n> ')): Promise<string> {
|
|
134
|
-
- return new Promise((resolve) => {
|
|
135
|
-
- this.rl.question(prefix, (answer) => {
|
|
136
|
-
- resolve(answer);
|
|
137
|
-
- });
|
|
138
|
-
- });
|
|
139
|
-
+ this.consecutiveCancels = 0;
|
|
140
|
-
+ return { kind: 'value', value: trimmed };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
- * Set prompt prefix
|
|
145
|
-
+ * Should we ask the loop to exit? Two consecutive Ctrl+C on empty input => exit.
|
|
146
|
-
*/
|
|
147
|
-
- setPrompt(prefix: string): void {
|
|
148
|
-
- this.rl.setPrompt(prefix);
|
|
149
|
-
+ shouldExitOnCancel(): boolean {
|
|
150
|
-
+ return this.consecutiveCancels >= 2;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
- * Close the interface
|
|
155
|
-
+ * Reset consecutive-cancel counter when the loop has decided to keep running.
|
|
156
|
-
*/
|
|
157
|
-
- close(): void {
|
|
158
|
-
- this.rl.close();
|
|
159
|
-
+ resetCancelCounter(): void {
|
|
160
|
-
+ this.consecutiveCancels = 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
- * Pause input
|
|
165
|
-
+ * Decide what to prefill. Two modes:
|
|
166
|
-
+ * - 'history': when user is navigating up/down with arrows
|
|
167
|
-
+ * - 'draft': when user has cleared the input — preserve their draft
|
|
168
|
-
*/
|
|
169
|
-
- pause(): void {
|
|
170
|
-
- this.rl.pause();
|
|
171
|
-
+ private computePrefill(): { kind: 'history' | 'draft' | 'none'; value: string } {
|
|
172
|
-
+ if (this.historyIndex >= 0 && this.historyIndex < this.history.length) {
|
|
173
|
-
+ return { kind: 'history', value: this.history[this.historyIndex] };
|
|
174
|
-
+ }
|
|
175
|
-
+ if (this.currentDraft) return { kind: 'draft', value: this.currentDraft };
|
|
176
|
-
+ return { kind: 'none', value: '' };
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
- * Resume input
|
|
181
|
-
+ * Save the user's in-progress draft so ↑↓ preserves their unsent typing.
|
|
182
|
-
*/
|
|
183
|
-
- resume(): void {
|
|
184
|
-
- this.rl.resume();
|
|
185
|
-
+ saveDraft(text: string): void {
|
|
186
|
-
+ this.currentDraft = text;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
- * Get previous command from history (for key handler)
|
|
191
|
-
+ * Navigate up in history (called from a global key listener, if needed).
|
|
192
|
-
*/
|
|
193
|
-
- getPreviousHistory(): string | null {
|
|
194
|
-
- if (this.history.length === 0) return null;
|
|
195
|
-
-
|
|
196
|
-
- if (this.historyIndex > 0) {
|
|
197
|
-
- this.historyIndex--;
|
|
198
|
-
- }
|
|
199
|
-
- return this.history[this.historyIndex] || null;
|
|
200
|
-
+ historyUp(): string {
|
|
201
|
-
+ if (this.history.length === 0) return this.currentDraft;
|
|
202
|
-
+ if (this.historyIndex < 0) this.historyIndex = this.history.length;
|
|
203
|
-
+ if (this.historyIndex > 0) this.historyIndex--;
|
|
204
|
-
+ return this.history[this.historyIndex] ?? '';
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
- * Get next command from history (for key handler)
|
|
209
|
-
+ * Navigate down in history.
|
|
210
|
-
*/
|
|
211
|
-
- getNextHistory(): string | null {
|
|
212
|
-
- if (this.historyIndex < this.history.length - 1) {
|
|
213
|
-
- this.historyIndex++;
|
|
214
|
-
- return this.history[this.historyIndex];
|
|
215
|
-
+ historyDown(): string {
|
|
216
|
-
+ if (this.history.length === 0) return this.currentDraft;
|
|
217
|
-
+ if (this.historyIndex >= this.history.length) return this.currentDraft;
|
|
218
|
-
+ this.historyIndex++;
|
|
219
|
-
+ if (this.historyIndex >= this.history.length) {
|
|
220
|
-
+ this.historyIndex = this.history.length;
|
|
221
|
-
+ return this.currentDraft;
|
|
222
|
-
}
|
|
223
|
-
- this.historyIndex = this.history.length;
|
|
224
|
-
- return '';
|
|
225
|
-
+ return this.history[this.historyIndex] ?? '';
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
- /**
|
|
229
|
-
- * Reset history navigation
|
|
230
|
-
- */
|
|
231
|
-
- resetHistoryNavigation(): void {
|
|
232
|
-
- this.historyIndex = this.history.length;
|
|
233
|
-
+ getHistory(): readonly string[] {
|
|
234
|
-
+ return this.history;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
- /**
|
|
238
|
-
- * Simple completer for commands
|
|
239
|
-
- */
|
|
240
|
-
- private completer(line: string): [string[], string] {
|
|
241
|
-
- const commands = [
|
|
242
|
-
- 'exit', 'quit', 'clear', 'context',
|
|
243
|
-
- 'help', 'tools', 'models', 'providers',
|
|
244
|
-
- 'history', 'git', 'file', 'shell'
|
|
245
|
-
- ];
|
|
246
|
-
-
|
|
247
|
-
- const hits = commands.filter(c => c.startsWith(line.toLowerCase()));
|
|
248
|
-
- return [hits.length ? hits : [], line];
|
|
249
|
-
- }
|
|
250
|
-
-
|
|
251
|
-
- /**
|
|
252
|
-
- * Handle multiline input detection
|
|
253
|
-
- */
|
|
254
|
-
- isMultilineComplete(input: string): boolean {
|
|
255
|
-
- // Check for explicit multiline trigger
|
|
256
|
-
- if (input.endsWith('\\')) {
|
|
257
|
-
- return false;
|
|
258
|
-
- }
|
|
259
|
-
-
|
|
260
|
-
- // Check bracket balance
|
|
261
|
-
- const openBrackets = (input.match(/\{/g) || []).length;
|
|
262
|
-
- const closeBrackets = (input.match(/\}/g) || []).length;
|
|
263
|
-
- const openParens = (input.match(/\(/g) || []).length;
|
|
264
|
-
- const closeParens = (input.match(/\)/g) || []).length;
|
|
265
|
-
- const openBrackets2 = (input.match(/\[/g) || []).length;
|
|
266
|
-
- const closeBrackets2 = (input.match(/\]/g) || []).length;
|
|
267
|
-
-
|
|
268
|
-
- // Balanced if all counts match
|
|
269
|
-
- return openBrackets === closeBrackets &&
|
|
270
|
-
- openParens === closeParens &&
|
|
271
|
-
- openBrackets2 === closeBrackets2;
|
|
272
|
-
+ clearHistory(): void {
|
|
273
|
-
+ this.history = [];
|
|
274
|
-
+ this.historyIndex = -1;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
--
|
|
278
|
-
2.45.2.windows.1
|
|
279
|
-
|