提交 d1b9a70d 编写于 作者: J Joao Moreno

git: data uris

上级 1a54aa89
{
"name": "git",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/file-type": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@types/file-type/-/file-type-5.2.1.tgz",
"integrity": "sha512-Im0cJaIPJbbpuW91OrjXnqWPZCJK/tcFy2cFX+1qjG1gubgVZPPO9OVsTVAjotN4I1E6FAV0eIqt+rR8Y1c3iA==",
"dev": true,
"requires": {
"@types/node": "8.0.50"
},
"dependencies": {
"@types/node": {
"version": "8.0.50",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.0.50.tgz",
"integrity": "sha512-N9OVsMBspboNvYaLAQnLEhb2eQ96lavogMR5LoH5k8nb1PvBZHSBFhzhsq2LNzGTBBOtBviOc1GiSu+wlM/pGw==",
"dev": true
}
}
},
"applicationinsights": {
"version": "0.18.0",
"from": "applicationinsights@0.18.0",
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz"
"resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-0.18.0.tgz",
"integrity": "sha1-Fi67SKODQIvE3kTbMrQXMH9Fu8E="
},
"byline": {
"version": "5.0.0",
"from": "byline@latest",
"resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz"
"resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz",
"integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE="
},
"file-type": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-7.2.0.tgz",
"integrity": "sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q="
},
"iconv-lite": {
"version": "0.4.19",
"from": "iconv-lite@0.4.19",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz"
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
"integrity": "sha1-90aPYBNfXl2tM5nAqBvpoWA6CCs="
},
"vscode-extension-telemetry": {
"version": "0.0.8",
"from": "vscode-extension-telemetry@>=0.0.8 <0.0.9",
"resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz"
"resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz",
"integrity": "sha1-ImG/+Ya2aQpvH3RqRaxb0fhdKeA=",
"requires": {
"applicationinsights": "0.18.0",
"winreg": "1.2.3"
}
},
"vscode-nls": {
"version": "2.0.2",
"from": "vscode-nls@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz"
"resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-2.0.2.tgz",
"integrity": "sha1-gIUiOAhEuK0VNJmvXDsDkhrqAto="
},
"winreg": {
"version": "1.2.3",
"from": "winreg@1.2.3",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz"
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.3.tgz",
"integrity": "sha1-k60RayaW2ofVj3JlqPzqUlSpZdU="
}
}
}
......@@ -909,14 +909,16 @@
},
"dependencies": {
"byline": "^5.0.0",
"file-type": "^7.2.0",
"iconv-lite": "0.4.19",
"vscode-extension-telemetry": "0.0.8",
"vscode-nls": "2.0.2"
},
"devDependencies": {
"@types/byline": "4.2.31",
"@types/file-type": "^5.2.1",
"@types/mocha": "2.2.43",
"@types/node": "7.0.43",
"@types/byline": "4.2.31",
"mocha": "^3.2.0"
}
}
\ No newline at end of file
......@@ -127,6 +127,15 @@ function command(commandId: string, options: CommandOptions = {}): Function {
};
}
const ImageMimetypes = [
'image/png',
'image/gif',
'image/jpeg',
'image/webp',
'image/tiff',
'image/bmp'
];
export class CommandCenter {
private disposables: Disposable[];
......@@ -159,8 +168,8 @@ export class CommandCenter {
}
private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise<void> {
const left = this.getLeftResource(resource);
const right = this.getRightResource(resource);
const left = await this.getLeftResource(resource);
const right = await this.getRightResource(resource);
const title = this.getTitle(resource);
if (!right) {
......@@ -184,56 +193,70 @@ export class CommandCenter {
}
if (!left) {
if (right.scheme === 'git') {
const repository = this.model.getRepository(right);
await commands.executeCommand<void>('vscode.open', right, opts);
} else {
await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
}
}
if (repository) {
const { path, ref } = fromGitUri(right);
private async getURI(uri: Uri, ref: string): Promise<Uri | undefined> {
const repository = this.model.getRepository(uri);
if (/png$/i.test(path)) {
const contents = await repository.buffer(ref, path);
const uri = Uri.parse(`data:image/png;label:${'Label'};description:${'Description'};base64,${contents.toString('base64')}`); // TODO@JOao
if (!repository) {
return toGitUri(uri, ref);
}
await commands.executeCommand<void>('vscode.open', uri, opts);
return;
try {
const { size, object } = await repository.lstree(ref, uri.fsPath);
if (size > 5000000) { // 5 MB
return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${ref},`);
}
const { mimetype, encoding } = await repository.detectObjectType(object);
if (mimetype === 'text/plain') {
return toGitUri(uri, ref);
}
if (ImageMimetypes.indexOf(mimetype) > -1) {
const contents = await repository.buffer(ref, uri.fsPath);
return Uri.parse(`data:${mimetype};label:${path.basename(uri.fsPath)};description:${ref};size:${size};base64,${contents.toString('base64')}`);
}
await commands.executeCommand<void>('vscode.open', right, opts);
return Uri.parse(`data:;label:${path.basename(uri.fsPath)};description:${ref},`);
return;
} catch (err) {
return toGitUri(uri, ref);
}
return await commands.executeCommand<void>('vscode.diff', left, right, title, opts);
}
private getLeftResource(resource: Resource): Uri | undefined {
private async getLeftResource(resource: Resource): Promise<Uri | undefined> {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_RENAMED:
return toGitUri(resource.original, 'HEAD');
return this.getURI(resource.original, 'HEAD');
case Status.MODIFIED:
return toGitUri(resource.resourceUri, '~');
return this.getURI(resource.resourceUri, '~');
case Status.DELETED_BY_THEM:
return toGitUri(resource.resourceUri, '');
return this.getURI(resource.resourceUri, '');
}
}
private getRightResource(resource: Resource): Uri | undefined {
private async getRightResource(resource: Resource): Promise<Uri | undefined> {
switch (resource.type) {
case Status.INDEX_MODIFIED:
case Status.INDEX_ADDED:
case Status.INDEX_COPIED:
case Status.INDEX_RENAMED:
return toGitUri(resource.resourceUri, '');
return this.getURI(resource.resourceUri, '');
case Status.INDEX_DELETED:
case Status.DELETED_BY_THEM:
case Status.DELETED:
return toGitUri(resource.resourceUri, 'HEAD');
return this.getURI(resource.resourceUri, 'HEAD');
case Status.MODIFIED:
case Status.UNTRACKED:
......
......@@ -11,7 +11,8 @@ import * as os from 'os';
import * as cp from 'child_process';
import { EventEmitter } from 'events';
import iconv = require('iconv-lite');
import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp } from './util';
import * as filetype from 'file-type';
import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding } from './util';
const readfile = denodeify<string>(fs.readFile);
......@@ -569,6 +570,59 @@ export class Repository {
return stdout;
}
async lstree(treeish: string, path: string): Promise<{ mode: number, type: string, object: string, size: number }> {
const { stdout } = await this.run(['ls-tree', '-l', treeish, '--', path]);
const match = /^(\d+)\s+(\w+)\s+([0-9a-f]{40})\s+(\d+)/.exec(stdout);
if (!match) {
throw new GitError({ message: 'Error running ls-tree' });
}
const [, mode, type, object, size] = match;
return { mode: parseInt(mode), type, object, size: parseInt(size) };
}
async detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
const child = await this.stream(['show', object]);
const buffer = await readBytes(child.stdout, 4100);
try {
child.kill();
} catch (err) {
// noop
}
const encoding = detectUnicodeEncoding(buffer);
let isText = true;
if (encoding !== Encoding.UTF16be && encoding !== Encoding.UTF16le) {
for (let i = 0; i < buffer.length; i++) {
if (buffer.readInt8(i) === 0) {
isText = false;
break;
}
}
}
if (!isText) {
const result = filetype(buffer);
if (!result) {
return { mimetype: 'application/octet-stream' };
} else {
return { mimetype: result.mime };
}
}
if (encoding) {
return { mimetype: 'text/plain', encoding };
} else {
// TODO@JOAO: read the setting OUTSIDE!
return { mimetype: 'text/plain' };
}
}
async add(paths: string[]): Promise<void> {
const args = ['add', '-A', '--'];
......
......@@ -300,7 +300,8 @@ export enum Operation {
Ignore = 'Ignore',
Tag = 'Tag',
Stash = 'Stash',
CheckIgnore = 'CheckIgnore'
CheckIgnore = 'CheckIgnore',
LSTree = 'LSTree'
}
function isReadOnly(operation: Operation): boolean {
......@@ -308,6 +309,7 @@ function isReadOnly(operation: Operation): boolean {
case Operation.Show:
case Operation.GetCommitTemplate:
case Operation.CheckIgnore:
case Operation.LSTree:
return true;
default:
return false;
......@@ -318,6 +320,8 @@ function shouldShowProgress(operation: Operation): boolean {
switch (operation) {
case Operation.Fetch:
case Operation.CheckIgnore:
case Operation.LSTree:
case Operation.Show:
return false;
default:
return true;
......@@ -679,6 +683,7 @@ export class Repository implements Disposable {
const configFiles = workspace.getConfiguration('files', Uri.file(filePath));
const encoding = configFiles.get<string>('encoding');
// TODO@joao: Resource config api
return await this.repository.bufferString(`${ref}:${relativePath}`, encoding);
});
}
......@@ -687,11 +692,21 @@ export class Repository implements Disposable {
return await this.run(Operation.Show, async () => {
const relativePath = path.relative(this.repository.root, filePath).replace(/\\/g, '/');
const configFiles = workspace.getConfiguration('files', Uri.file(filePath));
const encoding = configFiles.get<string>('encoding');
// TODO@joao: REsource config api
return await this.repository.buffer(`${ref}:${relativePath}`);
});
}
lstree(ref: string, filePath: string): Promise<{ mode: number, type: string, object: string, size: number }> {
return this.run(Operation.LSTree, () => this.repository.lstree(ref, filePath));
}
detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }> {
return this.run(Operation.Show, () => this.repository.detectObjectType(object));
}
async getStashes(): Promise<Stash[]> {
return await this.repository.getStashes();
}
......
......@@ -7,6 +7,7 @@
import { Event } from 'vscode';
import { dirname } from 'path';
import { Readable } from 'stream';
import * as fs from 'fs';
import * as byline from 'byline';
......@@ -206,3 +207,70 @@ export async function grep(filename: string, pattern: RegExp): Promise<boolean>
stream.on('end', () => c(false));
});
}
export function readBytes(stream: Readable, bytes: number): Promise<Buffer> {
return new Promise<Buffer>((complete, error) => {
let done = false;
let buffer = new Buffer(bytes);
let bytesRead = 0;
stream.on('data', (data: Buffer) => {
let bytesToRead = Math.min(bytes - bytesRead, data.length);
data.copy(buffer, bytesRead, 0, bytesToRead);
bytesRead += bytesToRead;
if (bytesRead === bytes) {
(stream as any).destroy(); // Will trigger the close event eventually
}
});
stream.on('error', (e: Error) => {
if (!done) {
done = true;
error(e);
}
});
stream.on('close', () => {
if (!done) {
done = true;
complete(buffer.slice(0, bytesRead));
}
});
});
}
export enum Encoding {
UTF8 = 'utf8',
UTF16be = 'utf16be',
UTF16le = 'utf16le'
}
export function detectUnicodeEncoding(buffer: Buffer): Encoding | null {
if (buffer.length < 2) {
return null;
}
const b0 = buffer.readUInt8(0);
const b1 = buffer.readUInt8(1);
if (b0 === 0xFE && b1 === 0xFF) {
return Encoding.UTF16be;
}
if (b0 === 0xFF && b1 === 0xFE) {
return Encoding.UTF16le;
}
if (buffer.length < 3) {
return null;
}
const b2 = buffer.readUInt8(2);
if (b0 === 0xEF && b1 === 0xBB && b2 === 0xBF) {
return Encoding.UTF8;
}
return null;
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册