Skip to content

Commit 272d8f0

Browse files
authored
Tweak inline chat voice gestures (microsoft#202731)
- special rendering for recognized text - enable hold more for quick inline voice chat - increase/adjust hold mode timeouts
1 parent 4a5169c commit 272d8f0

File tree

3 files changed

+48
-19
lines changed

3 files changed

+48
-19
lines changed

src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatActions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class StartSessionAction extends EditorAction2 {
4949
const commandService = accessor.get(ICommandService);
5050

5151
if (configService.getValue<boolean>(InlineChatConfigKeys.HoldToSpeech) // enabled
52-
&& speechService.hasSpeechProvider // possible
52+
&& speechService.hasSpeechProvider // possible
5353
) {
5454

5555
const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id);
@@ -59,7 +59,7 @@ export class StartSessionAction extends EditorAction2 {
5959
// start VOICE input
6060
commandService.executeCommand(StartVoiceChatAction.ID);
6161
listening = true;
62-
}, 100);
62+
}, 250);
6363

6464
holdMode.finally(() => {
6565
if (listening) {

src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,7 @@
3737
color: var(--vscode-editorCursor-background);
3838
}
3939
}
40+
41+
.monaco-editor .inline-chat-quick-voice .message.preview {
42+
opacity: .4;
43+
}

src/vs/workbench/contrib/inlineChat/electron-sandbox/inlineChatQuickVoice.ts

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import 'vs/css!./inlineChatQuickVoice';
77
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
88
import { Codicon } from 'vs/base/common/codicons';
9-
import { KeyCode } from 'vs/base/common/keyCodes';
9+
import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes';
1010
import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
1111
import { EditorAction2 } from 'vs/editor/browser/editorExtensions';
1212
import { IEditorContribution } from 'vs/editor/common/editorCommon';
@@ -17,10 +17,11 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis
1717
import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService';
1818
import { CancellationTokenSource } from 'vs/base/common/cancellation';
1919
import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
20-
import { h, reset } from 'vs/base/browser/dom';
20+
import { getWindow, h, reset, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom';
2121
import { IDimension } from 'vs/editor/common/core/dimension';
2222
import { EditorOption } from 'vs/editor/common/config/editorOptions';
2323
import { AbstractInlineChatAction } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions';
24+
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2425

2526
const CTX_QUICK_CHAT_IN_PROGRESS = new RawContextKey<boolean>('inlineChat.quickChatInProgress', false);
2627

@@ -33,14 +34,29 @@ export class StartAction extends EditorAction2 {
3334
category: AbstractInlineChatAction.category,
3435
precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS.toNegated()),
3536
f1: true,
36-
// keybinding: {
37-
// primary: KeyChord(KeyCode.F12, KeyCode.F12),
38-
// weight: KeybindingWeight.WorkbenchContrib
39-
// }
37+
keybinding: {
38+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI),
39+
weight: KeybindingWeight.WorkbenchContrib + 100
40+
}
4041
});
4142
}
4243

43-
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
44+
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor) {
45+
const keybindingService = accessor.get(IKeybindingService);
46+
47+
const holdMode = keybindingService.enableKeybindingHoldMode(this.desc.id);
48+
if (holdMode) {
49+
let shouldCallStop = false;
50+
const handle = setTimeout(() => {
51+
shouldCallStop = true;
52+
}, 500);
53+
holdMode.finally(() => {
54+
clearTimeout(handle);
55+
if (shouldCallStop) {
56+
InlineChatQuickVoice.get(editor)?.stop();
57+
}
58+
});
59+
}
4460
InlineChatQuickVoice.get(editor)?.start();
4561
}
4662
}
@@ -55,13 +71,13 @@ export class StopAction extends EditorAction2 {
5571
precondition: ContextKeyExpr.and(HasSpeechProvider, CTX_QUICK_CHAT_IN_PROGRESS),
5672
f1: true,
5773
keybinding: {
58-
primary: KeyCode.Escape,
59-
weight: KeybindingWeight.WorkbenchContrib
74+
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyCode.KeyI),
75+
weight: KeybindingWeight.WorkbenchContrib + 100
6076
}
6177
});
6278
}
6379

64-
override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]) {
80+
override runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) {
6581
InlineChatQuickVoice.get(editor)?.stop();
6682
}
6783
}
@@ -93,12 +109,14 @@ class QuickVoiceWidget implements IContentWidget {
93109
private readonly _domNode = document.createElement('div');
94110
private readonly _elements = h('.inline-chat-quick-voice@main', [
95111
h('span@mic'),
96-
h('span@message'),
112+
h('span.message@message'),
97113
]);
98114

99115
constructor(private readonly _editor: ICodeEditor) {
100116
this._domNode.appendChild(this._elements.root);
101117
this._domNode.style.zIndex = '1000';
118+
this._domNode.tabIndex = -1;
119+
this._domNode.style.outline = 'none';
102120
reset(this._elements.mic, renderIcon(Codicon.micFilled));
103121
}
104122

@@ -134,17 +152,22 @@ class QuickVoiceWidget implements IContentWidget {
134152

135153
// ---
136154

137-
updateInput(input: string | undefined): void {
155+
updateInput(input: string | undefined, isDefinite: boolean): void {
156+
this._elements.message.classList.toggle('preview', !isDefinite);
138157
this._elements.message.textContent = input ?? '';
139158
}
140159

160+
focus(): void {
161+
this._domNode.focus();
162+
}
163+
141164
active(): void {
142165
this._elements.main.classList.add('recording');
143166
}
144167

145168
reset(): void {
146169
this._elements.main.classList.remove('recording');
147-
this.updateInput(undefined);
170+
this.updateInput(undefined, true);
148171
}
149172
}
150173

@@ -179,8 +202,11 @@ export class InlineChatQuickVoice implements IEditorContribution {
179202
this._editor.addContentWidget(this._widget);
180203
this._ctxQuickChatInProgress.set(true);
181204

182-
let message: string | undefined;
205+
runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._widget.getDomNode()), () => {
206+
this._widget.focus(); // requires RAF because...
207+
});
183208

209+
let message: string | undefined;
184210
const session = this._speechService.createSpeechToTextSession(cts.token);
185211
const listener = session.onDidChange(e => {
186212

@@ -195,12 +221,11 @@ export class InlineChatQuickVoice implements IEditorContribution {
195221
case SpeechToTextStatus.Stopped:
196222
break;
197223
case SpeechToTextStatus.Recognizing:
198-
// TODO@jrieken special rendering for "in-flight" message?
199-
this._widget.updateInput(!message ? e.text : `${message} ${e.text}`);
224+
this._widget.updateInput(!message ? e.text : `${message} ${e.text}`, false);
200225
break;
201226
case SpeechToTextStatus.Recognized:
202227
message = !message ? e.text : `${message} ${e.text}`;
203-
this._widget.updateInput(message);
228+
this._widget.updateInput(message, true);
204229
break;
205230
}
206231
});

0 commit comments

Comments
 (0)