1
1
mirror of https://github.com/kahole/edamagit.git synced 2024-09-11 07:15:31 +03:00

refactor, create menu system

This commit is contained in:
kahole 2019-12-08 19:57:55 +01:00
parent e82e8a8737
commit 0a6b90bf58
20 changed files with 455 additions and 153 deletions

View File

@ -1,7 +1,7 @@
{
"folding": {
"markers": {
"start": "^(Untracked files)|((Unstaged|Staged) changes)|(Recent commits)",
"start": "^(Untracked files)|((Unstaged|Staged) changes)|(Stashes)|(Recent commits)",
"end": "^\\.$"
}
}

View File

@ -26,19 +26,33 @@
},
{
"command": "extension.magit-key-b",
"title": "Magit Branch",
"title": "Magit Branching",
"when": "editorLangId == magit-status"
},
{
"command": "extension.magit-pushing",
"title": "Magit Pushing",
"when": "editorLangId == magit-status"
},
{
"command": "extension.magit-help",
"title": "Magit Help",
"when": "editorLangId == magit-status"
}
],
"menus": {
"commandPalette": [
{
"command": "extension.magit-key-b",
"when": "false"
},
{
"command": "extension.magit-choose",
"when": "editorLangId == magit-status"
},
{
"command": "extension.magit-key-b",
"when": "false && editorLangId == magit-status"
"command": "extension.magit-pushing",
"when": "editorLangId == magit-status"
}
]
},
@ -72,6 +86,16 @@
"command": "extension.magit-key-b",
"key": "b",
"when": "editorTextFocus && editorLangId == magit-status"
},
{
"command": "extension.magit-pushing",
"key": "shift+p",
"when": "editorTextFocus && editorLangId == magit-status"
},
{
"command": "extension.magit-help",
"key": "?",
"when": "editorTextFocus && editorLangId == magit-status"
}
]
},

View File

@ -0,0 +1,6 @@
import { BranchingMenu } from "../menus/branching/branchingMenu";
import { MagitPicker } from "../menus/magitPicker";
export function branching() {
MagitPicker.showMagitPicker(new BranchingMenu());
}

View File

@ -0,0 +1,23 @@
import { window } from "vscode";
export function magitHelp() {
window.showInformationMessage(`Popup and dwim commands
A Cherry-picking b Branching B Bisecting c Committing
d Diffing D Change diffs e Ediff dwimming E Ediffing
f Fetching F Pulling l Logging L Change logs
m Merging M Remoting o Submodules O Subtrees
P Pushing r Rebasing t Tagging T Notes
V Reverting w Apply patches W Format patches X Resetting
y Show Refs z Stashing ! Running % Worktree
Applying changes
a Apply s Stage u Unstage
v Reverse S Stage all U Unstage all
k Discard
Essential commands
g refresh current buffer
TAB toggle section at point
RET visit thing at point
C-h m show all key bindings `);
}

View File

@ -1,11 +0,0 @@
import { MagitState } from "../model/magitStatus";
// export async function magitStatus() : Promise<MagitStatus> {
// return {
// head: "",
// untrackedFiles: [],
// unstagedChanges: [],
// stashes: [],
// recentCommits: []
// };
// }

View File

@ -0,0 +1,7 @@
import { MagitPicker } from "../menus/magitPicker";
import { PushingMenu } from "../menus/pushing/pushingMenu";
export function pushing() {
MagitPicker.showMagitPicker(new PushingMenu());
}

View File

@ -0,0 +1,94 @@
import { MagitState } from "../model/magitStatus";
import { MagitChange } from "../model/magitChange";
import { encodeLocation } from "../contentProvider";
import { workspace, window, ViewColumn } from "vscode";
import { gitApi, magitStates } from "../extension";
import { isDescendant, keepOnlyChunksFromDiff } from "../util";
import { Repository, Status } from "../typings/git";
export function magitStatus() {
if (workspace.workspaceFolders && workspace.workspaceFolders[0]) {
const rootPath = workspace.workspaceFolders[0].uri.fsPath;
const repository = gitApi.repositories.filter(r => isDescendant(r.rootUri.fsPath, rootPath))[0];
const uri = encodeLocation(rootPath);
MagitStatus(repository)
.then(m => {
magitStates[uri.query] = m;
workspace.openTextDocument(uri).then(doc => window.showTextDocument(doc, ViewColumn.Beside));
});
}
}
async function MagitStatus(repository: Repository): Promise<MagitState> {
await repository.status();
let stashTask = repository._repository.getStashes();
let logTask = repository.log({ maxEntries: 10 });
let commitTasks = Promise.all(
[repository.state.HEAD!.commit!]
.map(c => repository.getCommit(c)));
// .map(repository.getCommit));
let untrackedFiles: MagitChange[] = [];
let workingTreeChanges_NoUntracked = repository.state.workingTreeChanges
.filter( c => {
if (c.status === Status.UNTRACKED) {
untrackedFiles.push(c);
return false;
} else {
return true;
}
});
let workingTreeChangesTasks = Promise.all(workingTreeChanges_NoUntracked
.map(change =>
repository.diffWithHEAD(change.uri.path)
.then(keepOnlyChunksFromDiff)
.then<MagitChange>(diff => {
let magitChange: MagitChange = change;
magitChange.diff = diff;
return magitChange;
})
));
let indexChangesTasks = Promise.all(repository.state.indexChanges
.map(change =>
repository.diffIndexWithHEAD(change.uri.path)
.then(keepOnlyChunksFromDiff)
.then<MagitChange>(diff => {
let magitChange: MagitChange = change;
magitChange.diff = diff;
return magitChange;
})
));
let [commits, workingTreeChanges, indexChanges, stashes, log] =
await Promise.all([
commitTasks,
workingTreeChangesTasks,
indexChangesTasks,
stashTask,
logTask
]);
let commitCache = commits.reduce((prev, commit) => ({ ...prev, [commit.hash]: commit }), {});
return {
_state: repository.state,
stashes,
log,
commitCache,
workingTreeChanges,
indexChanges,
mergeChanges: undefined,
untrackedFiles
};
}

View File

@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import { MagitState, Change } from '../model/magitStatus';
import { MagitState } from '../model/magitStatus';
import { MagitChange } from "../model/magitChange";
import { Status } from '../typings/git';
export default class StatusDocument {
@ -45,10 +46,19 @@ export default class StatusDocument {
this._lines.push(this.SECTION_FOLD_REGION_END);
}
if (magitStatus.stashes) {
this._lines.push(`Stashes (${magitStatus.stashes.length})`);
magitStatus.stashes
.forEach(stash => {
this._lines.push(`stash@{${stash.index}} ${stash.description}`);
});
this._lines.push(this.SECTION_FOLD_REGION_END);
}
if (magitStatus.log) {
this._lines.push(`Recent commits`);
magitStatus.log
.forEach(commit => {
this._lines.push(`Recent commits`);
this._lines.push(`${commit.hash.slice(0, 7)} ${commit.message}`);
});
this._lines.push(this.SECTION_FOLD_REGION_END);
@ -57,7 +67,7 @@ export default class StatusDocument {
this._lines.push('');
}
private renderChanges(changes: Change[]) : string[] {
private renderChanges(changes: MagitChange[]) : string[] {
return changes
.map(change => `${mapFileStatusToLabel(change.status)} ${change.uri.path}${change.diff ? '\n' + change.diff : ''}`);
}

View File

@ -1,46 +1,48 @@
import { workspace, languages, window, extensions, commands, ExtensionContext, Disposable, ViewColumn, FileChangeType } from 'vscode';
import ContentProvider, { encodeLocation } from './contentProvider';
import { GitExtension } from './typings/git';
import { MagitStatus, MagitState } from './model/magitStatus';
import { isDescendant } from './util';
import { workspace, extensions, commands, ExtensionContext, Disposable } from 'vscode';
import ContentProvider from './contentProvider';
import { GitExtension, API } from './typings/git';
import { pushing } from './commands/pushingCommands';
import { branching } from './commands/branchingCommands';
import { magitHelp } from './commands/helpCommands';
import { magitStatus } from './commands/statusCommands';
import { MagitState } from './model/magitStatus';
export let magitStates: {[id: string]: MagitState} = {};
export let magitStates: { [id: string]: MagitState } = {};
export let gitApi: API;
export function activate(context: ExtensionContext) {
if (workspace.workspaceFolders && workspace.workspaceFolders[0]) {
let gitExtension = extensions.getExtension<GitExtension>('vscode.git')!.exports;
if (!gitExtension.enabled) {
throw new Error("vscode.git Git extension not enabled");
}
const gitApi = gitExtension.getAPI(1);
const rootPath = workspace.workspaceFolders[0].uri.fsPath;
const repository = gitApi.repositories.filter(r => isDescendant(r.rootUri.fsPath, rootPath))[0];
const provider = new ContentProvider();
const providerRegistrations = Disposable.from(
workspace.registerTextDocumentContentProvider(ContentProvider.scheme, provider)
);
let disposable = commands.registerCommand('extension.magit', async () => {
const uri = encodeLocation(rootPath);
MagitStatus(repository)
.then(m => {
magitStates[uri.query] = m;
workspace.openTextDocument(uri).then(doc => window.showTextDocument(doc, ViewColumn.Beside));
});
});
context.subscriptions.push(
provider,
providerRegistrations,
disposable
);
let gitExtension = extensions.getExtension<GitExtension>('vscode.git')!.exports;
if (!gitExtension.enabled) {
throw new Error("vscode.git Git extension not enabled");
}
// TODO: Ikke sikkert dette er et problem. Kan være magit virker fortsatt!
// gitExtension.onDidChangeEnablement(enabled => {
// if (!enabled) {
// throw new Error("vscode.git Git extension was disabled");
// }
// });
gitApi = gitExtension.getAPI(1);
// gitExecutablePath = gitApi.git.path;
const provider = new ContentProvider();
const providerRegistrations = Disposable.from(
workspace.registerTextDocumentContentProvider(ContentProvider.scheme, provider)
);
context.subscriptions.push(
provider,
providerRegistrations,
);
context.subscriptions.push(commands.registerCommand('extension.magit', magitStatus));
context.subscriptions.push(commands.registerCommand('extension.magit-help', magitHelp));
context.subscriptions.push(commands.registerCommand('extension.magit-key-F', async () => {
// TODO: Options should be dynamically decided based on whether or not they can be done
// e.g Pull in magit with no remotes results in: e elsewhere
}));
context.subscriptions.push(commands.registerCommand('extension.magit-pushing', pushing));
context.subscriptions.push(commands.registerCommand('extension.magit-key-b', branching));
}
// this method is called when your extension is deactivated

37
src/gitApiExtensions.ts Normal file
View File

@ -0,0 +1,37 @@
import { Repository } from "./typings/git";
export interface BaseRepository extends Repository {
getStashes(): Promise<Stash[]>;
pushTo(remote?: string, name?: string, setUpstream?: boolean, forcePushMode?: ForcePushMode): Promise<void>;
}
// Repository.prototype.getStashes = function(this: any) {
// return this._repository.getStashes();
// };
declare module "./typings/git" {
export interface Repository {
readonly _repository: BaseRepository;
}
}
// Repository.prototype.getStashes = function(this: any) {
// return this._repository.getStashes();
// };
// (Repository.fn as any).getStashes = function () {
// const _self = this as moment.Moment;
// return _self.year() - 1911;
// }
// types from /extensions/git/src/git.ts
export interface Stash {
index: number;
description: string;
}
export enum ForcePushMode {
Force,
ForceWithLease
}

View File

@ -0,0 +1,8 @@
import { MenuItem } from "./menuItem";
export interface Menu {
title?: string;
items: MenuItem[];
onDidAcceptItems(acceptedItems: readonly MenuItem[]): Menu | undefined;
isSwitchesMenu?: boolean;
}

View File

@ -0,0 +1,5 @@
import { QuickPick, QuickPickItem, window } from "vscode";
export interface MenuItem extends QuickPickItem {
id: number;
}

View File

@ -0,0 +1,57 @@
import { Menu } from "../abstract/menu";
import { MenuItem } from "../abstract/menuItem";
import { MagitState } from "../../model/magitStatus";
enum Items {
Checkout,
Configure,
CreateNewSpinoff,
CheckoutNewBranch,
Reset,
CreateNewWorktree,
CheckoutPullRequest,
CheckoutLocalBranch,
Rename,
CreateNewBranch,
CheckoutNewWorktree,
Delete,
CreateFromPullRequest
}
export class BranchingMenu implements Menu {
title: string = "Branching";
items: MenuItem[];
constructor() {
this.items = [
{id: Items.Checkout , label: "b", description: "Checkout"},
{id: Items.Configure , label: "C", description: "Configure"},
{id: Items.CreateNewSpinoff , label: "s", description: "Create new spin-off"},
{id: Items.CheckoutNewBranch , label: "c", description: "Checkout new branch"},
{id: Items.Reset , label: "x", description: "Reset"},
{id: Items.CreateNewWorktree , label: "W", description: "Create new worktree"},
{id: Items.CheckoutPullRequest , label: "y", description: "Checkout pull-request"},
{id: Items.CheckoutLocalBranch , label: "l", description: "Checkout local branch"},
{id: Items.Rename , label: "m", description: "Rename"},
{id: Items.CreateNewBranch , label: "n", description: "Create new branch"},
{id: Items.CheckoutNewWorktree , label: "w", description: "Checkout new worktree"},
{id: Items.Delete , label: "k", description: "Delete"},
{id: Items.CreateFromPullRequest , label: "Y", description: "Create from pull-request"}
];
}
onDidAcceptItems(acceptedItems: readonly MenuItem[]): Menu | undefined {
// Weak abstraction in this function
let firstItem = acceptedItems[0];
switch (firstItem.id) {
case Items.Checkout:
// branchQuickPick.hide();
// window.showQuickPick(["master", "exp", "origin/master"]);
default:
return undefined;
}
}
}

41
src/menus/magitPicker.ts Normal file
View File

@ -0,0 +1,41 @@
import { QuickPick, window } from "vscode";
import { MenuItem } from "./abstract/menuItem";
import { Menu } from "./abstract/menu";
export class MagitPicker {
private _quickPick: QuickPick<MenuItem>;
static showMagitPicker(menu: Menu) {
new MagitPicker(menu).show();
}
constructor(private _menu: Menu) {
this._quickPick = window.createQuickPick();
this.loadMenu(this._menu);
this._quickPick.onDidAccept(() => {
let nextMenu = this._menu.onDidAcceptItems(this._quickPick.activeItems);
if (nextMenu) {
this._menu = nextMenu;
this.loadMenu(this._menu);
}
});
}
loadMenu(menu: Menu) {
this._quickPick.title = menu.title;
if (menu.isSwitchesMenu) {
this._quickPick.canSelectMany = true;
this._quickPick.title = "Switches (select with <space>)";
}
this._quickPick.items = menu.items;
}
show() {
this._quickPick.show();
}
}

View File

@ -0,0 +1,32 @@
import { Menu } from "../abstract/menu";
import { MenuItem } from "../abstract/menuItem";
import { MagitState } from "../../model/magitStatus";
import { PushingSwitches } from "./switches";
enum Items {
Switches
}
export class PushingMenu implements Menu {
title: string = "Pushing";
items: MenuItem[];
constructor() {
// TODO: take in some state to customize and narrow selection!
this.items = [{id: Items.Switches, label: "-", description: "Switches"}];
}
onDidAcceptItems(acceptedItems: readonly MenuItem[]): Menu | undefined {
// Weak abstraction in this function
let firstItem = acceptedItems[0];
switch (firstItem.id) {
case Items.Switches:
return new PushingSwitches();
default:
return undefined;
}
}
}

View File

@ -0,0 +1,44 @@
import { Menu } from "../abstract/menu";
import { MenuItem } from "../abstract/menuItem";
import { MagitState } from "../../model/magitStatus";
enum Items {
ForceWithLease,
Force,
DisableHooks,
DryRun
}
export class PushingSwitches implements Menu {
items: MenuItem[];
isSwitchesMenu = true;
constructor() {
this.items = [
{ id: Items.ForceWithLease, label: "-f", description: "force with lease (--force-with-lease)" },
{ id: Items.Force, label: "-F", description: "force (--force)" },
{ id: Items.DisableHooks, label: "-h", description: "disable hooks (--no-verify)" },
{ id: Items.DryRun, label: "-d", description: "Dry run (--dry-run)" },
];
}
onDidAcceptItems(acceptedItems: readonly MenuItem[]): Menu | undefined {
// Weak abstraction in this function
let firstItem = acceptedItems[0];
switch (firstItem.id) {
case Items.ForceWithLease:
return undefined;
case Items.Force:
return undefined;
case Items.DisableHooks:
return undefined;
case Items.DryRun:
return undefined;
default:
return undefined;
}
}
}

5
src/model/magitChange.ts Normal file
View File

@ -0,0 +1,5 @@
import { Change } from "../typings/git";
export interface MagitChange extends Change {
diff?: string;
}

View File

@ -1,100 +1,14 @@
import { RepositoryState, Commit, Repository, Change as GitChange, Status } from "../typings/git";
export interface Change extends GitChange {
diff?: string;
}
import { RepositoryState, Commit } from "../typings/git";
import { Stash } from "../gitApiExtensions";
import { MagitChange } from "./magitChange";
export interface MagitState {
_state: RepositoryState; // TODO: shouldnt need this?
readonly stashes?: Commit[];
readonly log?: Commit[];
readonly commitCache: { [id: string]: Commit; }; // TODO: rigid model with everything needed better?
readonly workingTreeChanges?: Change[];
readonly indexChanges?: Change[];
readonly mergeChanges?: Change[];
readonly untrackedFiles?: Change[];
}
export async function MagitStatus(repository: Repository): Promise<MagitState> {
await repository.status();
let logTask = repository.log({ maxEntries: 10 });
let commitTasks = Promise.all(
[repository.state.HEAD!.commit!]
.map(c => repository.getCommit(c)));
// .map(repository.getCommit));
let untrackedFiles: Change[] = [];
let workingTreeChanges_NoUntracked = repository.state.workingTreeChanges
.filter( c => {
if (c.status === Status.UNTRACKED) {
untrackedFiles.push(c);
return false;
} else {
return true;
}
});
let workingTreeChangesTasks = Promise.all(workingTreeChanges_NoUntracked
.map(change =>
repository.diffWithHEAD(change.uri.path)
.then(getOnlyChunksFromDiff)
.then<Change>(diff => {
let magitChange: Change = change;
magitChange.diff = diff;
return magitChange;
})
));
let indexChangesTasks = Promise.all(repository.state.indexChanges
.map(change =>
repository.diffIndexWithHEAD(change.uri.path)
.then(getOnlyChunksFromDiff)
.then<Change>(diff => {
let magitChange: Change = change;
magitChange.diff = diff;
return magitChange;
})
));
let [log, commits, workingTreeChanges, indexChanges] =
await Promise.all([
logTask,
commitTasks,
workingTreeChangesTasks,
indexChangesTasks
]);
let commitCache = commits.reduce((prev, commit) => ({ ...prev, [commit.hash]: commit }), {});
return {
_state: repository.state,
stashes: undefined,
log,
commitCache,
workingTreeChanges,
indexChanges,
mergeChanges: undefined,
untrackedFiles
};
}
function getOnlyChunksFromDiff(diff: string): string {
return diff.substring(diff.indexOf("@@"));
}
// function inflateChanges(changes: GitChange[]) : Promise<Change[]> {
// return Promise.all(changes
// .map(change =>
// repository.diffIndexWithHEAD(change.uri.path)
// .then(getOnlyChunksFromDiff)
// .then<Change>(diff => {
// let magitChange: Change = change;
// magitChange.diff = diff;
// return magitChange;
// })
// ));
// }
readonly workingTreeChanges?: MagitChange[];
readonly indexChanges?: MagitChange[];
readonly mergeChanges?: MagitChange[];
readonly untrackedFiles?: MagitChange[];
readonly stashes?: Stash[];
readonly log?: Commit[];
}

View File

@ -21,4 +21,8 @@ export function isDescendant(parent: string, descendant: string): boolean {
}
return descendant.startsWith(parent);
}
export function keepOnlyChunksFromDiff(diff: string): string {
return diff.substring(diff.indexOf("@@"));
}

View File

@ -6,7 +6,7 @@
"patterns": [{ "include": "#sectionHeader" }, { "include": "#branchName" }]
},
"sectionHeader": {
"match": "Untracked files|((Unstaged|Staged) changes)|Recent commits",
"match": "Untracked files|((Unstaged|Staged) changes)|Stashes|Recent commits",
"name": "keyword.sectionHeader",
"comment": ";; kan bruke markup.bold.sectionHeader"
},