Initial merge of 'better-merge' extension into core extensions.
- 'better-merge' command and setting namespace replaced with 'merge-conflict' - Configuration settings from extension replaced with single 'merge-conflicts.enabled' : boolean setting
This commit is contained in:
parent
94c647f246
commit
61f16319ab
123
extensions/merge-conflict/package.json
Normal file
123
extensions/merge-conflict/package.json
Normal file
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"name": "merge-conflict",
|
||||
"publisher": "vscode",
|
||||
"displayName": "merge-conflict",
|
||||
"description": "Merge Conflict",
|
||||
"version": "0.7.0",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"enableProposedApi": true,
|
||||
"engines": {
|
||||
"vscode": "^1.5.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:merge-conflict",
|
||||
"watch": "gulp watch-extension:merge-conflict"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept all incoming",
|
||||
"command": "merge-conflict.accept.all-incoming"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept all both",
|
||||
"command": "merge-conflict.accept.all-both"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept current",
|
||||
"command": "merge-conflict.accept.current"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept incoming",
|
||||
"command": "merge-conflict.accept.incoming"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept selection",
|
||||
"command": "merge-conflict.accept.selection"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Accept both",
|
||||
"command": "merge-conflict.accept.both"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Next conflict",
|
||||
"command": "merge-conflict.next"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Previous conflict",
|
||||
"command": "merge-conflict.previous"
|
||||
},
|
||||
{
|
||||
"category": "Merge Conflict",
|
||||
"title": "Compare current conflict",
|
||||
"command": "merge-conflict.compare"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "merge-conflict.next",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m down"
|
||||
},
|
||||
{
|
||||
"command": "merge-conflict.previous",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m up"
|
||||
},
|
||||
{
|
||||
"command": "merge-conflict.accept.selection",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m enter"
|
||||
},
|
||||
{
|
||||
"command": "merge-conflict.accept.current",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 1"
|
||||
},
|
||||
{
|
||||
"command": "merge-conflict.accept.incoming",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 2"
|
||||
},
|
||||
{
|
||||
"command": "merge-conflict.accept.both",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 3"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "Merge Conflict",
|
||||
"properties": {
|
||||
"merge-conflict.enabled": {
|
||||
"type": "boolean",
|
||||
"description": "%config.enabled%",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-extension-telemetry": "^0.0.7",
|
||||
"vscode-nls": "^2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^7.0.4",
|
||||
"mocha": "^3.2.0"
|
||||
}
|
||||
}
|
3
extensions/merge-conflict/package.nls.json
Normal file
3
extensions/merge-conflict/package.nls.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"config.enabled": "Whether merge conflict editor decorations and code lenses are enabled"
|
||||
}
|
82
extensions/merge-conflict/src/codelensProvider.ts
Normal file
82
extensions/merge-conflict/src/codelensProvider.ts
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as interfaces from './interfaces';
|
||||
|
||||
export default class MergeConflictCodeLensProvider implements vscode.CodeLensProvider, vscode.Disposable {
|
||||
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
private config: interfaces.IExtensionConfiguration;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) {
|
||||
}
|
||||
|
||||
begin(config: interfaces.IExtensionConfiguration) {
|
||||
this.config = config;
|
||||
this.disposables.push(
|
||||
vscode.languages.registerCodeLensProvider({ pattern: '**/*' }, this)
|
||||
);
|
||||
}
|
||||
|
||||
configurationUpdated(config: interfaces.IExtensionConfiguration) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
|
||||
async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[] | null> {
|
||||
|
||||
if (!this.config || !this.config.enableCodeLens) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let conflicts = await this.tracker.getConflicts(document);
|
||||
|
||||
if (!conflicts || conflicts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let items: vscode.CodeLens[] = [];
|
||||
|
||||
conflicts.forEach(conflict => {
|
||||
let acceptCurrentCommand: vscode.Command = {
|
||||
command: 'git.merge.accept.current',
|
||||
title: `Accept current change`,
|
||||
arguments: ['known-conflict', conflict]
|
||||
};
|
||||
|
||||
let acceptIncomingCommand: vscode.Command = {
|
||||
command: 'git.merge.accept.incoming',
|
||||
title: `Accept incoming change`,
|
||||
arguments: ['known-conflict', conflict]
|
||||
};
|
||||
|
||||
let acceptBothCommand: vscode.Command = {
|
||||
command: 'git.merge.accept.both',
|
||||
title: `Accept both changes`,
|
||||
arguments: ['known-conflict', conflict]
|
||||
};
|
||||
|
||||
let diffCommand: vscode.Command = {
|
||||
command: 'git.merge.compare',
|
||||
title: `Compare changes`,
|
||||
arguments: [conflict]
|
||||
};
|
||||
|
||||
items.push(
|
||||
new vscode.CodeLens(conflict.range, acceptCurrentCommand),
|
||||
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 1 })), acceptIncomingCommand),
|
||||
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 2 })), acceptBothCommand),
|
||||
new vscode.CodeLens(conflict.range.with(conflict.range.start.with({ character: conflict.range.start.character + 3 })), diffCommand)
|
||||
);
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
279
extensions/merge-conflict/src/commandHandler.ts
Normal file
279
extensions/merge-conflict/src/commandHandler.ts
Normal file
|
@ -0,0 +1,279 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as interfaces from './interfaces';
|
||||
import ContentProvider from './contentProvider';
|
||||
import * as path from 'path';
|
||||
|
||||
// TODO: Localization
|
||||
const messages = {
|
||||
cursorNotInConflict: 'Editor cursor is not within a merge conflict',
|
||||
cursorOnSplitterRange: 'Editor cursor is within the merge conflict splitter, please move it to either the "current" or "incoming" block',
|
||||
noConflicts: 'No merge conflicts found in this file',
|
||||
noOtherConflictsInThisFile: 'No other merge conflicts within this file'
|
||||
};
|
||||
|
||||
interface IDocumentMergeConflictNavigationResults {
|
||||
canNavigate: boolean;
|
||||
conflict?: interfaces.IDocumentMergeConflict;
|
||||
}
|
||||
|
||||
enum NavigationDirection {
|
||||
Forwards,
|
||||
Backwards
|
||||
}
|
||||
|
||||
export default class CommandHandler implements vscode.Disposable {
|
||||
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
|
||||
constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) {
|
||||
}
|
||||
|
||||
begin() {
|
||||
this.disposables.push(
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.current', this.acceptCurrent, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.incoming', this.acceptIncoming, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.selection', this.acceptSelection, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.both', this.acceptBoth, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.all-current', this.acceptAllCurrent, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.all-incoming', this.acceptAllIncoming, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.accept.all-both', this.acceptAllBoth, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.next', this.navigateNext, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.previous', this.navigatePrevious, this),
|
||||
vscode.commands.registerTextEditorCommand('git.merge.compare', this.compare, this)
|
||||
);
|
||||
}
|
||||
|
||||
acceptCurrent(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.accept(interfaces.CommitType.Current, editor, ...args);
|
||||
}
|
||||
|
||||
acceptIncoming(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.accept(interfaces.CommitType.Incoming, editor, ...args);
|
||||
}
|
||||
|
||||
acceptBoth(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.accept(interfaces.CommitType.Both, editor, ...args);
|
||||
}
|
||||
|
||||
acceptAllCurrent(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.acceptAll(interfaces.CommitType.Current, editor);
|
||||
}
|
||||
|
||||
acceptAllIncoming(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.acceptAll(interfaces.CommitType.Incoming, editor);
|
||||
}
|
||||
|
||||
acceptAllBoth(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.acceptAll(interfaces.CommitType.Both, editor);
|
||||
}
|
||||
|
||||
async compare(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, conflict: interfaces.IDocumentMergeConflict | null, ...args) {
|
||||
const fileName = path.basename(editor.document.uri.fsPath);
|
||||
|
||||
// No conflict, command executed from command palette
|
||||
if (!conflict) {
|
||||
conflict = await this.findConflictContainingSelection(editor);
|
||||
|
||||
// Still failed to find conflict, warn the user and exit
|
||||
if (!conflict) {
|
||||
vscode.window.showWarningMessage(messages.cursorNotInConflict);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let range = conflict.current.content;
|
||||
const leftUri = editor.document.uri.with({
|
||||
scheme: ContentProvider.scheme,
|
||||
query: JSON.stringify(range)
|
||||
});
|
||||
|
||||
const leftTitle = `Current changes`; // (Ln ${range.start.line}${!range.isSingleLine ? `-${range.end.line}` : ''})`;
|
||||
|
||||
range = conflict.incoming.content;
|
||||
const rightUri = leftUri.with({ query: JSON.stringify(range) });
|
||||
|
||||
const rightTitle = `Incoming changes`; // (Ln${range.start.line}${!range.isSingleLine ? `-${range.end.line}` : ''})`;
|
||||
|
||||
const title = `${fileName}: ${leftTitle} \u2194 ${rightTitle}`;
|
||||
vscode.commands.executeCommand('vscode.diff', leftUri, rightUri, title);
|
||||
}
|
||||
|
||||
navigateNext(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.navigate(editor, NavigationDirection.Forwards);
|
||||
}
|
||||
|
||||
navigatePrevious(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
return this.navigate(editor, NavigationDirection.Backwards);
|
||||
}
|
||||
|
||||
async acceptSelection(editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args): Promise<void> {
|
||||
let conflict = await this.findConflictContainingSelection(editor);
|
||||
|
||||
if (!conflict) {
|
||||
vscode.window.showWarningMessage(messages.cursorNotInConflict);
|
||||
return;
|
||||
}
|
||||
|
||||
let typeToAccept: interfaces.CommitType;
|
||||
|
||||
// Figure out if the cursor is in current or incoming, we do this by seeing if
|
||||
// the active position is before or after the range of the splitter. We can
|
||||
// use this trick as the previous check in findConflictByActiveSelection will
|
||||
// ensure it's within the conflict range, so we don't falsely identify "current"
|
||||
// or "incoming" if outside of a conflict range.
|
||||
if (editor.selection.active.isBefore(conflict.splitter.start)) {
|
||||
typeToAccept = interfaces.CommitType.Current;
|
||||
}
|
||||
else if (editor.selection.active.isAfter(conflict.splitter.end)) {
|
||||
typeToAccept = interfaces.CommitType.Incoming;
|
||||
}
|
||||
else {
|
||||
vscode.window.showWarningMessage(messages.cursorOnSplitterRange);
|
||||
return;
|
||||
}
|
||||
|
||||
this.tracker.forget(editor.document);
|
||||
conflict.commitEdit(typeToAccept, editor);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.disposables.forEach(disposable => disposable.dispose());
|
||||
this.disposables = [];
|
||||
}
|
||||
|
||||
private async navigate(editor: vscode.TextEditor, direction: NavigationDirection): Promise<void> {
|
||||
let navigationResult = await this.findConflictForNavigation(editor, direction);
|
||||
|
||||
if (!navigationResult) {
|
||||
vscode.window.showWarningMessage(messages.noConflicts);
|
||||
return;
|
||||
}
|
||||
else if (!navigationResult.canNavigate) {
|
||||
vscode.window.showWarningMessage(messages.noOtherConflictsInThisFile);
|
||||
return;
|
||||
}
|
||||
else if (!navigationResult.conflict) {
|
||||
// TODO: Show error message?
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the selection to the first line of the conflict
|
||||
editor.selection = new vscode.Selection(navigationResult.conflict.range.start, navigationResult.conflict.range.start);
|
||||
editor.revealRange(navigationResult.conflict.range, vscode.TextEditorRevealType.Default);
|
||||
}
|
||||
|
||||
private async accept(type: interfaces.CommitType, editor: vscode.TextEditor, ...args): Promise<void> {
|
||||
|
||||
let conflict: interfaces.IDocumentMergeConflict | null;
|
||||
|
||||
// If launched with known context, take the conflict from that
|
||||
if (args[0] === 'known-conflict') {
|
||||
conflict = args[1];
|
||||
}
|
||||
else {
|
||||
// Attempt to find a conflict that matches the current curosr position
|
||||
conflict = await this.findConflictContainingSelection(editor);
|
||||
}
|
||||
|
||||
if (!conflict) {
|
||||
vscode.window.showWarningMessage(messages.cursorNotInConflict);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tracker can forget as we know we are going to do an edit
|
||||
this.tracker.forget(editor.document);
|
||||
conflict.commitEdit(type, editor);
|
||||
}
|
||||
|
||||
private async acceptAll(type: interfaces.CommitType, editor: vscode.TextEditor): Promise<void> {
|
||||
let conflicts = await this.tracker.getConflicts(editor.document);
|
||||
|
||||
if (!conflicts || conflicts.length === 0) {
|
||||
vscode.window.showWarningMessage(messages.noConflicts);
|
||||
return;
|
||||
}
|
||||
|
||||
// For get the current state of the document, as we know we are doing to do a large edit
|
||||
this.tracker.forget(editor.document);
|
||||
|
||||
// Apply all changes as one edit
|
||||
await editor.edit((edit) => conflicts.forEach(conflict => {
|
||||
conflict.applyEdit(type, editor, edit);
|
||||
}));
|
||||
}
|
||||
|
||||
private async findConflictContainingSelection(editor: vscode.TextEditor, conflicts?: interfaces.IDocumentMergeConflict[]): Promise<interfaces.IDocumentMergeConflict | null> {
|
||||
|
||||
if (!conflicts) {
|
||||
conflicts = await this.tracker.getConflicts(editor.document);
|
||||
}
|
||||
|
||||
if (!conflicts || conflicts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < conflicts.length; i++) {
|
||||
if (conflicts[i].range.contains(editor.selection.active)) {
|
||||
return conflicts[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async findConflictForNavigation(editor: vscode.TextEditor, direction: NavigationDirection, conflicts?: interfaces.IDocumentMergeConflict[]): Promise<IDocumentMergeConflictNavigationResults | null> {
|
||||
if (!conflicts) {
|
||||
conflicts = await this.tracker.getConflicts(editor.document);
|
||||
}
|
||||
|
||||
if (!conflicts || conflicts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let selection = editor.selection.active;
|
||||
if (conflicts.length === 1) {
|
||||
if (conflicts[0].range.contains(selection)) {
|
||||
return {
|
||||
canNavigate: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
canNavigate: true,
|
||||
conflict: conflicts[0]
|
||||
};
|
||||
}
|
||||
|
||||
let predicate: (conflict) => boolean;
|
||||
let fallback: () => interfaces.IDocumentMergeConflict;
|
||||
|
||||
if (direction === NavigationDirection.Forwards) {
|
||||
predicate = (conflict) => selection.isBefore(conflict.range.start);
|
||||
fallback = () => conflicts![0];
|
||||
} else if (direction === NavigationDirection.Backwards) {
|
||||
predicate = (conflict) => selection.isAfter(conflict.range.start);
|
||||
fallback = () => conflicts![conflicts!.length - 1];
|
||||
} else {
|
||||
throw new Error(`Unsupported direction ${direction}`);
|
||||
}
|
||||
|
||||
for (let i = 0; i < conflicts.length; i++) {
|
||||
if (predicate(conflicts[i]) && !conflicts[i].range.contains(selection)) {
|
||||
return {
|
||||
canNavigate: true,
|
||||
conflict: conflicts[i]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Went all the way to the end, return the head
|
||||
return {
|
||||
canNavigate: true,
|
||||
conflict: fallback()
|
||||
};
|
||||
}
|
||||
}
|
38
extensions/merge-conflict/src/contentProvider.ts
Normal file
38
extensions/merge-conflict/src/contentProvider.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as vscode from 'vscode';
|
||||
import * as interfaces from './interfaces';
|
||||
|
||||
export default class MergeConflictContentProvider implements vscode.TextDocumentContentProvider, vscode.Disposable {
|
||||
|
||||
static scheme = 'git.merge.conflict-diff';
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
begin(config: interfaces.IExtensionConfiguration) {
|
||||
this.context.subscriptions.push(
|
||||
vscode.workspace.registerTextDocumentContentProvider(MergeConflictContentProvider.scheme, this)
|
||||
);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
}
|
||||
|
||||
async provideTextDocumentContent(uri: vscode.Uri): Promise<string | null> {
|
||||
try {
|
||||
const [start, end] = JSON.parse(uri.query) as { line: number, character: number }[];
|
||||
|
||||
const document = await vscode.workspace.openTextDocument(uri.with({ scheme: 'file', query: '' }));
|
||||
const text = document.getText(new vscode.Range(start.line, start.character, end.line, end.character));
|
||||
return text;
|
||||
}
|
||||
catch (ex) {
|
||||
await vscode.window.showErrorMessage('Unable to show comparison');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
80
extensions/merge-conflict/src/delayer.ts
Normal file
80
extensions/merge-conflict/src/delayer.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export interface ITask<T> {
|
||||
(): T;
|
||||
}
|
||||
|
||||
export class Delayer<T> {
|
||||
|
||||
public defaultDelay: number;
|
||||
private timeout: any; // Timer
|
||||
private completionPromise: Promise<T> | null;
|
||||
private onSuccess: ((value?: T | Thenable<T> | null) => void) | null;
|
||||
private task: ITask<T> | null;
|
||||
|
||||
constructor(defaultDelay: number) {
|
||||
this.defaultDelay = defaultDelay;
|
||||
this.timeout = null;
|
||||
this.completionPromise = null;
|
||||
this.onSuccess = null;
|
||||
this.task = null;
|
||||
}
|
||||
|
||||
public trigger(task: ITask<T>, delay: number = this.defaultDelay): Promise<T> {
|
||||
this.task = task;
|
||||
if (delay >= 0) {
|
||||
this.cancelTimeout();
|
||||
}
|
||||
|
||||
if (!this.completionPromise) {
|
||||
this.completionPromise = new Promise<T>((resolve) => {
|
||||
this.onSuccess = resolve;
|
||||
}).then(() => {
|
||||
this.completionPromise = null;
|
||||
this.onSuccess = null;
|
||||
var result = this.task!();
|
||||
this.task = null;
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
if (delay >= 0 || this.timeout === null) {
|
||||
this.timeout = setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.onSuccess!(null);
|
||||
}, delay >= 0 ? delay : this.defaultDelay);
|
||||
}
|
||||
|
||||
return this.completionPromise;
|
||||
}
|
||||
|
||||
public forceDelivery(): Promise<T> | null {
|
||||
if (!this.completionPromise) {
|
||||
return null;
|
||||
}
|
||||
this.cancelTimeout();
|
||||
let result = this.completionPromise;
|
||||
this.onSuccess!(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public isTriggered(): boolean {
|
||||
return this.timeout !== null;
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.cancelTimeout();
|
||||
this.completionPromise = null;
|
||||
}
|
||||
|
||||
private cancelTimeout(): void {
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
104
extensions/merge-conflict/src/documentMergeConflict.ts
Normal file
104
extensions/merge-conflict/src/documentMergeConflict.ts
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as interfaces from './interfaces';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export class DocumentMergeConflict implements interfaces.IDocumentMergeConflict {
|
||||
|
||||
public range: vscode.Range;
|
||||
public current: interfaces.IMergeRegion;
|
||||
public incoming: interfaces.IMergeRegion;
|
||||
public splitter: vscode.Range;
|
||||
|
||||
constructor(document: vscode.TextDocument, descriptor: interfaces.IDocumentMergeConflictDescriptor) {
|
||||
this.range = descriptor.range;
|
||||
this.current = descriptor.current;
|
||||
this.incoming = descriptor.incoming;
|
||||
this.splitter = descriptor.splitter;
|
||||
}
|
||||
|
||||
public commitEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit): Thenable<boolean> {
|
||||
|
||||
if (edit) {
|
||||
|
||||
this.applyEdit(type, editor, edit);
|
||||
return Promise.resolve(true);
|
||||
};
|
||||
|
||||
return editor.edit((edit) => this.applyEdit(type, editor, edit));
|
||||
}
|
||||
|
||||
public applyEdit(type: interfaces.CommitType, editor: vscode.TextEditor, edit: vscode.TextEditorEdit): void {
|
||||
|
||||
// Each conflict is a set of ranges as follows, note placements or newlines
|
||||
// which may not in in spans
|
||||
// [ Conflict Range -- (Entire content below)
|
||||
// [ Current Header ]\n -- >>>>> Header
|
||||
// [ Current Content ] -- (content)
|
||||
// [ Splitter ]\n -- =====
|
||||
// [ Incoming Content ] -- (content)
|
||||
// [ Incoming Header ]\n -- <<<<< Incoming
|
||||
// ]
|
||||
if (type === interfaces.CommitType.Current) {
|
||||
// Replace [ Conflict Range ] with [ Current Content ]
|
||||
let content = editor.document.getText(this.current.content);
|
||||
this.replaceRangeWithContent(content, edit);
|
||||
}
|
||||
else if (type === interfaces.CommitType.Incoming) {
|
||||
let content = editor.document.getText(this.incoming.content);
|
||||
this.replaceRangeWithContent(content, edit);
|
||||
}
|
||||
else if (type === interfaces.CommitType.Both) {
|
||||
// Replace [ Conflict Range ] with [ Current Content ] + \n + [ Incoming Content ]
|
||||
//
|
||||
// NOTE: Due to headers and splitters NOT covering \n (this is so newlines inserted)
|
||||
// by the user after (e.g. <<<<< HEAD do not fall into the header range but the
|
||||
// content ranges), we can't push 3x deletes, we need to replace the range with the
|
||||
// union of the content.
|
||||
|
||||
const currentContent = editor.document.getText(this.current.content);
|
||||
const incomingContent = editor.document.getText(this.incoming.content);
|
||||
|
||||
let finalContent = '';
|
||||
|
||||
if (!this.isNewlineOnly(currentContent)) {
|
||||
finalContent += currentContent;
|
||||
}
|
||||
|
||||
if (!this.isNewlineOnly(incomingContent)) {
|
||||
if (finalContent.length > 0) {
|
||||
finalContent += '\n';
|
||||
}
|
||||
|
||||
finalContent += incomingContent;
|
||||
}
|
||||
|
||||
if (finalContent.length > 0 && !this.isNewlineOnly(finalContent)) {
|
||||
finalContent += '\n';
|
||||
}
|
||||
|
||||
edit.setEndOfLine(vscode.EndOfLine.LF);
|
||||
edit.replace(this.range, finalContent);
|
||||
}
|
||||
}
|
||||
|
||||
private replaceRangeWithContent(content: string, edit: vscode.TextEditorEdit) {
|
||||
if (this.isNewlineOnly(content)) {
|
||||
edit.replace(this.range, '');
|
||||
return;
|
||||
}
|
||||
|
||||
let updatedContent = content.concat('\n');
|
||||
edit.setEndOfLine(vscode.EndOfLine.LF);
|
||||
|
||||
// Replace [ Conflict Range ] with [ Current Content ]
|
||||
|
||||
edit.replace(this.range, updatedContent);
|
||||
}
|
||||
|
||||
private isNewlineOnly(text: string) {
|
||||
return text === '\n' || text === '\r\n';
|
||||
}
|
||||
}
|
66
extensions/merge-conflict/src/documentTracker.ts
Normal file
66
extensions/merge-conflict/src/documentTracker.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { MergeConflictParser } from './mergeConflictParser';
|
||||
import * as interfaces from './interfaces';
|
||||
import { Delayer } from './delayer';
|
||||
|
||||
export default class DocumentMergeConflictTracker implements vscode.Disposable, interfaces.IDocumentMergeConflictTracker {
|
||||
|
||||
private cache: Map<string, Delayer<interfaces.IDocumentMergeConflict[]>> = new Map();
|
||||
private delayExpireTime: number = 150;
|
||||
|
||||
getConflicts(document: vscode.TextDocument): PromiseLike<interfaces.IDocumentMergeConflict[]> {
|
||||
// Attempt from cache
|
||||
|
||||
let key = this.getCacheKey(document);
|
||||
|
||||
if (!key) {
|
||||
// Document doesnt have a uri, can't cache it, so return
|
||||
return Promise.resolve(this.getConflictsOrEmpty(document));
|
||||
}
|
||||
|
||||
let cacheItem = this.cache.get(key);
|
||||
if (!cacheItem) {
|
||||
cacheItem = new Delayer<interfaces.IDocumentMergeConflict[]>(this.delayExpireTime);
|
||||
this.cache.set(key, cacheItem);
|
||||
}
|
||||
|
||||
return cacheItem.trigger(() => {
|
||||
let conflicts = this.getConflictsOrEmpty(document);
|
||||
|
||||
if (this.cache) {
|
||||
this.cache.delete(key!);
|
||||
}
|
||||
|
||||
return conflicts;
|
||||
});
|
||||
}
|
||||
|
||||
forget(document: vscode.TextDocument) {
|
||||
let key = this.getCacheKey(document);
|
||||
|
||||
if (key) {
|
||||
this.cache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.cache.clear();
|
||||
}
|
||||
|
||||
private getConflictsOrEmpty(document: vscode.TextDocument): interfaces.IDocumentMergeConflict[] {
|
||||
return MergeConflictParser.containsConflict(document) ? MergeConflictParser.scanDocument(document) : [];
|
||||
}
|
||||
|
||||
private getCacheKey(document: vscode.TextDocument): string | null {
|
||||
if (document.uri && document.uri) {
|
||||
return document.uri.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
18
extensions/merge-conflict/src/extension.ts
Normal file
18
extensions/merge-conflict/src/extension.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import MergeConflictServices from './services';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
// Register disposables
|
||||
const services = new MergeConflictServices(context);
|
||||
services.begin();
|
||||
context.subscriptions.push(services);
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
}
|
||||
|
40
extensions/merge-conflict/src/interfaces.ts
Normal file
40
extensions/merge-conflict/src/interfaces.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IMergeRegion {
|
||||
name: string;
|
||||
header: vscode.Range;
|
||||
content: vscode.Range;
|
||||
}
|
||||
|
||||
export enum CommitType {
|
||||
Current,
|
||||
Incoming,
|
||||
Both
|
||||
}
|
||||
|
||||
export interface IExtensionConfiguration {
|
||||
enableCodeLens: boolean;
|
||||
enableDecorations: boolean;
|
||||
enableEditorOverview: boolean;
|
||||
}
|
||||
|
||||
export interface IDocumentMergeConflict extends IDocumentMergeConflictDescriptor {
|
||||
commitEdit(type: CommitType, editor: vscode.TextEditor, edit?: vscode.TextEditorEdit);
|
||||
applyEdit(type: CommitType, editor: vscode.TextEditor, edit: vscode.TextEditorEdit);
|
||||
}
|
||||
|
||||
export interface IDocumentMergeConflictDescriptor {
|
||||
range: vscode.Range;
|
||||
current: IMergeRegion;
|
||||
incoming: IMergeRegion;
|
||||
splitter: vscode.Range;
|
||||
}
|
||||
|
||||
export interface IDocumentMergeConflictTracker {
|
||||
getConflicts(document: vscode.TextDocument): PromiseLike<IDocumentMergeConflict[]>;
|
||||
forget(document: vscode.TextDocument);
|
||||
}
|
129
extensions/merge-conflict/src/mergeConflictParser.ts
Normal file
129
extensions/merge-conflict/src/mergeConflictParser.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as interfaces from './interfaces';
|
||||
import { DocumentMergeConflict } from './documentMergeConflict';
|
||||
import * as vm from 'vm';
|
||||
|
||||
export class MergeConflictParser {
|
||||
|
||||
static scanDocument(document: vscode.TextDocument): interfaces.IDocumentMergeConflict[] {
|
||||
|
||||
// Conflict matching regex, comments are in the format of "description - [group index] group name"
|
||||
// Premise is: Match the current change (<<<<<<), match anything up to the splitter (======) then
|
||||
// match anything up to the incoming change (>>>>>>), this leaves some oddities with newlines not being
|
||||
// pulled into the "body" of each change, DocumentMergeConflict.applyEdit will deal with these cases
|
||||
// and append newlines when needed
|
||||
|
||||
const conflictMatcher = new RegExp([
|
||||
/(^<<<<<<<\s(.+)\r?\n)/, // "Current" conflict header - [1] entire line, [2] name
|
||||
/([\s\S]*?)/, // "Current" conflict body - [3] body text
|
||||
/(^=======\r?\n)/, // Splitter - [4] entire line
|
||||
/([\s\S]*?)/, // Incoming conflict body - [5]
|
||||
/(^>>>>>>>\s(.+)\r?\n)/ // Incoming conflict header - [6] entire line, [7] name
|
||||
].map(r => r.source).join(''), 'mg');
|
||||
|
||||
const offsetGroups = [1, 3, 4, 5, 6]; // Skip inner matches when calculating length
|
||||
|
||||
let text = document.getText();
|
||||
let sandboxScope = {
|
||||
result: [],
|
||||
conflictMatcher,
|
||||
text: text
|
||||
};
|
||||
const context = vm.createContext(sandboxScope);
|
||||
const script = new vm.Script(`
|
||||
let match;
|
||||
while (match = conflictMatcher.exec(text)) {
|
||||
// Ensure we don't get stuck in an infinite loop
|
||||
if (match.index === conflictMatcher.lastIndex) {
|
||||
conflictMatcher.lastIndex++;
|
||||
}
|
||||
|
||||
result.push(match);
|
||||
}`);
|
||||
|
||||
try {
|
||||
// If the regex takes longer than 1s consider it dead
|
||||
script.runInContext(context, { timeout: 1000 });
|
||||
}
|
||||
catch (ex) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return sandboxScope.result.map(match => new DocumentMergeConflict(document, MergeConflictParser.matchesToDescriptor(document, match, offsetGroups)));
|
||||
}
|
||||
|
||||
static containsConflict(document: vscode.TextDocument): boolean {
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Ask source control if the file contains a conflict
|
||||
let text = document.getText();
|
||||
return text.includes('<<<<<<<') && text.includes('>>>>>>>');
|
||||
}
|
||||
|
||||
static matchesToDescriptor(document: vscode.TextDocument, match: RegExpExecArray, offsets?: number[]): interfaces.IDocumentMergeConflictDescriptor {
|
||||
|
||||
var item: interfaces.IDocumentMergeConflictDescriptor = {
|
||||
range: new vscode.Range(document.positionAt(match.index), document.positionAt(match.index + match[0].length)),
|
||||
current: {
|
||||
name: match[2],
|
||||
header: this.getMatchPositions(document, match, 1, offsets),
|
||||
content: this.getMatchPositions(document, match, 3, offsets),
|
||||
},
|
||||
splitter: this.getMatchPositions(document, match, 4, offsets),
|
||||
incoming: {
|
||||
name: match[9],
|
||||
header: this.getMatchPositions(document, match, 6, offsets),
|
||||
content: this.getMatchPositions(document, match, 5, offsets),
|
||||
}
|
||||
};
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
static getMatchPositions(document: vscode.TextDocument, match: RegExpExecArray, groupIndex: number, offsetGroups?: number[]): vscode.Range {
|
||||
// Javascript doesnt give of offsets within the match, we need to calculate these
|
||||
// based of the prior groups, skipping nested matches (yuck).
|
||||
if (!offsetGroups) {
|
||||
offsetGroups = match.map((i, idx) => idx);
|
||||
}
|
||||
|
||||
let start = match.index;
|
||||
|
||||
for (var i = 0; i < offsetGroups.length; i++) {
|
||||
let value = offsetGroups[i];
|
||||
|
||||
if (value >= groupIndex) {
|
||||
break;
|
||||
}
|
||||
|
||||
start += match[value] !== undefined ? match[value].length : 0;
|
||||
}
|
||||
|
||||
const groupMatch = match[groupIndex];
|
||||
let targetMatchLength = groupMatch !== undefined ? groupMatch.length : -1;
|
||||
let end = (start + targetMatchLength);
|
||||
|
||||
if (groupMatch !== undefined) {
|
||||
// Move the end up if it's capped by a trailing \r\n, this is so regions don't expand into
|
||||
// the line below, and can be "pulled down" by editing the line below
|
||||
if (match[groupIndex].lastIndexOf('\n') === targetMatchLength - 1) {
|
||||
end--;
|
||||
|
||||
// .. for windows encodings of new lines
|
||||
if (match[groupIndex].lastIndexOf('\r') === targetMatchLength - 2) {
|
||||
end--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new vscode.Range(document.positionAt(start), document.positionAt(end));
|
||||
}
|
||||
|
||||
}
|
204
extensions/merge-conflict/src/mergeDecorator.ts
Normal file
204
extensions/merge-conflict/src/mergeDecorator.ts
Normal file
|
@ -0,0 +1,204 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import * as interfaces from './interfaces';
|
||||
|
||||
|
||||
export default class MergeDectorator implements vscode.Disposable {
|
||||
|
||||
private decorations: { [key: string]: vscode.TextEditorDecorationType } = {};
|
||||
|
||||
private decorationUsesWholeLine: boolean = true; // Useful for debugging, set to false to see exact match ranges
|
||||
|
||||
// TODO: Move to config?
|
||||
private currentColorRgb = `32,200,94`;
|
||||
private incomingColorRgb = `24,134,255`;
|
||||
private config: interfaces.IExtensionConfiguration;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext, private tracker: interfaces.IDocumentMergeConflictTracker) {
|
||||
}
|
||||
|
||||
begin(config: interfaces.IExtensionConfiguration) {
|
||||
this.config = config;
|
||||
this.registerDecorationTypes(config);
|
||||
|
||||
// Check if we already have a set of active windows, attempt to track these.
|
||||
vscode.window.visibleTextEditors.forEach(e => this.applyDecorations(e));
|
||||
|
||||
vscode.workspace.onDidOpenTextDocument(event => {
|
||||
this.applyDecorationsFromEvent(event);
|
||||
}, null, this.context.subscriptions);
|
||||
|
||||
vscode.workspace.onDidChangeTextDocument(event => {
|
||||
this.applyDecorationsFromEvent(event.document);
|
||||
}, null, this.context.subscriptions);
|
||||
|
||||
vscode.window.onDidChangeActiveTextEditor((e) => {
|
||||
// New editor attempt to apply
|
||||
this.applyDecorations(e);
|
||||
}, null, this.context.subscriptions);
|
||||
}
|
||||
|
||||
configurationUpdated(config: interfaces.IExtensionConfiguration) {
|
||||
this.config = config;
|
||||
this.registerDecorationTypes(config);
|
||||
|
||||
// Re-apply the decoration
|
||||
vscode.window.visibleTextEditors.forEach(e => {
|
||||
this.removeDecorations(e);
|
||||
this.applyDecorations(e);
|
||||
});
|
||||
}
|
||||
|
||||
private registerDecorationTypes(config: interfaces.IExtensionConfiguration) {
|
||||
|
||||
// Dispose of existing decorations
|
||||
Object.keys(this.decorations).forEach(k => this.decorations[k].dispose());
|
||||
this.decorations = {};
|
||||
|
||||
// None of our features are enabled
|
||||
if (!config.enableDecorations || !config.enableEditorOverview) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create decorators
|
||||
if (config.enableDecorations || config.enableEditorOverview) {
|
||||
this.decorations['current.content'] = vscode.window.createTextEditorDecorationType(
|
||||
this.generateBlockRenderOptions(this.currentColorRgb, config)
|
||||
);
|
||||
|
||||
this.decorations['incoming.content'] = vscode.window.createTextEditorDecorationType(
|
||||
this.generateBlockRenderOptions(this.incomingColorRgb, config)
|
||||
);
|
||||
}
|
||||
|
||||
if (config.enableDecorations) {
|
||||
this.decorations['current.header'] = vscode.window.createTextEditorDecorationType({
|
||||
// backgroundColor: 'rgba(255, 0, 0, 0.01)',
|
||||
// border: '2px solid red',
|
||||
isWholeLine: this.decorationUsesWholeLine,
|
||||
backgroundColor: `rgba(${this.currentColorRgb}, 1.0)`,
|
||||
color: 'white',
|
||||
after: {
|
||||
contentText: ' (Current change)',
|
||||
color: 'rgba(0, 0, 0, 0.7)'
|
||||
}
|
||||
});
|
||||
|
||||
this.decorations['splitter'] = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.25)',
|
||||
color: 'white',
|
||||
isWholeLine: this.decorationUsesWholeLine,
|
||||
});
|
||||
|
||||
this.decorations['incoming.header'] = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: `rgba(${this.incomingColorRgb}, 1.0)`,
|
||||
color: 'white',
|
||||
isWholeLine: this.decorationUsesWholeLine,
|
||||
after: {
|
||||
contentText: ' (Incoming change)',
|
||||
color: 'rgba(0, 0, 0, 0.7)'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
// TODO: Replace with Map<string, T>
|
||||
Object.keys(this.decorations).forEach(name => {
|
||||
this.decorations[name].dispose();
|
||||
});
|
||||
|
||||
this.decorations = {};
|
||||
}
|
||||
|
||||
private generateBlockRenderOptions(color: string, config: interfaces.IExtensionConfiguration): vscode.DecorationRenderOptions {
|
||||
|
||||
let renderOptions: any = {};
|
||||
|
||||
if (config.enableDecorations) {
|
||||
renderOptions.backgroundColor = `rgba(${color}, 0.2)`;
|
||||
renderOptions.isWholeLine = this.decorationUsesWholeLine;
|
||||
}
|
||||
|
||||
if (config.enableEditorOverview) {
|
||||
renderOptions.overviewRulerColor = `rgba(${color}, 0.5)`;
|
||||
renderOptions.overviewRulerLane = vscode.OverviewRulerLane.Full;
|
||||
}
|
||||
|
||||
return renderOptions;
|
||||
}
|
||||
|
||||
private applyDecorationsFromEvent(eventDocument: vscode.TextDocument) {
|
||||
for (var i = 0; i < vscode.window.visibleTextEditors.length; i++) {
|
||||
if (vscode.window.visibleTextEditors[i].document === eventDocument) {
|
||||
// Attempt to apply
|
||||
this.applyDecorations(vscode.window.visibleTextEditors[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async applyDecorations(editor: vscode.TextEditor) {
|
||||
if (!editor || !editor.document) { return; }
|
||||
|
||||
if (!this.config || (!this.config.enableDecorations && !this.config.enableEditorOverview)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let conflicts = await this.tracker.getConflicts(editor.document);
|
||||
|
||||
if (conflicts.length === 0) {
|
||||
// TODO: Remove decorations
|
||||
this.removeDecorations(editor);
|
||||
return;
|
||||
}
|
||||
|
||||
// Store decorations keyed by the type of decoration, set decoration wants a "style"
|
||||
// to go with it, which will match this key (see constructor);
|
||||
let matchDecorations: { [key: string]: vscode.DecorationOptions[] } = {};
|
||||
|
||||
let pushDecoration = (key: string, d: vscode.DecorationOptions) => {
|
||||
matchDecorations[key] = matchDecorations[key] || [];
|
||||
matchDecorations[key].push(d);
|
||||
};
|
||||
|
||||
conflicts.forEach(conflict => {
|
||||
// TODO, this could be more effective, just call getMatchPositions once with a map of decoration to position
|
||||
pushDecoration('current.content', { range: conflict.current.content });
|
||||
pushDecoration('incoming.content', { range: conflict.incoming.content });
|
||||
|
||||
if (this.config.enableDecorations) {
|
||||
pushDecoration('current.header', { range: conflict.current.header });
|
||||
pushDecoration('splitter', { range: conflict.splitter });
|
||||
pushDecoration('incoming.header', { range: conflict.incoming.header });
|
||||
}
|
||||
});
|
||||
|
||||
// For each match we've generated, apply the generated decoration with the matching decoration type to the
|
||||
// editor instance. Keys in both matches and decorations should match.
|
||||
Object.keys(matchDecorations).forEach(decorationKey => {
|
||||
let decorationType = this.decorations[decorationKey];
|
||||
|
||||
if (decorationType) {
|
||||
editor.setDecorations(decorationType, matchDecorations[decorationKey]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private removeDecorations(editor: vscode.TextEditor) {
|
||||
// Remove all decorations, there might be none
|
||||
Object.keys(this.decorations).forEach(decorationKey => {
|
||||
|
||||
// Race condition, while editing the settings, it's possible to
|
||||
// generate regions before the configuration has been refreshed
|
||||
let decorationType = this.decorations[decorationKey];
|
||||
|
||||
if (decorationType) {
|
||||
editor.setDecorations(decorationType, []);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
722
extensions/merge-conflict/src/package.json
Normal file
722
extensions/merge-conflict/src/package.json
Normal file
|
@ -0,0 +1,722 @@
|
|||
{
|
||||
"name": "git",
|
||||
"publisher": "vscode",
|
||||
"displayName": "git",
|
||||
"description": "Git",
|
||||
"version": "0.0.1",
|
||||
"engines": {
|
||||
"vscode": "^1.5.0"
|
||||
},
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"enableProposedApi": true,
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:git",
|
||||
"watch": "gulp watch-extension:git"
|
||||
},
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "git.clone",
|
||||
"title": "%command.clone%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.init",
|
||||
"title": "%command.init%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.refresh",
|
||||
"title": "%command.refresh%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/refresh.svg",
|
||||
"dark": "resources/icons/dark/refresh.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"title": "%command.openChange%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/open-change.svg",
|
||||
"dark": "resources/icons/dark/open-change.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.openFile",
|
||||
"title": "%command.openFile%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/open-file.svg",
|
||||
"dark": "resources/icons/dark/open-file.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.stage",
|
||||
"title": "%command.stage%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/stage.svg",
|
||||
"dark": "resources/icons/dark/stage.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"title": "%command.stageAll%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/stage.svg",
|
||||
"dark": "resources/icons/dark/stage.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.stageSelectedRanges",
|
||||
"title": "%command.stageSelectedRanges%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.revertSelectedRanges",
|
||||
"title": "%command.revertSelectedRanges%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.unstage",
|
||||
"title": "%command.unstage%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/unstage.svg",
|
||||
"dark": "resources/icons/dark/unstage.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.unstageAll",
|
||||
"title": "%command.unstageAll%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/unstage.svg",
|
||||
"dark": "resources/icons/dark/unstage.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.unstageSelectedRanges",
|
||||
"title": "%command.unstageSelectedRanges%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.clean",
|
||||
"title": "%command.clean%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/clean.svg",
|
||||
"dark": "resources/icons/dark/clean.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.cleanAll",
|
||||
"title": "%command.cleanAll%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/clean.svg",
|
||||
"dark": "resources/icons/dark/clean.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.commit",
|
||||
"title": "%command.commit%",
|
||||
"category": "Git",
|
||||
"icon": {
|
||||
"light": "resources/icons/light/check.svg",
|
||||
"dark": "resources/icons/dark/check.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "git.commitStaged",
|
||||
"title": "%command.commitStaged%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedSigned",
|
||||
"title": "%command.commitStagedSigned%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAll",
|
||||
"title": "%command.commitAll%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllSigned",
|
||||
"title": "%command.commitAllSigned%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.undoCommit",
|
||||
"title": "%command.undoCommit%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.checkout",
|
||||
"title": "%command.checkout%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.branch",
|
||||
"title": "%command.branch%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.pull",
|
||||
"title": "%command.pull%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.pullRebase",
|
||||
"title": "%command.pullRebase%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.push",
|
||||
"title": "%command.push%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.pushTo",
|
||||
"title": "%command.pushTo%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.sync",
|
||||
"title": "%command.sync%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.publish",
|
||||
"title": "%command.publish%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.showOutput",
|
||||
"title": "%command.showOutput%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.accept.all-current",
|
||||
"title": "Accept all current",
|
||||
"category": "Git Merge"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept all incoming",
|
||||
"command": "git.merge.accept.all-incoming"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept all both",
|
||||
"command": "git.merge.accept.all-both"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept current",
|
||||
"command": "git.merge.accept.current"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept incoming",
|
||||
"command": "git.merge.accept.incoming"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept selection",
|
||||
"command": "git.merge.accept.selection"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Accept both",
|
||||
"command": "git.merge.accept.both"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Next conflict",
|
||||
"command": "git.merge.next"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Previous conflict",
|
||||
"command": "git.merge.previous"
|
||||
},
|
||||
{
|
||||
"category": "Git Merge",
|
||||
"title": "Compare current conflict",
|
||||
"command": "git.merge.compare"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "git.merge.next",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m down"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.previous",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m up"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.accept.selection",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m enter"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.accept.current",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 1"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.accept.incoming",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 2"
|
||||
},
|
||||
{
|
||||
"command": "git.merge.accept.both",
|
||||
"when": "editorTextFocus",
|
||||
"key": "alt+m 3"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "git.clone",
|
||||
"when": "config.git.enabled"
|
||||
},
|
||||
{
|
||||
"command": "git.init",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == norepo"
|
||||
},
|
||||
{
|
||||
"command": "git.refresh",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.openFile",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.stageSelectedRanges",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.revertSelectedRanges",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.unstage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageSelectedRanges",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.clean",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.cleanAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStaged",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedSigned",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllSigned",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.undoCommit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.checkout",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.branch",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pull",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pullRebase",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.push",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pushTo",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.publish",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.showOutput",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
}
|
||||
],
|
||||
"scm/title": [
|
||||
{
|
||||
"command": "git.init",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == norepo"
|
||||
},
|
||||
{
|
||||
"command": "git.commit",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.refresh",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.sync",
|
||||
"group": "1_sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pull",
|
||||
"group": "1_sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pullRebase",
|
||||
"group": "1_sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.push",
|
||||
"group": "1_sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.pushTo",
|
||||
"group": "1_sync",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.publish",
|
||||
"group": "2_publish",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStaged",
|
||||
"group": "3_commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedSigned",
|
||||
"group": "3_commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAll",
|
||||
"group": "3_commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllSigned",
|
||||
"group": "3_commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.undoCommit",
|
||||
"group": "3_commit",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageAll",
|
||||
"group": "4_stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.cleanAll",
|
||||
"group": "4_stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
},
|
||||
{
|
||||
"command": "git.showOutput",
|
||||
"group": "5_output",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle"
|
||||
}
|
||||
],
|
||||
"scm/resourceGroup/context": [
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.cleanAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.cleanAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.stageAll",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "inline"
|
||||
}
|
||||
],
|
||||
"scm/resourceState/context": [
|
||||
{
|
||||
"command": "git.stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == merge",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "git.openFile",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "git.unstage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.unstage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == index",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "git.openFile",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "navigation"
|
||||
},
|
||||
{
|
||||
"command": "git.stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.clean",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "1_modification"
|
||||
},
|
||||
{
|
||||
"command": "git.clean",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "git.stage",
|
||||
"when": "config.git.enabled && scmProvider == git && gitState == idle && scmResourceGroup == workingTree",
|
||||
"group": "inline"
|
||||
}
|
||||
],
|
||||
"editor/title": [
|
||||
{
|
||||
"command": "git.openFile",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && scmProvider == git && isInDiffEditor && resourceScheme != extension"
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && scmProvider == git && !isInDiffEditor && resourceScheme != extension"
|
||||
},
|
||||
{
|
||||
"command": "git.stageSelectedRanges",
|
||||
"group": "2_git@1",
|
||||
"when": "config.git.enabled && scmProvider == git && isInDiffEditor"
|
||||
},
|
||||
{
|
||||
"command": "git.unstageSelectedRanges",
|
||||
"group": "2_git@2",
|
||||
"when": "config.git.enabled && scmProvider == git && isInDiffEditor"
|
||||
},
|
||||
{
|
||||
"command": "git.revertSelectedRanges",
|
||||
"group": "2_git@3",
|
||||
"when": "config.git.enabled && scmProvider == git && isInDiffEditor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Git",
|
||||
"properties": {
|
||||
"git.enabled": {
|
||||
"type": "boolean",
|
||||
"description": "%config.enabled%",
|
||||
"default": true
|
||||
},
|
||||
"git.path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "%config.path%",
|
||||
"default": null,
|
||||
"isExecutable": true
|
||||
},
|
||||
"git.autorefresh": {
|
||||
"type": "boolean",
|
||||
"description": "%config.autorefresh%",
|
||||
"default": true
|
||||
},
|
||||
"git.autofetch": {
|
||||
"type": "boolean",
|
||||
"description": "%config.autofetch%",
|
||||
"default": true
|
||||
},
|
||||
"git.confirmSync": {
|
||||
"type": "boolean",
|
||||
"description": "%config.confirmSync%",
|
||||
"default": true
|
||||
},
|
||||
"git.countBadge": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all",
|
||||
"tracked",
|
||||
"off"
|
||||
],
|
||||
"description": "%config.countBadge%",
|
||||
"default": "all"
|
||||
},
|
||||
"git.checkoutType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"all",
|
||||
"local",
|
||||
"tags",
|
||||
"remote"
|
||||
],
|
||||
"description": "%config.checkoutType%",
|
||||
"default": "all"
|
||||
},
|
||||
"git.ignoreLegacyWarning": {
|
||||
"type": "boolean",
|
||||
"description": "%config.ignoreLegacyWarning%",
|
||||
"default": false
|
||||
},
|
||||
"git.ignoreLimitWarning": {
|
||||
"type": "boolean",
|
||||
"description": "%config.ignoreLimitWarning%",
|
||||
"default": false
|
||||
},
|
||||
"git.defaultCloneDirectory": {
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"description": "%config.defaultCloneDirectory%"
|
||||
},
|
||||
"git.enableSmartCommit": {
|
||||
"type": "boolean",
|
||||
"description": "%config.enableSmartCommit%",
|
||||
"default": false
|
||||
},
|
||||
"git.enableEditorMerge": {
|
||||
"type": "boolean",
|
||||
"description": "%config.enableEditorMerge%",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"iconv-lite": "0.4.15",
|
||||
"vscode-extension-telemetry": "^0.0.7",
|
||||
"vscode-nls": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^7.0.4",
|
||||
"mocha": "^3.2.0"
|
||||
}
|
||||
}
|
66
extensions/merge-conflict/src/services.ts
Normal file
66
extensions/merge-conflict/src/services.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import DocumentTracker from './documentTracker';
|
||||
import CodeLensProvider from './codelensProvider';
|
||||
import CommandHandler from './commandHandler';
|
||||
import ContentProvider from './contentProvider';
|
||||
import Decorator from './mergeDecorator';
|
||||
import * as interfaces from './interfaces';
|
||||
|
||||
const ConfigurationSectionName = 'git';
|
||||
|
||||
export default class ServiceWrapper implements vscode.Disposable {
|
||||
|
||||
private services: vscode.Disposable[] = [];
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
begin() {
|
||||
|
||||
let configuration = this.createExtensionConfiguration();
|
||||
const documentTracker = new DocumentTracker();
|
||||
|
||||
this.services.push(
|
||||
documentTracker,
|
||||
new CommandHandler(this.context, documentTracker),
|
||||
new CodeLensProvider(this.context, documentTracker),
|
||||
new ContentProvider(this.context),
|
||||
new Decorator(this.context, documentTracker),
|
||||
);
|
||||
|
||||
this.services.forEach((service: any) => {
|
||||
if (service.begin && service.begin instanceof Function) {
|
||||
service.begin(configuration);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(() => {
|
||||
this.services.forEach((service: any) => {
|
||||
if (service.configurationUpdated && service.configurationUpdated instanceof Function) {
|
||||
service.configurationUpdated(this.createExtensionConfiguration());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
createExtensionConfiguration(): interfaces.IExtensionConfiguration {
|
||||
const workspaceConfiguration = vscode.workspace.getConfiguration(ConfigurationSectionName);
|
||||
const isEnabled: boolean = workspaceConfiguration.get('enableEditorMerge', true);
|
||||
|
||||
return {
|
||||
enableCodeLens: isEnabled,
|
||||
enableDecorations: isEnabled,
|
||||
enableEditorOverview: isEnabled
|
||||
};
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.services.forEach(disposable => disposable.dispose());
|
||||
this.services = [];
|
||||
}
|
||||
}
|
||||
|
9
extensions/merge-conflict/src/typings/refs.d.ts
vendored
Normal file
9
extensions/merge-conflict/src/typings/refs.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
/// <reference types='@types/mocha'/>
|
15
extensions/merge-conflict/tsconfig.json
Normal file
15
extensions/merge-conflict/tsconfig.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"es2016"
|
||||
],
|
||||
"module": "commonjs",
|
||||
"outDir": "./out",
|
||||
"strictNullChecks": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
Loading…
Reference in a new issue