Merge branch 'main' into do-162262

This commit is contained in:
John Murray
2024-02-29 12:08:45 +00:00
committed by GitHub
53 changed files with 510 additions and 201 deletions
+79 -36
View File
@@ -94,41 +94,42 @@ impl ServerMessageSink {
async fn server_message_or_closed(
&mut self,
body: Option<&[u8]>,
body_or_end: Option<&[u8]>,
) -> Result<(), mpsc::error::SendError<SocketSignal>> {
let i = self.id;
let mut tx = self.tx.take().unwrap();
let msg = body
.map(|b| self.get_server_msg_content(b))
.map(|body| RefServerMessageParams { i, body });
let r = match &mut tx {
ServerMessageDestination::Channel(tx) => {
tx.send(SocketSignal::from_message(&ToClientRequest {
id: None,
params: match msg {
Some(msg) => ClientRequestMethod::servermsg(msg),
None => ClientRequestMethod::serverclose(ServerClosedParams { i }),
},
}))
.await
}
ServerMessageDestination::Rpc(caller) => {
match msg {
Some(msg) => caller.notify("servermsg", msg),
None => caller.notify("serverclose", ServerClosedParams { i }),
};
Ok(())
}
};
if let Some(b) = body_or_end {
let body = self.get_server_msg_content(b, false);
let r =
send_data_or_close_if_none(i, &mut tx, Some(RefServerMessageParams { i, body }))
.await;
self.tx = Some(tx);
return r;
}
let tail = self.get_server_msg_content(&[], true);
if !tail.is_empty() {
let _ = send_data_or_close_if_none(
i,
&mut tx,
Some(RefServerMessageParams { i, body: tail }),
)
.await;
}
let r = send_data_or_close_if_none(i, &mut tx, None).await;
self.tx = Some(tx);
r
}
pub(crate) fn get_server_msg_content<'a: 'b, 'b>(&'a mut self, body: &'b [u8]) -> &'b [u8] {
pub(crate) fn get_server_msg_content<'a: 'b, 'b>(
&'a mut self,
body: &'b [u8],
finish: bool,
) -> &'b [u8] {
if let Some(flate) = &mut self.flate {
if let Ok(compressed) = flate.process(body) {
if let Ok(compressed) = flate.process(body, finish) {
return compressed;
}
}
@@ -137,6 +138,32 @@ impl ServerMessageSink {
}
}
async fn send_data_or_close_if_none(
i: u16,
tx: &mut ServerMessageDestination,
msg: Option<RefServerMessageParams<'_>>,
) -> Result<(), mpsc::error::SendError<SocketSignal>> {
match tx {
ServerMessageDestination::Channel(tx) => {
tx.send(SocketSignal::from_message(&ToClientRequest {
id: None,
params: match msg {
Some(msg) => ClientRequestMethod::servermsg(msg),
None => ClientRequestMethod::serverclose(ServerClosedParams { i }),
},
}))
.await
}
ServerMessageDestination::Rpc(caller) => {
match msg {
Some(msg) => caller.notify("servermsg", msg),
None => caller.notify("serverclose", ServerClosedParams { i }),
};
Ok(())
}
}
}
impl Drop for ServerMessageSink {
fn drop(&mut self) {
self.multiplexer.remove(self.id);
@@ -162,7 +189,8 @@ impl ClientMessageDecoder {
pub fn decode<'a: 'b, 'b>(&'a mut self, message: &'b [u8]) -> std::io::Result<&'b [u8]> {
match &mut self.dec {
Some(d) => d.process(message),
// todo@connor4312 do we ever need to actually 'finish' the client message stream?
Some(d) => d.process(message, false),
None => Ok(message),
}
}
@@ -175,6 +203,7 @@ trait FlateAlgorithm {
&mut self,
contents: &[u8],
output: &mut [u8],
finish: bool,
) -> Result<flate2::Status, std::io::Error>;
}
@@ -193,9 +222,15 @@ impl FlateAlgorithm for DecompressFlateAlgorithm {
&mut self,
contents: &[u8],
output: &mut [u8],
finish: bool,
) -> Result<flate2::Status, std::io::Error> {
let mode = match finish {
true => flate2::FlushDecompress::Finish,
false => flate2::FlushDecompress::None,
};
self.0
.decompress(contents, output, flate2::FlushDecompress::None)
.decompress(contents, output, mode)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
}
@@ -215,9 +250,15 @@ impl FlateAlgorithm for CompressFlateAlgorithm {
&mut self,
contents: &[u8],
output: &mut [u8],
finish: bool,
) -> Result<flate2::Status, std::io::Error> {
let mode = match finish {
true => flate2::FlushCompress::Finish,
false => flate2::FlushCompress::Sync,
};
self.0
.compress(contents, output, flate2::FlushCompress::Sync)
.compress(contents, output, mode)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))
}
}
@@ -241,23 +282,25 @@ where
}
}
pub fn process(&mut self, contents: &[u8]) -> std::io::Result<&[u8]> {
pub fn process(&mut self, contents: &[u8], finish: bool) -> std::io::Result<&[u8]> {
let mut out_offset = 0;
let mut in_offset = 0;
loop {
let in_before = self.flate.total_in();
let out_before = self.flate.total_out();
match self
.flate
.process(&contents[in_offset..], &mut self.output[out_offset..])
{
match self.flate.process(
&contents[in_offset..],
&mut self.output[out_offset..],
finish,
) {
Ok(flate2::Status::Ok | flate2::Status::BufError) => {
let processed_len = in_offset + (self.flate.total_in() - in_before) as usize;
let output_len = out_offset + (self.flate.total_out() - out_before) as usize;
if processed_len < contents.len() {
if processed_len < contents.len() || output_len == self.output.len() {
// If we filled the output buffer but there's more data to compress,
// extend the output buffer and keep compressing.
// or the output got filled after processing all input, extend
// the output buffer and keep compressing.
out_offset = output_len;
in_offset = processed_len;
if output_len == self.output.len() {
@@ -298,7 +341,7 @@ mod tests {
// 3000 and 30000 test resizing the buffer
for msg_len in [3, 30, 300, 3000, 30000] {
let vals = (0..msg_len).map(|v| v as u8).collect::<Vec<u8>>();
let compressed = sink.get_server_msg_content(&vals);
let compressed = sink.get_server_msg_content(&vals, false);
assert_ne!(compressed, vals);
let decompressed = decompress.decode(compressed).unwrap();
assert_eq!(decompressed.len(), vals.len());
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.88.0",
"distro": "de07f23454d5352cc3711ca34d51278767e6eb0a",
"distro": "a5b6daf94540aab9d17335c2c2533e629d750123",
"author": {
"name": "Microsoft Corporation"
},
@@ -140,7 +140,7 @@ export class HighlightedLabel extends Disposable {
} else {
if (!this.customHover && this.title !== '') {
const hoverDelegate = this.options?.hoverDelegate ?? getDefaultHoverDelegate('mouse');
this.customHover = this._store.add(setupCustomHover(hoverDelegate, this.domNode, this.title));
this.customHover = this._register(setupCustomHover(hoverDelegate, this.domNode, this.title));
} else if (this.customHover) {
this.customHover.update(this.title);
}
@@ -4,9 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { reset } from 'vs/base/browser/dom';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { IDisposable } from 'vs/base/common/lifecycle';
export class SimpleIconLabel {
export class SimpleIconLabel implements IDisposable {
private hover?: ICustomHover;
constructor(
private readonly _container: HTMLElement
@@ -17,6 +22,14 @@ export class SimpleIconLabel {
}
set title(title: string) {
this._container.title = title;
if (!this.hover && title) {
this.hover = setupCustomHover(getDefaultHoverDelegate('mouse'), this._container, title);
} else if (this.hover) {
this.hover.update(title);
}
}
dispose(): void {
this.hover?.dispose();
}
}
@@ -4,8 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
import { ResolvedKeybinding, ResolvedChord } from 'vs/base/common/keybindings';
import { Disposable } from 'vs/base/common/lifecycle';
import { equals } from 'vs/base/common/objects';
import { OperatingSystem } from 'vs/base/common/platform';
import 'vs/css!./keybindingLabel';
@@ -50,18 +53,21 @@ export const unthemedKeybindingLabelOptions: KeybindingLabelOptions = {
keybindingLabelShadow: undefined
};
export class KeybindingLabel {
export class KeybindingLabel extends Disposable {
private domNode: HTMLElement;
private options: KeybindingLabelOptions;
private readonly keyElements = new Set<HTMLSpanElement>();
private hover: ICustomHover;
private keybinding: ResolvedKeybinding | undefined;
private matches: Matches | undefined;
private didEverRender: boolean;
constructor(container: HTMLElement, private os: OperatingSystem, options?: KeybindingLabelOptions) {
super();
this.options = options || Object.create(null);
const labelForeground = this.options.keybindingLabelForeground;
@@ -71,6 +77,8 @@ export class KeybindingLabel {
this.domNode.style.color = labelForeground;
}
this.hover = this._register(setupCustomHover(getDefaultHoverDelegate('mouse'), this.domNode, ''));
this.didEverRender = false;
container.appendChild(this.domNode);
}
@@ -102,11 +110,8 @@ export class KeybindingLabel {
this.renderChord(this.domNode, chords[i], this.matches ? this.matches.chordPart : null);
}
const title = (this.options.disableTitle ?? false) ? undefined : this.keybinding.getAriaLabel() || undefined;
if (title !== undefined) {
this.domNode.title = title;
} else {
this.domNode.removeAttribute('title');
}
this.hover.update(title);
this.domNode.setAttribute('aria-label', title || '');
} else if (this.options && this.options.renderUnboundKeybindings) {
this.renderUnbound(this.domNode);
}
+1
View File
@@ -59,6 +59,7 @@ export class ToggleActionViewItem extends BaseActionViewItem {
inputActiveOptionBackground: options.toggleStyles?.inputActiveOptionBackground,
inputActiveOptionBorder: options.toggleStyles?.inputActiveOptionBorder,
inputActiveOptionForeground: options.toggleStyles?.inputActiveOptionForeground,
hoverDelegate: options.hoverDelegate
}));
this._register(this.toggle.onChange(() => this._action.checked = !!this.toggle && this.toggle.checked));
}
@@ -389,12 +389,12 @@ export class ObservableValue<T, TChange = void>
constructor(
private readonly _owner: Owner,
private readonly _debugName: string | undefined,
initialValue: T
initialValue: T,
) {
super();
this._value = initialValue;
}
public get(): T {
public override get(): T {
return this._value;
}
@@ -253,6 +253,9 @@ class ObservableSignal<TChange> extends BaseObservable<void, TChange> implements
}
}
/**
* @deprecated Use `debouncedObservable2` instead.
*/
export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: number, disposableStore: DisposableStore): IObservable<T | undefined> {
const debouncedObservable = observableValue<T | undefined>('debounced', undefined);
@@ -276,6 +279,48 @@ export function debouncedObservable<T>(observable: IObservable<T>, debounceMs: n
return debouncedObservable;
}
/**
* Creates an observable that debounces the input observable.
*/
export function debouncedObservable2<T>(observable: IObservable<T>, debounceMs: number): IObservable<T> {
let hasValue = false;
let lastValue: T | undefined;
let timeout: any = undefined;
return observableFromEvent<T, void>(cb => {
const d = autorun(reader => {
const value = observable.read(reader);
if (!hasValue) {
hasValue = true;
lastValue = value;
} else {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
lastValue = value;
cb();
}, debounceMs);
}
});
return {
dispose() {
d.dispose();
hasValue = false;
lastValue = undefined;
},
};
}, () => {
if (hasValue) {
return lastValue!;
} else {
return observable.get();
}
});
}
export function wasEventTriggeredRecently(event: Event<any>, timeoutMs: number, disposableStore: DisposableStore): IObservable<boolean> {
const observable = observableValue('triggeredRecently', false);
@@ -236,7 +236,7 @@ export class DiffEditorItemTemplate extends Disposable implements IPooledObject<
});
}
private readonly _headerHeight = /*this._elements.header.clientHeight*/ 48;
private readonly _headerHeight = /*this._elements.header.clientHeight*/ 40;
private _lastScrollTop = -1;
private _isSettingScrollTop = false;
@@ -285,6 +285,6 @@ function isFocused(editor: ICodeEditor): IObservable<boolean> {
store.add(editor.onDidBlurEditorWidget(() => h(false)));
return store;
},
() => editor.hasWidgetFocus()
() => editor.hasTextFocus()
);
}
@@ -37,7 +37,7 @@
.header-content {
margin: 8px 8px 0px 8px;
padding: 8px 5px;
padding: 4px 5px;
border-top: 1px solid var(--vscode-multiDiffEditor-border);
border-right: 1px solid var(--vscode-multiDiffEditor-border);
+8
View File
@@ -1710,6 +1710,14 @@ export interface CommentInfo {
commentingRanges: CommentingRanges;
}
/**
* @internal
*/
export interface CommentingRangeResourceHint {
schemes: readonly string[];
}
/**
* @internal
*/
@@ -1051,6 +1051,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IVerticalSashL
icon: findSelectionIcon,
title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
isChecked: false,
hoverDelegate: hoverDelegate,
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground),
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
@@ -302,7 +302,7 @@ class StatusBarViewItem extends MenuEntryActionViewItem {
if (this.label) {
const div = h('div.keybinding').root;
const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions });
const k = this._register(new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }));
k.set(kb);
this.label.textContent = this._action.label;
this.label.appendChild(div);
@@ -121,7 +121,7 @@ export class InlineEditController extends Disposable {
if (this._configurationService.getValue('editor.experimentalInlineEdit.keepOnBlur') || editor.getOption(EditorOption.inlineEdit).keepOnBlur) {
return;
}
this._currentRequestCts?.dispose();
this._currentRequestCts?.dispose(true);
this._currentRequestCts = undefined;
this.clear(false);
}));
@@ -175,7 +175,7 @@ class StatusBarViewItem extends MenuEntryActionViewItem {
if (this.label) {
const div = h('div.keybinding').root;
const k = new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions });
const k = this._register(new KeybindingLabel(div, OS, { disableTitle: true, ...unthemedKeybindingLabelOptions }));
k.set(kb);
this.label.textContent = this._action.label;
this.label.appendChild(div);
@@ -140,7 +140,7 @@ class ActionItemRenderer<T> implements IListRenderer<IActionListItem<T>, IAction
}
disposeTemplate(_templateData: IActionMenuTemplateData): void {
// noop
_templateData.keybinding.dispose();
}
}
@@ -277,6 +277,7 @@ class ListElementRenderer implements IListRenderer<IListElement, IListElementTem
// Keybinding
const keybindingContainer = dom.append(row1, $('.quick-input-list-entry-keybinding'));
data.keybinding = new KeybindingLabel(keybindingContainer, platform.OS);
data.toDisposeTemplate.push(data.keybinding);
// Detail
const detailContainer = dom.append(row2, $('.quick-input-list-label-meta'));
@@ -515,8 +515,6 @@ class WindowsPtyHeuristics extends Disposable {
private _onCursorMoveListener = this._register(new MutableDisposable());
private _recentlyPerformedCsiJ = false;
private _tryAdjustCommandStartMarkerScheduler?: RunOnceScheduler;
private _tryAdjustCommandStartMarkerScannedLineCount: number = 0;
private _tryAdjustCommandStartMarkerPollCount: number = 0;
@@ -530,8 +528,8 @@ class WindowsPtyHeuristics extends Disposable {
super();
this._register(_terminal.parser.registerCsiHandler({ final: 'J' }, params => {
// Clear commands when the viewport is cleared
if (params.length >= 1 && (params[0] === 2 || params[0] === 3)) {
this._recentlyPerformedCsiJ = true;
this._hooks.clearCommandsInViewport();
}
// We don't want to override xterm.js' default behavior, just augment it
@@ -539,11 +537,6 @@ class WindowsPtyHeuristics extends Disposable {
}));
this._register(this._capability.onBeforeCommandFinished(command => {
if (this._recentlyPerformedCsiJ) {
this._recentlyPerformedCsiJ = false;
return;
}
// For older Windows backends we cannot listen to CSI J, instead we assume running clear
// or cls will clear all commands in the viewport. This is not perfect but it's right
// most of the time.
@@ -370,8 +370,8 @@ export class MainThreadCommentController implements ICommentController {
}
}
updateCommentingRanges() {
this._commentService.updateCommentingRanges(this._uniqueId);
updateCommentingRanges(resourceHints?: languages.CommentingRangeResourceHint) {
this._commentService.updateCommentingRanges(this._uniqueId, resourceHints);
}
private getKnownThread(commentThreadHandle: number): MainThreadCommentThread<IRange | ICellRange> {
@@ -591,14 +591,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments
return provider.deleteCommentThread(commentThreadHandle);
}
$updateCommentingRanges(handle: number) {
$updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint) {
const provider = this._commentControllers.get(handle);
if (!provider) {
return;
}
provider.updateCommentingRanges();
provider.updateCommentingRanges(resourceHints);
}
private registerView(commentsViewAlreadyRegistered: boolean) {
@@ -145,7 +145,7 @@ export interface MainThreadCommentsShape extends IDisposable {
$createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean): languages.CommentThread<IRange | ICellRange> | undefined;
$updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void;
$deleteCommentThread(handle: number, commentThreadHandle: number): void;
$updateCommentingRanges(handle: number): void;
$updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint): void;
}
export interface AuthenticationForceNewSessionOptions {
@@ -565,7 +565,10 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) {
this._commentingRangeProvider = provider;
proxy.$updateCommentingRanges(this.handle);
if (provider?.resourceHints) {
checkProposedApiEnabled(this._extension, 'commentingRangeHint');
}
proxy.$updateCommentingRanges(this.handle, provider?.resourceHints);
}
private _reactionHandler?: ReactionHandler;
@@ -517,7 +517,7 @@ export class BreadcrumbsControl {
this._widget.setSelection(items[idx + 1], BreadcrumbsControl.Payload_Pick);
}
} else {
element.outline.reveal(element, { pinned }, group === SIDE_GROUP);
element.outline.reveal(element, { pinned }, group === SIDE_GROUP, false);
}
}
@@ -860,7 +860,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
return (<IOutline<any>>input).reveal(element, {
pinned: true,
preserveFocus: false
}, true);
}, true, false);
}
}
});
@@ -507,7 +507,7 @@ export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker {
protected async _revealElement(element: any, options: IEditorOptions, sideBySide: boolean): Promise<boolean> {
this._onWillPickElement.fire();
const outline: IOutline<any> = this._tree.getInput();
await outline.reveal(element, options, sideBySide);
await outline.reveal(element, options, sideBySide, false);
return true;
}
}
@@ -62,6 +62,7 @@ export class EditorGroupWatermark extends Disposable {
private readonly transientDisposables = this._register(new DisposableStore());
private enabled: boolean = false;
private workbenchState: WorkbenchState;
private keybindingLabel?: KeybindingLabel;
constructor(
container: HTMLElement,
@@ -145,8 +146,9 @@ export class EditorGroupWatermark extends Disposable {
const dt = append(dl, $('dt'));
dt.textContent = entry.text;
const dd = append(dl, $('dd'));
const keybinding = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
keybinding.set(keys);
this.keybindingLabel?.dispose();
this.keybindingLabel = new KeybindingLabel(dd, OS, { renderUnboundKeybindings: true, ...defaultKeybindingLabelStyles });
this.keybindingLabel.set(keys);
}
};
@@ -162,5 +164,6 @@ export class EditorGroupWatermark extends Disposable {
override dispose(): void {
super.dispose();
this.clear();
this.keybindingLabel?.dispose();
}
}
@@ -100,7 +100,7 @@ export abstract class EditorPlaceholder extends EditorPane {
// Icon
const iconContainer = container.appendChild($('.editor-placeholder-icon-container'));
const iconWidget = new SimpleIconLabel(iconContainer);
const iconWidget = disposables.add(new SimpleIconLabel(iconContainer));
iconWidget.text = icon;
// Label
@@ -73,7 +73,7 @@ export class StatusbarEntryItem extends Disposable {
this._register(Gesture.addTarget(this.labelContainer)); // enable touch
// Label (with support for progress)
this.label = new StatusBarCodiconLabel(this.labelContainer);
this.label = this._register(new StatusBarCodiconLabel(this.labelContainer));
this.container.appendChild(this.labelContainer);
// Beak Container
@@ -34,6 +34,8 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IConfigurationMigrationRegistry } from 'vs/workbench/common/configuration';
import { LOG_MODE_ID, OUTPUT_MODE_ID } from 'vs/workbench/services/output/common/output';
import { SEARCH_RESULT_LANGUAGE_ID } from 'vs/workbench/services/search/common/search';
import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
const $ = dom.$;
@@ -263,7 +265,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget {
hintElement.appendChild(before);
const label = new KeybindingLabel(hintElement, OS);
const label = hintHandler.disposables.add(new KeybindingLabel(hintElement, OS));
label.set(keybindingHint);
label.element.style.width = 'min-content';
label.element.style.display = 'inline';
@@ -382,7 +384,7 @@ class EmptyTextEditorHintContentWidget implements IContentWidget {
anchor.style.cursor = 'pointer';
const id = keybindingsLookup.shift();
const title = id && this.keybindingService.lookupKeybinding(id)?.getLabel();
anchor.title = title ?? '';
hintHandler.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), anchor, title ?? ''));
}
return { hintElement, ariaLabel };
@@ -219,7 +219,7 @@ class DocumentSymbolsOutline implements IOutline<DocumentSymbolItem> {
return this._outlineModel?.uri;
}
async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean): Promise<void> {
async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean, select: boolean): Promise<void> {
const model = OutlineModel.get(entry);
if (!model || !(entry instanceof OutlineElement)) {
return;
@@ -228,7 +228,7 @@ class DocumentSymbolsOutline implements IOutline<DocumentSymbolItem> {
resource: model.uri,
options: {
...options,
selection: Range.collapseToStart(entry.symbol.selectionRange),
selection: select ? entry.symbol.range : Range.collapseToStart(entry.symbol.selectionRange),
selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport,
}
}, this._editor, sideBySide);
@@ -183,7 +183,7 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
picker.hide();
const [entry] = picker.selectedItems;
if (entry && entries[entry.index]) {
outline.reveal(entries[entry.index].element, {}, false);
outline.reveal(entries[entry.index].element, {}, false, false);
}
}));
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread } from 'vs/editor/common/languages';
import { CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread, CommentOptions, PendingCommentThread, CommentingRangeResourceHint } from 'vs/editor/common/languages';
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
@@ -104,7 +104,7 @@ export interface ICommentService {
disposeCommentThread(ownerId: string, threadId: string): void;
getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]>;
getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]>;
updateCommentingRanges(ownerId: string): void;
updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint): void;
hasReactionHandler(owner: string): boolean;
toggleReaction(owner: string, resource: URI, thread: CommentThread<IRange | ICellRange>, comment: Comment, reaction: CommentReaction): Promise<void>;
setActiveEditingCommentThread(commentThread: CommentThread<IRange | ICellRange> | null): void;
@@ -172,6 +172,7 @@ export class CommentService extends Disposable implements ICommentService {
public readonly commentsModel: ICommentsModel = this._commentsModel;
private _commentingRangeResources = new Set<string>(); // URIs
private _commentingRangeResourceHintSchemes = new Set<string>(); // schemes
constructor(
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@@ -406,7 +407,12 @@ export class CommentService extends Disposable implements ICommentService {
this._onDidUpdateNotebookCommentThreads.fire(evt);
}
updateCommentingRanges(ownerId: string) {
updateCommentingRanges(ownerId: string, resourceHints?: CommentingRangeResourceHint) {
if (resourceHints?.schemes && resourceHints.schemes.length > 0) {
for (const scheme of resourceHints.schemes) {
this._commentingRangeResourceHintSchemes.add(scheme);
}
}
this._workspaceHasCommenting.set(true);
this._onDidUpdateCommentingRanges.fire({ owner: ownerId });
}
@@ -518,6 +524,6 @@ export class CommentService extends Disposable implements ICommentService {
}
resourceHasCommentingRanges(resource: URI): boolean {
return this._commentingRangeResources.has(resource.toString());
return this._commentingRangeResourceHintSchemes.has(resource.scheme) || this._commentingRangeResources.has(resource.toString());
}
}
@@ -376,6 +376,7 @@ export class CommentController implements IEditorContribution {
private _commentThreadRangeDecorator!: CommentThreadRangeDecorator;
private mouseDownInfo: { lineNumber: number } | null = null;
private _commentingRangeSpaceReserved = false;
private _commentingRangeAmountReserved = 0;
private _computePromise: CancelablePromise<Array<ICommentInfo | null>> | null;
private _addInProgress!: boolean;
private _emptyThreadsToAddQueue: [Range | undefined, IEditorMouseEvent | undefined][] = [];
@@ -1179,15 +1180,20 @@ export class CommentController implements IEditorContribution {
return { extraEditorClassName, lineDecorationsWidth };
}
private getWithCommentsEditorOptions(editor: ICodeEditor, extraEditorClassName: string[], startingLineDecorationsWidth: number) {
private getWithCommentsLineDecorationWidth(editor: ICodeEditor, startingLineDecorationsWidth: number) {
let lineDecorationsWidth = startingLineDecorationsWidth;
const options = editor.getOptions();
if (options.get(EditorOption.folding) && options.get(EditorOption.showFoldingControls) !== 'never') {
lineDecorationsWidth -= 11;
}
lineDecorationsWidth += 24;
this._commentingRangeAmountReserved = lineDecorationsWidth;
return this._commentingRangeAmountReserved;
}
private getWithCommentsEditorOptions(editor: ICodeEditor, extraEditorClassName: string[], startingLineDecorationsWidth: number) {
extraEditorClassName.push('inline-comment');
return { lineDecorationsWidth, extraEditorClassName };
return { lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, startingLineDecorationsWidth), extraEditorClassName };
}
private updateEditorLayoutOptions(editor: ICodeEditor, extraEditorClassName: string[], lineDecorationsWidth: number) {
@@ -1197,6 +1203,15 @@ export class CommentController implements IEditorContribution {
});
}
private ensureCommentingRangeReservedAmount(editor: ICodeEditor) {
const existing = this.getExistingCommentEditorOptions(editor);
if (existing.lineDecorationsWidth !== this._commentingRangeAmountReserved) {
editor.updateOptions({
lineDecorationsWidth: this.getWithCommentsLineDecorationWidth(editor, existing.lineDecorationsWidth)
});
}
}
private tryUpdateReservedSpace(uri?: URI) {
if (!this.editor) {
return;
@@ -1211,11 +1226,15 @@ export class CommentController implements IEditorContribution {
const hasCommentsOrRanges = hasCommentsOrRangesInInfo || resourceHasCommentingRanges;
if (hasCommentsOrRanges && !this._commentingRangeSpaceReserved && this.commentService.isCommentingEnabled) {
this._commentingRangeSpaceReserved = true;
const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor);
const newOptions = this.getWithCommentsEditorOptions(this.editor, extraEditorClassName, lineDecorationsWidth);
this.updateEditorLayoutOptions(this.editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth);
if (hasCommentsOrRanges && this.commentService.isCommentingEnabled) {
if (!this._commentingRangeSpaceReserved) {
this._commentingRangeSpaceReserved = true;
const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor);
const newOptions = this.getWithCommentsEditorOptions(this.editor, extraEditorClassName, lineDecorationsWidth);
this.updateEditorLayoutOptions(this.editor, newOptions.extraEditorClassName, newOptions.lineDecorationsWidth);
} else {
this.ensureCommentingRangeReservedAmount(this.editor);
}
} else if ((!hasCommentsOrRanges || !this.commentService.isCommentingEnabled) && this._commentingRangeSpaceReserved) {
this._commentingRangeSpaceReserved = false;
const { lineDecorationsWidth, extraEditorClassName } = this.getExistingCommentEditorOptions(this.editor);
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { $, append, clearNode } from 'vs/base/browser/dom';
import { Emitter, Event } from 'vs/base/common/event';
import { ExtensionIdentifier, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
@@ -447,16 +447,18 @@ class ExtensionFeatureView extends Disposable {
private renderTableData(container: HTMLElement, renderer: IExtensionFeatureTableRenderer): void {
const tableData = this._register(renderer.render(this.manifest));
const tableDisposable = this._register(new MutableDisposable());
if (tableData.onDidChange) {
this._register(tableData.onDidChange(data => {
clearNode(container);
this.renderTable(data, container);
tableDisposable.value = this.renderTable(data, container);
}));
}
this.renderTable(tableData.data, container);
tableDisposable.value = this.renderTable(tableData.data, container);
}
private renderTable(tableData: ITableData, container: HTMLElement): void {
private renderTable(tableData: ITableData, container: HTMLElement): IDisposable {
const disposables = new DisposableStore();
append(container,
$('table', undefined,
$('tr', undefined,
@@ -478,7 +480,7 @@ class ExtensionFeatureView extends Disposable {
result.push(element);
} else if (item instanceof ResolvedKeybinding) {
const element = $('');
const kbl = new KeybindingLabel(element, OS, defaultKeybindingLabelStyles);
const kbl = disposables.add(new KeybindingLabel(element, OS, defaultKeybindingLabelStyles));
kbl.set(item);
result.push(element);
} else if (item instanceof Color) {
@@ -490,6 +492,7 @@ class ExtensionFeatureView extends Disposable {
})
);
})));
return disposables;
}
private renderMarkdownData(container: HTMLElement, renderer: IExtensionFeatureMarkdownRenderer): void {
@@ -52,7 +52,7 @@ import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUti
import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { mainWindow } from 'vs/base/browser/window';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IDialogService, IPromptButton } from 'vs/platform/dialogs/common/dialogs';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@@ -1650,17 +1650,19 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
}
async install(arg: string | URI | IExtension, installOptions: InstallExtensionOptions = {}, progressLocation?: ProgressLocation): Promise<IExtension> {
let installable: URI | { extension: IExtension; gallery: IGalleryExtension };
let installable: URI | IGalleryExtension | undefined;
let extension: IExtension | undefined;
if (arg instanceof URI) {
installable = arg;
} else {
let installableInfo: IExtensionInfo | undefined;
let gallery: IGalleryExtension | undefined;
let extension: IExtension | undefined;
if (isString(arg)) {
installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions?.installPreReleaseVersion ?? this.preferPreReleases };
extension = this.local.find(e => areSameExtensions(e.identifier, { id: arg }));
if (!extension?.isBuiltin) {
installableInfo = { id: arg, version: installOptions.version, preRelease: installOptions.installPreReleaseVersion ?? this.preferPreReleases };
}
} else {
extension = arg;
gallery = arg.gallery;
@@ -1672,47 +1674,44 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
const targetPlatform = extension?.server ? await extension.server.extensionManagementService.getTargetPlatform() : undefined;
gallery = firstOrDefault(await this.galleryService.getExtensions([installableInfo], { targetPlatform }, CancellationToken.None));
}
if (!gallery) {
const id = isString(arg) ? arg : (<IExtension>arg).identifier.id;
if (installOptions.version) {
throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version));
} else {
throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id));
}
}
if (!extension) {
if (!extension && gallery) {
extension = this.instantiationService.createInstance(Extension, ext => this.getExtensionState(ext), ext => this.getReloadStatus(ext), undefined, undefined, gallery);
Extensions.updateExtensionFromControlManifest(extension as Extension, await this.extensionManagementService.getExtensionsControlManifest());
}
if (extension?.isMalicious) {
throw new Error(nls.localize('malicious', "This extension is reported to be problematic."));
}
installable = { extension, gallery };
if (installOptions.version) {
installOptions.installGivenVersion = true;
// Do not install if requested to enable and extension is already installed
if (!(installOptions.enable && extension?.local)) {
if (!gallery) {
const id = isString(arg) ? arg : (<IExtension>arg).identifier.id;
if (installOptions.version) {
throw new Error(nls.localize('not found version', "Unable to install extension '{0}' because the requested version '{1}' is not found.", id, installOptions.version));
} else {
throw new Error(nls.localize('not found', "Unable to install extension '{0}' because it is not found.", id));
}
}
installable = gallery;
if (installOptions.version) {
installOptions.installGivenVersion = true;
}
}
}
let extension: IExtension;
if (installable instanceof URI || !(installOptions.enable && installable.extension.local)) {
if (installable) {
if (installOptions.justification) {
const syncCheck = isUndefined(installOptions.isMachineScoped) && this.userDataSyncEnablementService.isEnabled() && this.userDataSyncEnablementService.isResourceEnabled(SyncResource.Extensions);
const buttons: IPromptButton<boolean>[] = [];
buttons.push({ label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action), run: () => true });
if (!extension) {
buttons.push({ label: nls.localize('open', "Open Extension"), run: () => { this.open(extension!); return false; } });
}
const result = await this.dialogService.prompt<boolean>({
title: nls.localize('installExtensionTitle', "Install Extension"),
message: installable instanceof URI ? nls.localize('installVSIXMessage', "Would you like to install the extension?") : nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", installable.extension.displayName, installable.extension.publisherDisplayName),
message: extension ? nls.localize('installExtensionMessage', "Would you like to install '{0}' extension from '{1}'?", extension.displayName, extension.publisherDisplayName) : nls.localize('installVSIXMessage', "Would you like to install the extension?"),
detail: isString(installOptions.justification) ? installOptions.justification : installOptions.justification.reason,
cancelButton: true,
buttons: [{
label: isString(installOptions.justification) ? nls.localize({ key: 'installButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Install Extension") : nls.localize({ key: 'installButtonLabelWithAction', comment: ['&& denotes a mnemonic'] }, "&&Install Extension and {0}", installOptions.justification.action),
run: () => true
}, {
label: nls.localize('open', "Open Extension"),
run: () => {
if (!(installable instanceof URI)) {
this.open(installable.extension);
}
return false;
}
}],
buttons,
checkbox: syncCheck ? {
label: nls.localize('sync extension', "Sync this extension"),
checked: true,
@@ -1725,11 +1724,15 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
installOptions.isMachineScoped = !result.checkboxChecked;
}
}
extension = await this.doInstall(installable instanceof URI ? installable : installable.extension,
() => installable instanceof URI ? this.installFromVSIX(installable, installOptions) : this.installFromGallery(installable.extension, installable.gallery, installOptions),
progressLocation);
} else {
extension = installable.extension;
if (installable instanceof URI) {
extension = await this.doInstall(undefined, () => this.installFromVSIX(installable, installOptions), progressLocation);
} else if (extension) {
extension = await this.doInstall(extension, () => this.installFromGallery(extension!, installable, installOptions), progressLocation);
}
}
if (!extension) {
throw new Error(nls.localize('unknown', "Unable to install extension"));
}
if (installOptions.version) {
@@ -1902,21 +1905,21 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension
return extension;
}
private doInstall(extension: IExtension | URI, installTask: () => Promise<ILocalExtension>, progressLocation?: ProgressLocation): Promise<IExtension> {
const title = extension instanceof URI ? nls.localize('installing extension', 'Installing extension....') : nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName);
private doInstall(extension: IExtension | undefined, installTask: () => Promise<ILocalExtension>, progressLocation?: ProgressLocation): Promise<IExtension> {
const title = extension ? nls.localize('installing named extension', "Installing '{0}' extension....", extension.displayName) : nls.localize('installing extension', 'Installing extension....');
return this.withProgress({
location: progressLocation ?? ProgressLocation.Extensions,
title
}, async () => {
try {
if (!(extension instanceof URI)) {
if (extension) {
this.installing.push(extension);
this._onChange.fire(extension);
}
const local = await installTask();
return await this.waitAndGetInstalledExtension(local.identifier);
} finally {
if (!(extension instanceof URI)) {
if (extension) {
this.installing = this.installing.filter(e => e !== extension);
// Trigger the change without passing the extension because it is replaced by a new instance.
this._onChange.fire(undefined);
@@ -365,7 +365,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
const extension = page.firstPage[0];
assert.strictEqual(ExtensionState.Uninstalled, extension.state);
testObject.install(extension);
const identifier = gallery.identifier;
// Installing
@@ -450,7 +449,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
const extension = page.firstPage[0];
assert.strictEqual(ExtensionState.Uninstalled, extension.state);
testObject.install(extension);
installEvent.fire({ identifier: gallery.identifier, source: gallery });
const promise = Event.toPromise(testObject.onChange);
@@ -470,7 +468,6 @@ suite('ExtensionsWorkbenchServiceTest', () => {
const extension = page.firstPage[0];
assert.strictEqual(ExtensionState.Uninstalled, extension.state);
testObject.install(extension);
disposableStore.add(testObject.onChange(target));
// Installing
@@ -46,6 +46,8 @@
.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint {
position: absolute;
user-select: none;
display: flex;
align-items: center;
}
.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folded-hint-label {
@@ -55,6 +57,22 @@
opacity: 0.7;
}
.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button {
position: relative;
left: 0px;
padding: 2px;
border-radius: 5px;
margin-right: 4px;
height: 16px;
width: 16px;
z-index: var(--z-index-notebook-cell-expand-part-button);
}
.monaco-workbench .notebookOverlay > .cell-list-container .folded-cell-run-section-button:hover {
background-color: var(--vscode-editorStickyScrollHover-background);
cursor: pointer;
}
.monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-expanded,
.monaco-workbench .notebookOverlay .cell-editor-container .monaco-editor .margin-view-overlays .codicon-folding-collapsed {
margin-left: 0;
@@ -294,7 +294,7 @@ class CellStatusBarItem extends Disposable {
this._itemDisposables.clear();
if (!this._currentItem || this._currentItem.text !== item.text) {
new SimpleIconLabel(this.container).text = item.text.replace(/\n/g, ' ');
this._itemDisposables.add(new SimpleIconLabel(this.container)).text = item.text.replace(/\n/g, ' ');
}
const resolveColor = (color: ThemeColor | string) => {
@@ -11,12 +11,21 @@ import { FoldingController } from 'vs/workbench/contrib/notebook/browser/control
import { CellEditState, CellFoldingState, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cellPart';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { MutableDisposable } from 'vs/base/common/lifecycle';
export class FoldedCellHint extends CellContentPart {
private readonly _runButtonListener = this._register(new MutableDisposable());
private readonly _cellExecutionListener = this._register(new MutableDisposable());
constructor(
private readonly _notebookEditor: INotebookEditor,
private readonly _container: HTMLElement,
@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService
) {
super();
}
@@ -27,20 +36,27 @@ export class FoldedCellHint extends CellContentPart {
private update(element: MarkupCellViewModel) {
if (!this._notebookEditor.hasModel()) {
this._cellExecutionListener.clear();
this._runButtonListener.clear();
return;
}
if (element.isInputCollapsed || element.getEditState() === CellEditState.Editing) {
this._cellExecutionListener.clear();
this._runButtonListener.clear();
DOM.hide(this._container);
} else if (element.foldingState === CellFoldingState.Collapsed) {
const idx = this._notebookEditor.getViewModel().getCellIndex(element);
const length = this._notebookEditor.getViewModel().getFoldedLength(idx);
DOM.reset(this._container, this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element));
DOM.reset(this._container, this.getRunFoldedSectionButton({ start: idx, end: idx + length }), this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element));
DOM.show(this._container);
const foldHintTop = element.layoutInfo.previewHeight;
this._container.style.top = `${foldHintTop}px`;
} else {
this._cellExecutionListener.clear();
this._runButtonListener.clear();
DOM.hide(this._container);
}
}
@@ -67,6 +83,40 @@ export class FoldedCellHint extends CellContentPart {
return expandIcon;
}
private getRunFoldedSectionButton(range: ICellRange): HTMLElement {
const runAllContainer = DOM.$('span.folded-cell-run-section-button');
const cells = this._notebookEditor.getCellsInRange(range);
const isRunning = cells.some(cell => {
const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri);
return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing;
});
const runAllIcon = isRunning ?
ThemeIcon.modify(executingStateIcon, 'spin') :
Codicon.play;
runAllContainer.classList.add(...ThemeIcon.asClassNameArray(runAllIcon));
this._runButtonListener.value = DOM.addDisposableListener(runAllContainer, DOM.EventType.CLICK, () => {
this._notebookEditor.executeNotebookCells(cells);
});
this._cellExecutionListener.value = this._notebookExecutionStateService.onDidChangeExecution(() => {
const isRunning = cells.some(cell => {
const cellExecution = this._notebookExecutionStateService.getCellExecution(cell.uri);
return cellExecution && cellExecution.state === NotebookCellExecutionState.Executing;
});
const runAllIcon = isRunning ?
ThemeIcon.modify(executingStateIcon, 'spin') :
Codicon.play;
runAllContainer.className = '';
runAllContainer.classList.add('folded-cell-run-section-button', ...ThemeIcon.asClassNameArray(runAllIcon));
});
return runAllContainer;
}
override updateInternalLayoutNow(element: MarkupCellViewModel) {
this.update(element);
}
@@ -49,6 +49,7 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl';
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
const $ = DOM.$;
@@ -109,6 +110,8 @@ abstract class AbstractCellRenderer {
export class MarkupCellRenderer extends AbstractCellRenderer implements IListRenderer<MarkupCellViewModel, MarkdownCellRenderTemplate> {
static readonly TEMPLATE_ID = 'markdown_cell';
private _notebookExecutionStateService: INotebookExecutionStateService;
constructor(
notebookEditor: INotebookEditorDelegate,
dndController: CellDragAndDropController,
@@ -120,8 +123,10 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
@IMenuService menuService: IMenuService,
@IKeybindingService keybindingService: IKeybindingService,
@INotificationService notificationService: INotificationService,
@INotebookExecutionStateService notebookExecutionStateService: INotebookExecutionStateService
) {
super(instantiationService, notebookEditor, contextMenuService, menuService, configurationService, keybindingService, notificationService, contextKeyServiceProvider, 'markdown', dndController);
this._notebookExecutionStateService = notebookExecutionStateService;
}
get templateId() {
@@ -169,7 +174,7 @@ export class MarkupCellRenderer extends AbstractCellRenderer implements IListRen
templateDisposables.add(scopedInstaService.createInstance(CellChatPart, this.notebookEditor, cellChatPart)),
templateDisposables.add(scopedInstaService.createInstance(CellEditorStatusBar, this.notebookEditor, container, editorPart, undefined)),
templateDisposables.add(new CellFocusIndicator(this.notebookEditor, titleToolbar, focusIndicatorTop, focusIndicatorLeft, focusIndicatorRight, focusIndicatorBottom)),
templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')))),
templateDisposables.add(new FoldedCellHint(this.notebookEditor, DOM.append(container, $('.notebook-folded-hint')), this._notebookExecutionStateService)),
templateDisposables.add(new CellDecorations(rootContainer, decorationContainer)),
templateDisposables.add(scopedInstaService.createInstance(CellComments, this.notebookEditor, cellCommentPartContainer)),
templateDisposables.add(new CollapsedCellInput(this.notebookEditor, cellInputCollapsedContainer)),
@@ -6,7 +6,7 @@
import 'vs/css!./outlinePane';
import * as dom from 'vs/base/browser/dom';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { TimeoutTimer } from 'vs/base/common/async';
import { TimeoutTimer, timeout } from 'vs/base/common/async';
import { IDisposable, toDisposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { LRUCache } from 'vs/base/common/map';
import { localize } from 'vs/nls';
@@ -304,7 +304,19 @@ export class OutlinePane extends ViewPane implements IOutlinePane {
// feature: reveal outline selection in editor
// on change -> reveal/select defining range
this._editorControlDisposables.add(tree.onDidOpen(e => newOutline.reveal(e.element, e.editorOptions, e.sideBySide)));
let idPool = 0;
this._editorControlDisposables.add(tree.onDidOpen(async e => {
const myId = ++idPool;
const isDoubleClick = e.browserEvent?.type === 'dblclick';
if (!isDoubleClick) {
// workaround for https://github.com/microsoft/vscode/issues/206424
await timeout(150);
if (myId !== idPool) {
return;
}
}
await newOutline.reveal(e.element, e.editorOptions, e.sideBySide, isDoubleClick);
}));
// feature: reveal editor selection in outline
const revealActiveElement = () => {
if (!this._outlineViewState.followCursor || !newOutline.activeElement) {
@@ -141,6 +141,7 @@ export class DefineKeybindingWidget extends Widget {
private _keybindingInputWidget: KeybindingsSearchWidget;
private _outputNode: HTMLElement;
private _showExistingKeybindingsNode: HTMLElement;
private _keybindingDisposables = this._register(new DisposableStore());
private _chords: ResolvedKeybinding[] | null = null;
private _isVisible: boolean = false;
@@ -238,17 +239,18 @@ export class DefineKeybindingWidget extends Widget {
}
private onKeybinding(keybinding: ResolvedKeybinding[] | null): void {
this._keybindingDisposables.clear();
this._chords = keybinding;
dom.clearNode(this._outputNode);
dom.clearNode(this._showExistingKeybindingsNode);
const firstLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles);
const firstLabel = this._keybindingDisposables.add(new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles));
firstLabel.set(this._chords?.[0] ?? undefined);
if (this._chords) {
for (let i = 1; i < this._chords.length; i++) {
this._outputNode.appendChild(document.createTextNode(nls.localize('defineKeybinding.chordsTo', "chord to")));
const chordLabel = new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles);
const chordLabel = this._keybindingDisposables.add(new KeybindingLabel(this._outputNode, OS, defaultKeybindingLabelStyles));
chordLabel.set(this._chords[i]);
}
}
@@ -58,6 +58,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands';
import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
const $ = DOM.$;
@@ -897,6 +899,7 @@ class ActionsColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IAct
interface ICommandColumnTemplateData {
commandColumn: HTMLElement;
commandColumnHover: ICustomHover;
commandLabelContainer: HTMLElement;
commandLabel: HighlightedLabel;
commandDefaultLabelContainer: HTMLElement;
@@ -913,13 +916,14 @@ class CommandColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ICom
renderTemplate(container: HTMLElement): ICommandColumnTemplateData {
const commandColumn = DOM.append(container, $('.command'));
const commandColumnHover = setupCustomHover(getDefaultHoverDelegate('mouse'), commandColumn, '');
const commandLabelContainer = DOM.append(commandColumn, $('.command-label'));
const commandLabel = new HighlightedLabel(commandLabelContainer);
const commandDefaultLabelContainer = DOM.append(commandColumn, $('.command-default-label'));
const commandDefaultLabel = new HighlightedLabel(commandDefaultLabelContainer);
const commandIdLabelContainer = DOM.append(commandColumn, $('.command-id.code'));
const commandIdLabel = new HighlightedLabel(commandIdLabelContainer);
return { commandColumn, commandLabelContainer, commandLabel, commandDefaultLabelContainer, commandDefaultLabel, commandIdLabelContainer, commandIdLabel };
return { commandColumn, commandColumnHover, commandLabelContainer, commandLabel, commandDefaultLabelContainer, commandDefaultLabel, commandIdLabelContainer, commandIdLabel };
}
renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ICommandColumnTemplateData, height: number | undefined): void {
@@ -928,7 +932,9 @@ class CommandColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ICom
const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches;
templateData.commandColumn.classList.toggle('vertical-align-column', commandIdMatched || commandDefaultLabelMatched);
templateData.commandColumn.title = keybindingItem.commandLabel ? localize('title', "{0} ({1})", keybindingItem.commandLabel, keybindingItem.command) : keybindingItem.command;
const title = keybindingItem.commandLabel ? localize('title', "{0} ({1})", keybindingItem.commandLabel, keybindingItem.command) : keybindingItem.command;
templateData.commandColumn.setAttribute('aria-label', title);
templateData.commandColumnHover.update(title);
if (keybindingItem.commandLabel) {
templateData.commandLabelContainer.classList.remove('hide');
@@ -956,6 +962,7 @@ class CommandColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ICom
}
disposeTemplate(templateData: ICommandColumnTemplateData): void {
templateData.commandColumnHover.dispose();
templateData.commandDefaultLabel.dispose();
templateData.commandIdLabel.dispose();
templateData.commandLabel.dispose();
@@ -989,11 +996,13 @@ class KeybindingColumnRenderer implements ITableRenderer<IKeybindingItemEntry, I
}
disposeTemplate(templateData: IKeybindingColumnTemplateData): void {
templateData.keybindingLabel.dispose();
}
}
interface ISourceColumnTemplateData {
sourceColumn: HTMLElement;
sourceColumnHover: ICustomHover;
sourceLabel: HighlightedLabel;
extensionContainer: HTMLElement;
extensionLabel: HTMLAnchorElement;
@@ -1027,11 +1036,12 @@ class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISour
renderTemplate(container: HTMLElement): ISourceColumnTemplateData {
const sourceColumn = DOM.append(container, $('.source'));
const sourceColumnHover = setupCustomHover(getDefaultHoverDelegate('mouse'), sourceColumn, '');
const sourceLabel = new HighlightedLabel(DOM.append(sourceColumn, $('.source-label')));
const extensionContainer = DOM.append(sourceColumn, $('.extension-container'));
const extensionLabel = DOM.append<HTMLAnchorElement>(extensionContainer, $('a.extension-label', { tabindex: 0 }));
const extensionId = new HighlightedLabel(DOM.append(extensionContainer, $('.extension-id-container.code')));
return { sourceColumn, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() };
return { sourceColumn, sourceColumnHover, sourceLabel, extensionLabel, extensionContainer, extensionId, disposables: new DisposableStore() };
}
renderElement(keybindingItemEntry: IKeybindingItemEntry, index: number, templateData: ISourceColumnTemplateData, height: number | undefined): void {
@@ -1039,14 +1049,14 @@ class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISour
if (isString(keybindingItemEntry.keybindingItem.source)) {
templateData.extensionContainer.classList.add('hide');
templateData.sourceLabel.element.classList.remove('hide');
templateData.sourceColumn.title = '';
templateData.sourceColumnHover.update('');
templateData.sourceLabel.set(keybindingItemEntry.keybindingItem.source || '-', keybindingItemEntry.sourceMatches);
} else {
templateData.extensionContainer.classList.remove('hide');
templateData.sourceLabel.element.classList.add('hide');
const extension = keybindingItemEntry.keybindingItem.source;
const extensionLabel = extension.displayName ?? extension.identifier.value;
templateData.sourceColumn.title = localize('extension label', "Extension ({0})", extensionLabel);
templateData.sourceColumnHover.update(localize('extension label', "Extension ({0})", extensionLabel));
templateData.extensionLabel.textContent = extensionLabel;
templateData.disposables.add(onClick(templateData.extensionLabel, () => {
this.extensionsWorkbenchService.open(extension.identifier.value);
@@ -1062,6 +1072,7 @@ class SourceColumnRenderer implements ITableRenderer<IKeybindingItemEntry, ISour
}
disposeTemplate(templateData: ISourceColumnTemplateData): void {
templateData.sourceColumnHover.dispose();
templateData.disposables.dispose();
templateData.sourceLabel.dispose();
templateData.extensionId.dispose();
@@ -1137,12 +1148,10 @@ class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenCo
) { }
renderTemplate(container: HTMLElement): IWhenColumnTemplateData {
const disposables = new DisposableStore();
const element = DOM.append(container, $('.when'));
const whenLabelContainer = DOM.append(element, $('div.when-label'));
const whenLabel = disposables.add(new HighlightedLabel(whenLabelContainer));
const whenLabel = new HighlightedLabel(whenLabelContainer);
const whenInputContainer = DOM.append(element, $('div.when-input-container'));
@@ -1151,7 +1160,7 @@ class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenCo
whenLabelContainer,
whenLabel,
whenInputContainer,
disposables,
disposables: new DisposableStore(),
};
}
@@ -1193,16 +1202,15 @@ class WhenColumnRenderer implements ITableRenderer<IKeybindingItemEntry, IWhenCo
if (keybindingItemEntry.keybindingItem.when) {
templateData.whenLabel.set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches, keybindingItemEntry.keybindingItem.when);
templateData.element.title = keybindingItemEntry.keybindingItem.when;
templateData.disposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), templateData.element, keybindingItemEntry.keybindingItem.when));
} else {
templateData.whenLabel.set('-');
templateData.element.title = '';
}
}
disposeTemplate(templateData: IWhenColumnTemplateData): void {
templateData.disposables.dispose();
templateData.whenLabel.dispose();
}
}
@@ -34,6 +34,8 @@ import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, Workbenc
import { settingsEditIcon, settingsScopeDropDownIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
export class FolderSettingsActionViewItem extends BaseActionViewItem {
private _folder: IWorkspaceFolder | null;
@@ -41,6 +43,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
private container!: HTMLElement;
private anchorElement!: HTMLElement;
private anchorElementHover!: ICustomHover;
private labelElement!: HTMLElement;
private detailsElement!: HTMLElement;
private dropDownElement!: HTMLElement;
@@ -87,6 +90,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
'aria-haspopup': 'true',
'tabindex': '0'
}, this.labelElement, this.detailsElement, this.dropDownElement);
this.anchorElementHover = setupCustomHover(getDefaultHoverDelegate('mouse'), this.anchorElement, '');
this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.MOUSE_DOWN, e => DOM.EventHelper.stop(e)));
this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.CLICK, e => this.onClick(e)));
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, e => this.onKeyUp(e)));
@@ -145,7 +149,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
const workspace = this.contextService.getWorkspace();
if (this._folder) {
this.labelElement.textContent = this._folder.name;
this.anchorElement.title = this._folder.name;
this.anchorElementHover.update(this._folder.name);
const detailsText = this.labelWithCount(this._action.label, total);
this.detailsElement.textContent = detailsText;
this.dropDownElement.classList.toggle('hide', workspace.folders.length === 1 || !this._action.checked);
@@ -153,7 +157,7 @@ export class FolderSettingsActionViewItem extends BaseActionViewItem {
const labelText = this.labelWithCount(this._action.label, total);
this.labelElement.textContent = labelText;
this.detailsElement.textContent = '';
this.anchorElement.title = this._action.label;
this.anchorElementHover.update(this._action.label);
this.dropDownElement.classList.remove('hide');
}
@@ -135,12 +135,12 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}
private createWorkspaceTrustIndicator(): SettingIndicator {
const disposables = new DisposableStore();
const workspaceTrustElement = $('span.setting-indicator.setting-item-workspace-trust');
const workspaceTrustLabel = new SimpleIconLabel(workspaceTrustElement);
const workspaceTrustLabel = disposables.add(new SimpleIconLabel(workspaceTrustElement));
workspaceTrustLabel.text = '$(warning) ' + localize('workspaceUntrustedLabel', "Setting value not applied");
const content = localize('trustLabel', "The setting value can only be applied in a trusted workspace.");
const disposables = new DisposableStore();
const showHover = (focus: boolean) => {
return this.hoverService.showHover({
...this.defaultHoverOptions,
@@ -164,23 +164,24 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
}
private createScopeOverridesIndicator(): SettingIndicator {
const disposables = new DisposableStore();
// Don't add .setting-indicator class here, because it gets conditionally added later.
const otherOverridesElement = $('span.setting-item-overrides');
const otherOverridesLabel = new SimpleIconLabel(otherOverridesElement);
const otherOverridesLabel = disposables.add(new SimpleIconLabel(otherOverridesElement));
return {
element: otherOverridesElement,
label: otherOverridesLabel,
disposables: new DisposableStore()
disposables
};
}
private createSyncIgnoredIndicator(): SettingIndicator {
const disposables = new DisposableStore();
const syncIgnoredElement = $('span.setting-indicator.setting-item-ignored');
const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement);
const syncIgnoredLabel = disposables.add(new SimpleIconLabel(syncIgnoredElement));
syncIgnoredLabel.text = localize('extensionSyncIgnoredLabel', 'Not synced');
const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync");
const disposables = new DisposableStore();
const showHover = (focus: boolean) => {
return this.hoverService.showHover({
...this.defaultHoverOptions,
@@ -193,19 +194,20 @@ export class SettingsTreeIndicatorsLabel implements IDisposable {
return {
element: syncIgnoredElement,
label: syncIgnoredLabel,
disposables: new DisposableStore()
disposables
};
}
private createDefaultOverrideIndicator(): SettingIndicator {
const disposables = new DisposableStore();
const defaultOverrideIndicator = $('span.setting-indicator.setting-item-default-overridden');
const defaultOverrideLabel = new SimpleIconLabel(defaultOverrideIndicator);
const defaultOverrideLabel = disposables.add(new SimpleIconLabel(defaultOverrideIndicator));
defaultOverrideLabel.text = localize('defaultOverriddenLabel', "Default value changed");
return {
element: defaultOverrideIndicator,
label: defaultOverrideLabel,
disposables: new DisposableStore()
disposables
};
}
@@ -69,6 +69,8 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
import { getInvalidTypeError } from 'vs/workbench/services/preferences/common/preferencesValidation';
import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
const $ = DOM.$;
@@ -796,13 +798,13 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
const labelCategoryContainer = DOM.append(titleElement, $('.setting-item-cat-label-container'));
const categoryElement = DOM.append(labelCategoryContainer, $('span.setting-item-category'));
const labelElementContainer = DOM.append(labelCategoryContainer, $('span.setting-item-label'));
const labelElement = new SimpleIconLabel(labelElementContainer);
const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));
const indicatorsLabel = this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement);
toDispose.add(indicatorsLabel);
const descriptionElement = DOM.append(container, $('.setting-item-description'));
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope.");
toDispose.add(setupCustomHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, () => localize('modified', "The setting has been configured in the current scope.")));
const valueElement = DOM.append(container, $('.setting-item-value'));
const controlElement = DOM.append(valueElement, $('div.setting-item-control'));
@@ -889,7 +891,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');
template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : '';
template.categoryElement.title = titleTooltip;
template.elementDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.categoryElement, titleTooltip));
template.labelElement.text = element.displayLabel;
template.labelElement.title = titleTooltip;
@@ -1817,24 +1819,25 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre
_container.classList.add('setting-item');
_container.classList.add('setting-item-bool');
const toDispose = new DisposableStore();
const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR));
container.classList.add('settings-row-inner-container');
const titleElement = DOM.append(container, $('.setting-item-title'));
const categoryElement = DOM.append(titleElement, $('span.setting-item-category'));
const labelElementContainer = DOM.append(titleElement, $('span.setting-item-label'));
const labelElement = new SimpleIconLabel(labelElementContainer);
const labelElement = toDispose.add(new SimpleIconLabel(labelElementContainer));
const indicatorsLabel = this._instantiationService.createInstance(SettingsTreeIndicatorsLabel, titleElement);
const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description'));
const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control'));
const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description'));
const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator'));
modifiedIndicatorElement.title = localize('modified', "The setting has been configured in the current scope.");
toDispose.add(setupCustomHover(getDefaultHoverDelegate('mouse'), modifiedIndicatorElement, localize('modified', "The setting has been configured in the current scope.")));
const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message'));
const toDispose = new DisposableStore();
const checkbox = new Toggle({ icon: Codicon.check, actionClassName: 'setting-value-checkbox', isChecked: true, title: '', ...unthemedToggleStyles });
controlElement.appendChild(checkbox.domNode);
toDispose.add(checkbox);
@@ -27,6 +27,8 @@ import { ThemeIcon } from 'vs/base/common/themables';
import { settingsDiscardIcon, settingsEditIcon, settingsRemoveIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { defaultButtonStyles, getInputBoxStyle, getSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
const $ = DOM.$;
@@ -673,8 +675,8 @@ export class ListSettingWidget extends AbstractListSettingWidget<IListDataItem>
: localize('listSiblingHintLabel', "List item `{0}` with sibling `${1}`", value.data, sibling);
const { rowElement } = rowElementGroup;
rowElement.title = title;
rowElement.setAttribute('aria-label', rowElement.title);
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title));
rowElement.setAttribute('aria-label', title);
}
protected getLocalizedStrings() {
@@ -733,8 +735,8 @@ export class ExcludeSettingWidget extends ListSettingWidget {
: localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling);
const { rowElement } = rowElementGroup;
rowElement.title = title;
rowElement.setAttribute('aria-label', rowElement.title);
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title));
rowElement.setAttribute('aria-label', title);
}
protected override getLocalizedStrings() {
@@ -763,8 +765,8 @@ export class IncludeSettingWidget extends ListSettingWidget {
: localize('includeSiblingHintLabel', "Include files matching `{0}`, only when a file matching `{1}` is present", value.data, sibling);
const { rowElement } = rowElementGroup;
rowElement.title = title;
rowElement.setAttribute('aria-label', rowElement.title);
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), rowElement, title));
rowElement.setAttribute('aria-label', title);
}
protected override getLocalizedStrings() {
@@ -1161,10 +1163,10 @@ export class ObjectSettingDropdownWidget extends AbstractListSettingWidget<IObje
const accessibleDescription = localize('objectPairHintLabel', "The property `{0}` is set to `{1}`.", item.key.data, item.value.data);
const keyDescription = this.getEnumDescription(item.key) ?? item.keyDescription ?? accessibleDescription;
keyElement.title = keyDescription;
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), keyElement, keyDescription));
const valueDescription = this.getEnumDescription(item.value) ?? accessibleDescription;
valueElement!.title = valueDescription;
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), valueElement!, valueDescription));
rowElement.setAttribute('aria-label', accessibleDescription);
}
@@ -1315,7 +1317,7 @@ export class ObjectSettingCheckboxWidget extends AbstractListSettingWidget<IObje
const title = item.keyDescription ?? accessibleDescription;
const { rowElement, keyElement, valueElement } = rowElementGroup;
keyElement.title = title;
this.listDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), keyElement, title));
valueElement!.setAttribute('aria-label', accessibleDescription);
rowElement.setAttribute('aria-label', accessibleDescription);
}
@@ -4,11 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as DOM from 'vs/base/browser/dom';
import { getDefaultHoverDelegate } from 'vs/base/browser/ui/hover/hoverDelegateFactory';
import { setupCustomHover } from 'vs/base/browser/ui/hover/updatableHoverWidget';
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
import { Iterable } from 'vs/base/common/iterator';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -102,6 +105,7 @@ const TOC_ENTRY_TEMPLATE_ID = 'settings.toc.entry';
interface ITOCEntryTemplate {
labelElement: HTMLElement;
countElement: HTMLElement;
elementDisposables: DisposableStore;
}
export class TOCRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, ITOCEntryTemplate> {
@@ -111,17 +115,20 @@ export class TOCRenderer implements ITreeRenderer<SettingsTreeGroupElement, neve
renderTemplate(container: HTMLElement): ITOCEntryTemplate {
return {
labelElement: DOM.append(container, $('.settings-toc-entry')),
countElement: DOM.append(container, $('.settings-toc-count'))
countElement: DOM.append(container, $('.settings-toc-count')),
elementDisposables: new DisposableStore()
};
}
renderElement(node: ITreeNode<SettingsTreeGroupElement>, index: number, template: ITOCEntryTemplate): void {
template.elementDisposables.clear();
const element = node.element;
const count = element.count;
const label = element.label;
template.labelElement.textContent = label;
template.labelElement.title = label;
template.elementDisposables.add(setupCustomHover(getDefaultHoverDelegate('mouse'), template.labelElement, label));
if (count) {
template.countElement.textContent = ` (${count})`;
@@ -131,6 +138,7 @@ export class TOCRenderer implements ITreeRenderer<SettingsTreeGroupElement, neve
}
disposeTemplate(templateData: ITOCEntryTemplate): void {
templateData.elementDisposables.dispose();
}
}
@@ -441,7 +441,7 @@ export class WatchingProblemCollector extends AbstractProblemCollector implement
}, 500, false, true)(async (markerEvent) => {
markerChanged?.dispose();
markerChanged = undefined;
if (!markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) {
if (!markerEvent || !markerEvent.includes(modelEvent.uri) || (this.markerService.read({ resource: modelEvent.uri }).length !== 0)) {
return;
}
const oldLines = Array.from(this.lines);
@@ -154,6 +154,16 @@ export class TerminalLinkQuickpick extends DisposableStore {
r();
}));
disposables.add(Event.once(pick.onDidAccept)(() => {
// Restore terminal scroll state
if (this._terminalScrollStateSaved) {
const markTracker = this._instance?.xterm?.markTracker;
if (markTracker) {
markTracker.restoreScrollState();
markTracker.clear();
this._terminalScrollStateSaved = false;
}
}
accepted = true;
const event = new TerminalLinkQuickPickEvent(EventType.CLICK);
const activeItem = pick.activeItems?.[0];
@@ -8,7 +8,7 @@ import type { IBufferLine, IMarker, ITerminalOptions, ITheme, Terminal as RawXte
import { importAMDNodeModule } from 'vs/amdX';
import { $, addDisposableListener, addStandardDisposableListener, getWindow } from 'vs/base/browser/dom';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { debounce, memoize, throttle } from 'vs/base/common/decorators';
import { memoize, throttle } from 'vs/base/common/decorators';
import { Event } from 'vs/base/common/event';
import { Disposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
@@ -193,17 +193,18 @@ export class TerminalStickyScrollOverlay extends Disposable {
if (command && this._currentStickyCommand !== command) {
this._throttledRefresh();
} else {
this._debouncedRefresh();
// If it's the same command, do not throttle as the sticky scroll overlay height may
// need to be adjusted. This would cause a flicker if throttled.
this._refreshNow();
}
}
@debounce(20)
private _debouncedRefresh(): void {
this._throttledRefresh();
}
@throttle(0)
private _throttledRefresh(): void {
this._refreshNow();
}
private _refreshNow(): void {
const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY);
// The command from viewportY + 1 is used because this one will not be obscured by sticky
@@ -245,6 +246,12 @@ export class TerminalStickyScrollOverlay extends Disposable {
return;
}
// Hide sticky scroll if the prompt has been trimmed from the buffer
if (command.promptStartMarker?.line === -1) {
this._setVisible(false);
return;
}
// Determine sticky scroll line count
const buffer = xterm.buffer.active;
const promptRowCount = command.getPromptRowCount();
@@ -281,7 +288,7 @@ export class TerminalStickyScrollOverlay extends Disposable {
}
}
// Clear attrs, reset cursor position, clear right
// Get the line content of the command from the terminal
const content = this._serializeAddon.serialize({
range: {
start: stickyScrollLineStart + rowOffset,
@@ -299,6 +306,7 @@ export class TerminalStickyScrollOverlay extends Disposable {
// Write content if it differs
if (content && this._currentContent !== content) {
this._stickyScrollOverlay.resize(this._stickyScrollOverlay.cols, stickyScrollLineCount);
// Clear attrs, reset cursor position, clear right
this._stickyScrollOverlay.write('\x1b[0m\x1b[H\x1b[2J');
this._stickyScrollOverlay.write(content);
this._currentContent = content;
@@ -317,7 +325,18 @@ export class TerminalStickyScrollOverlay extends Disposable {
const termBox = xterm.element.getBoundingClientRect();
const rowHeight = termBox.height / xterm.rows;
const overlayHeight = stickyScrollLineCount * rowHeight;
this._element.style.bottom = `${termBox.height - overlayHeight + 1}px`;
// Adjust sticky scroll content if it would below the end of the command, obscuring the
// following command.
let endMarkerOffset = 0;
if (!isPartialCommand && command.endMarker && command.endMarker.line !== -1) {
if (buffer.viewportY + stickyScrollLineCount > command.endMarker.line) {
const diff = buffer.viewportY + stickyScrollLineCount - command.endMarker.line;
endMarkerOffset = diff * rowHeight;
}
}
this._element.style.bottom = `${termBox.height - overlayHeight + 1 + endMarkerOffset}px`;
}
} else {
this._setVisible(false);
@@ -376,6 +395,9 @@ export class TerminalStickyScrollOverlay extends Disposable {
}
}));
// Forward mouse events to the terminal
this._register(addStandardDisposableListener(hoverOverlay, 'wheel', e => this._xterm?.raw.element?.dispatchEvent(new WheelEvent(e.type, e))));
// Context menu - stop propagation on mousedown because rightClickBehavior listens on
// mousedown, not contextmenu
this._register(addDisposableListener(hoverOverlay, 'mousedown', e => {
@@ -21,6 +21,7 @@ export const allApiProposals = Object.freeze({
codeActionRanges: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codeActionRanges.d.ts',
codiconDecoration: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.codiconDecoration.d.ts',
commentReactor: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentReactor.d.ts',
commentingRangeHint: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentingRangeHint.d.ts',
commentsDraftState: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.commentsDraftState.d.ts',
contribCommentEditorActionsMenu: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentEditorActionsMenu.d.ts',
contribCommentPeekContext: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.contribCommentPeekContext.d.ts',
@@ -83,7 +83,7 @@ export interface IOutline<E> {
readonly activeElement: E | undefined;
readonly onDidChange: Event<OutlineChangeEvent>;
reveal(entry: E, options: IEditorOptions, sideBySide: boolean): Promise<void> | void;
reveal(entry: E, options: IEditorOptions, sideBySide: boolean, select: boolean): Promise<void> | void;
preview(entry: E): IDisposable;
captureViewState(): IDisposable;
dispose(): void;
+16
View File
@@ -0,0 +1,16 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// @alexr00 https://github.com/microsoft/vscode/issues/185551
/**
* Commenting range provider for a {@link CommentController comment controller}.
*/
export interface CommentingRangeProvider {
readonly resourceHints?: { schemes: readonly string[] };
}
}
+3 -3
View File
@@ -22,13 +22,13 @@ export class KeybindingsEditor {
await this.code.waitForSetValue(SEARCH_INPUT, `@command:${command}`);
const commandTitle = commandName ? `${commandName} (${command})` : command;
await this.code.waitAndClick(`.keybindings-table-container .monaco-list-row .command[title="${commandTitle}"]`);
await this.code.waitForElement(`.keybindings-table-container .monaco-list-row.focused.selected .command[title="${commandTitle}"]`);
await this.code.waitAndClick(`.keybindings-table-container .monaco-list-row .command[aria-label="${commandTitle}"]`);
await this.code.waitForElement(`.keybindings-table-container .monaco-list-row.focused.selected .command[aria-label="${commandTitle}"]`);
await this.code.dispatchKeybinding('enter');
await this.code.waitForActiveElement('.defineKeybindingWidget .monaco-inputbox input');
await this.code.dispatchKeybinding(keybinding);
await this.code.dispatchKeybinding('enter');
await this.code.waitForElement(`.keybindings-table-container .keybinding-label div[title="${keybindingTitle}"]`);
await this.code.waitForElement(`.keybindings-table-container .keybinding-label div[aria-label="${keybindingTitle}"]`);
}
}