diff --git a/extensions/git/src/iterators.ts b/extensions/git/src/iterators.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b0f741f31012fc296504558058bbfd30caca2cf --- /dev/null +++ b/extensions/git/src/iterators.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +function* filter(it: IterableIterator, condition: (t: T, i: number) => boolean): IterableIterator { + let i = 0; + for (let t of it) { + if (condition(t, i++)) { + yield t; + } + } +} + +function* map(it: IterableIterator, fn: (t: T, i: number) => R): IterableIterator { + let i = 0; + for (let t of it) { + yield fn(t, i++); + } +} + +export interface FunctionalIterator extends Iterable { + filter(condition: (t: T, i: number) => boolean): FunctionalIterator; + map(fn: (t: T, i: number) => R): FunctionalIterator; + toArray(): T[]; +} + +class FunctionalIteratorImpl implements FunctionalIterator { + + constructor(private iterator: IterableIterator) { } + + filter(condition: (t: T, i: number) => boolean): FunctionalIterator { + return new FunctionalIteratorImpl(filter(this.iterator, condition)); + } + + map(fn: (t: T, i: number) => R): FunctionalIterator { + return new FunctionalIteratorImpl(map(this.iterator, fn)); + } + + toArray(): T[] { + return Array.from(this.iterator); + } + + [Symbol.iterator](): IterableIterator { + return this.iterator; + } +} + +export function iterate(obj: T[] | IterableIterator): FunctionalIterator { + if (Array.isArray(obj)) { + return new FunctionalIteratorImpl(obj[Symbol.iterator]()); + } + + return new FunctionalIteratorImpl(obj); +} \ No newline at end of file diff --git a/extensions/git/src/main.ts b/extensions/git/src/main.ts index 83fa024be5ee12d49dbaf421b09df4247aa2a03b..1df0364a6ad5dc6d3ef2e293606e2f6af106b5e6 100644 --- a/extensions/git/src/main.ts +++ b/extensions/git/src/main.ts @@ -14,6 +14,7 @@ import { CheckoutStatusBar, SyncStatusBar } from './statusbar'; import { filterEvent, anyEvent } from './util'; import { GitContentProvider } from './contentProvider'; import { AutoFetcher } from './autofetch'; +import { MergeDecorator } from './merge'; import * as nls from 'vscode-nls'; const localize = nls.config()(); @@ -46,6 +47,7 @@ async function init(disposables: Disposable[]): Promise { const checkoutStatusBar = new CheckoutStatusBar(model); const syncStatusBar = new SyncStatusBar(model); const autoFetcher = new AutoFetcher(model); + const mergeDecorator = new MergeDecorator(model); disposables.push( commandCenter, @@ -55,7 +57,8 @@ async function init(disposables: Disposable[]): Promise { fsWatcher, checkoutStatusBar, syncStatusBar, - autoFetcher + autoFetcher, + mergeDecorator ); } diff --git a/extensions/git/src/merge.ts b/extensions/git/src/merge.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ac237c37483907455cc466d63023e2e8fad92dd --- /dev/null +++ b/extensions/git/src/merge.ts @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { window, workspace, Disposable, TextEditor, TextDocument, Range } from 'vscode'; +import { Model, Status } from './model'; +import { filterEvent } from './util'; +import { debounce } from './decorators'; +import { iterate } from './iterators'; + +function* lines(document: TextDocument): IterableIterator { + for (let i = 0; i < document.lineCount; i++) { + yield document.lineAt(i).text; + } +} + +const pattern = /^<<<<<<<|^=======|^>>>>>>>/; + +function decorate(document: TextDocument): Range[] { + return iterate(lines(document)) + .map((line, i) => pattern.test(line) ? i : null) + .filter(i => i !== null) + .map((i: number) => new Range(i, 1, i, 1)) + .toArray(); +} + +class TextEditorMergeDecorator { + + private static DecorationType = window.createTextEditorDecorationType({ + backgroundColor: 'rgba(255, 139, 0, 0.3)', + isWholeLine: true, + dark: { + backgroundColor: 'rgba(235, 59, 0, 0.3)' + } + }); + + private uri: string; + private disposables: Disposable[] = []; + + constructor( + private model: Model, + private editor: TextEditor + ) { + this.uri = this.editor.document.uri.toString(); + + const onDidChange = filterEvent(workspace.onDidChangeTextDocument, e => e.document && e.document.uri.toString() === this.uri); + onDidChange(this.redecorate, this, this.disposables); + model.onDidChange(this.redecorate, this, this.disposables); + + this.redecorate(); + } + + @debounce(300) + private redecorate(): void { + let decorations: Range[] = []; + + if (window.visibleTextEditors.every(e => e !== this.editor)) { + this.dispose(); + return; + } + + if (this.model.mergeGroup.resources.some(r => r.type === Status.BOTH_MODIFIED && r.uri.toString() === this.uri)) { + decorations = decorate(this.editor.document); + } + + this.editor.setDecorations(TextEditorMergeDecorator.DecorationType, decorations); + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + } +} + +export class MergeDecorator { + + private textEditorDecorators: TextEditorMergeDecorator[] = []; + private disposables: Disposable[] = []; + + constructor(private model: Model) { + window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables); + this.onDidChangeVisibleTextEditors(window.visibleTextEditors); + } + + private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { + this.textEditorDecorators.forEach(d => d.dispose()); + this.textEditorDecorators = editors.map(e => new TextEditorMergeDecorator(this.model, e)); + } + + dispose(): void { + this.textEditorDecorators.forEach(d => d.dispose()); + this.disposables.forEach(d => d.dispose()); + } +}