diff --git a/.github/commands.yml b/.github/commands.yml index 8076f05fd1f66dd9beb1b9dbd548f635afa276ec..ba8c8d7f3ceb08b946ee63b9a9cc77491e491840 100644 --- a/.github/commands.yml +++ b/.github/commands.yml @@ -56,5 +56,11 @@ action: 'updateLabels', addLabel: 'confirmed' }, + { + type: 'comment', + name: 'findDuplicates', + action: 'comment', + comment: "Potential duplicates:\n${potentialDuplicates}" + }, ] } diff --git a/.github/similarity.yml b/.github/similarity.yml index 4ec8e6cc7d4218b0a19e4a7aa0ff174cacf9bb1a..cd51cd2da64f4e7a053984846351018d94ddba3a 100644 --- a/.github/similarity.yml +++ b/.github/similarity.yml @@ -1,5 +1,5 @@ { perform: true, whenCreatedByTeam: false, - comment: "Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" + comment: "(Experimental duplicate detection)\nThanks for submitting this issue. Please also check if it is already covered by an existing one, like:\n${potentialDuplicates}" } diff --git a/extensions/markdown/media/main.js b/extensions/markdown/media/main.js index 53ac317d8517aebe86f6138ce929046957f4d0b6..d75f5940800e89108d7a0e302793c322aa27e9ca 100644 --- a/extensions/markdown/media/main.js +++ b/extensions/markdown/media/main.js @@ -155,9 +155,10 @@ if (previous) { if (next) { const betweenProgress = (offset - window.scrollY - previous.element.getBoundingClientRect().top) / (next.element.getBoundingClientRect().top - previous.element.getBoundingClientRect().top); - return previous.line + betweenProgress * (next.line - previous.line); + const line = previous.line + betweenProgress * (next.line - previous.line); + return Math.max(line, 0); } else { - return previous.line; + return Math.max(previous.line, 0); } } return null; @@ -232,7 +233,7 @@ } // Ignore clicks on links - for (let node = event.target; node; node = node.parentNode) { + for (let node = /** @type {HTMLElement} */(event.target); node; node = /** @type {HTMLElement} */(node.parentNode)) { if (node.tagName === "A") { return; } @@ -261,7 +262,7 @@ } if (node.href.startsWith('file://')) { const [path, fragment] = node.href.replace(/^file:\/\//i, '').split('#'); - postMessage('_markdown.openDocumentLink', { path, fragment }); + postMessage('_markdown.openDocumentLink', [{ path, fragment }]); event.preventDefault(); event.stopPropagation(); break; diff --git a/extensions/typescript/src/features/jsDocCompletionProvider.ts b/extensions/typescript/src/features/jsDocCompletionProvider.ts index ab515a9a07462645867b1e19ad512ff499355ac8..34963657b2858ba096a2162b41ed98125a0d2756 100644 --- a/extensions/typescript/src/features/jsDocCompletionProvider.ts +++ b/extensions/typescript/src/features/jsDocCompletionProvider.ts @@ -170,6 +170,12 @@ class TryCompleteJsDocCommand implements Command { if (!res || !res.body) { return undefined; } + // Workaround for #43619 + // docCommentTemplate previously returned undefined for empty jsdoc templates. + // TS 2.7 now returns a single line doc comment, which breaks indentation. + if (res.body.newText === '/** */') { + return undefined; + } return TryCompleteJsDocCommand.templateToSnippet(res.body.newText); }, () => undefined); } diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index ed5bf0ceba4f901a86c226d3a173cc3d3e87f64c..4c066c580a6ddb583b795e85d3d3243923240c0d 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -462,15 +462,18 @@ class MouseController implements IDisposable { } let reference = this.list.getFocus()[0]; - reference = reference === undefined ? this.list.getSelection()[0] : reference; + const selection = this.list.getSelection(); + reference = reference === undefined ? selection[0] : reference; + + const focus = e.index; + if (selection.every(s => s !== focus)) { + this.list.setFocus([focus]); + } if (this.multipleSelectionSupport && this.isSelectionRangeChangeEvent(e)) { return this.changeSelection(e, reference); } - const focus = e.index; - this.list.setFocus([focus]); - if (this.multipleSelectionSupport && this.isSelectionChangeEvent(e)) { return this.changeSelection(e, reference); } diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index d898a618ca4c402d39ece181fbec54f0e65b20be..e4dad463739bbf47360069328450249da9131e0e 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -104,7 +104,11 @@ export class FoldingController implements IEditorContribution { if (!model || !this._isEnabled || model.isTooLargeForTokenization()) { return {}; } - return { collapsedRegions: this.foldingModel.getMemento(), lineCount: model.getLineCount() }; + if (this.foldingModel) { // disposed ? + let collapsedRegions = this.foldingModel.isInitialized ? this.foldingModel.getMemento() : this.hiddenRangeModel.getMemento(); + return { collapsedRegions, lineCount: model.getLineCount() }; + } + return void 0; } /** diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index eb230e2d66e12144cd622ef61c1dd646de965374..33e1826061cb5beb203227d14be3c1855a528f98 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -26,18 +26,21 @@ export class FoldingModel { private _ranges: FoldingRanges; private _editorDecorationIds: string[]; + private _isInitialized: boolean; private _updateEventEmitter = new Emitter(); public get ranges(): FoldingRanges { return this._ranges; } public get onDidChange(): Event { return this._updateEventEmitter.event; } public get textModel() { return this._textModel; } + public get isInitialized() { return this._isInitialized; } constructor(textModel: ITextModel, decorationProvider: IDecorationProvider) { this._textModel = textModel; this._decorationProvider = decorationProvider; this._ranges = new FoldingRanges(new Uint32Array(0), new Uint32Array(0)); this._editorDecorationIds = []; + this._isInitialized = false; } public toggleCollapseState(regions: FoldingRegion[]) { @@ -128,6 +131,7 @@ export class FoldingModel { this._editorDecorationIds = this._decorationProvider.deltaDecorations(this._editorDecorationIds, newEditorDecorations); this._ranges = newRanges; + this._isInitialized = true; this._updateEventEmitter.fire({ model: this }); } diff --git a/src/vs/editor/contrib/folding/hiddenRangeModel.ts b/src/vs/editor/contrib/folding/hiddenRangeModel.ts index a684890bb8469de767a7c50ce26295706d8dbc9f..fee0f5108c283cb2b3e23039365ba6792cd05bf7 100644 --- a/src/vs/editor/contrib/folding/hiddenRangeModel.ts +++ b/src/vs/editor/contrib/folding/hiddenRangeModel.ts @@ -12,7 +12,7 @@ import { findFirst } from 'vs/base/common/arrays'; export class HiddenRangeModel { private _foldingModel: FoldingModel; - private _hiddenRanges: IRange[] = []; + private _hiddenRanges: IRange[]; private _foldingModelListener: IDisposable; private _updateEventEmitter = new Emitter(); @@ -22,6 +22,7 @@ export class HiddenRangeModel { public constructor(model: FoldingModel) { this._foldingModel = model; this._foldingModelListener = model.onDidChange(_ => this.updateHiddenRanges()); + this._hiddenRanges = []; if (model.ranges.length) { this.updateHiddenRanges(); } @@ -80,6 +81,13 @@ export class HiddenRangeModel { return true; } + /** + * Collapse state memento, for persistence only, only used if folding model is not yet initialized + */ + public getMemento(): CollapseMemento { + return this._hiddenRanges.map(r => ({ startLineNumber: r.startLineNumber - 1, endLineNumber: r.endLineNumber })); + } + private applyHiddenRanges(newHiddenAreas: IRange[]) { this._hiddenRanges = newHiddenAreas; this._updateEventEmitter.fire(newHiddenAreas); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index 551744065074fcec7bb0096637f03e0ba688e008..f5e6b3b97df02a28a5d193738e09a921b59edc6d 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -57,6 +57,7 @@ import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { DataTransfers } from 'vs/base/browser/dnd'; import { Schemas } from 'vs/base/common/network'; +import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; export class FileDataSource implements IDataSource { constructor( @@ -782,8 +783,8 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { return true; // NewStatPlaceholders can not be moved } - if (source.isRoot) { - return true; // Root folder can not be moved + if (source.isRoot && (sources.length > 1 || target instanceof FileStat && !target.isRoot)) { + return true; // Root folder can not be moved to a non root file stat. Do not allow root folder move when multi selection drag. } if (source.resource.toString() === target.resource.toString()) { @@ -916,7 +917,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { return updateConfirmSettingsPromise.then(() => { if (confirmation.confirmed) { - return TPromise.join(sources.map(source => this.doHandleExplorerDrop(tree, data, source, target, isCopy))).then(() => void 0); + return TPromise.join(sources.map(source => this.doHandleExplorerDrop(tree, source, target, isCopy))).then(() => void 0); } return TPromise.as(void 0); @@ -924,8 +925,37 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { }); } - private doHandleExplorerDrop(tree: ITree, data: IDragAndDropData, source: FileStat, target: FileStat, isCopy: boolean): TPromise { + private doHandleExplorerDrop(tree: ITree, source: FileStat, target: FileStat, isCopy: boolean): TPromise { return tree.expand(target).then(() => { + if (source.isRoot) { + const folders = this.contextService.getWorkspace().folders; + let sourceIndex: number; + let targetIndex: number; + const workspaceCreationData: IWorkspaceFolderCreationData[] = []; + + for (let index = 0; index < folders.length; index++) { + if (folders[index].uri.toString() === source.resource.toString()) { + sourceIndex = index; + } + if (folders[index].uri.toString() === target.resource.toString()) { + targetIndex = index; + } + workspaceCreationData.push({ + name: folders[index].name, + uri: folders[index].uri + }); + + if (sourceIndex && targetIndex) { + break; + } + } + + const swap = workspaceCreationData[sourceIndex]; + workspaceCreationData[sourceIndex] = workspaceCreationData[targetIndex]; + workspaceCreationData[targetIndex] = swap; + + return this.workspaceEditingService.updateFolders(Math.min(sourceIndex, targetIndex), workspaceCreationData.length, workspaceCreationData); + } // Reuse duplicate action if user copies if (isCopy) {