* initial copy paste code

* initial copy-paste pipe

* added copy paste functionality

* updated package.json

* added tests; removed console logs

* removed unnecessary code
This commit is contained in:
Anthony Dresser 2016-09-15 11:51:35 -07:00 коммит произвёл GitHub
Родитель 9428664c43
Коммит 35cd4a881b
13 изменённых файлов: 166 добавлений и 17 удалений

Просмотреть файл

@ -56,20 +56,22 @@
"vscode": "^0.11.0"
},
"dependencies": {
"body-parser": "^1.15.2",
"copy-paste": "^1.3.0",
"decompress": "^4.0.0",
"ejs": "^2.4.2",
"error-ex": "^1.3.0",
"express": "^4.13.3",
"figures": "^1.4.0",
"getmac": "1.2.1",
"request": "^2.73.0",
"underscore": "^1.8.3",
"vscode-extension-telemetry": "^0.0.5",
"vscode-languageclient": "^2.0.0",
"fs-extra-promise": "^0.3.1",
"getmac": "1.2.1",
"http-proxy-agent": "^1.0.0",
"https-proxy-agent": "^1.0.0",
"request": "^2.73.0",
"tmp": "^0.0.28",
"decompress": "^4.0.0"
"underscore": "^1.8.3",
"vscode-extension-telemetry": "^0.0.5",
"vscode-languageclient": "^2.0.0"
},
"contributes": {
"languages": [

Просмотреть файл

@ -3,6 +3,7 @@ import path = require('path');
import Utils = require('../models/utils');
import Constants = require('../models/constants');
import Interfaces = require('../models/interfaces');
const bodyParser = require('body-parser');
const express = require('express');
export default class LocalWebService {
@ -17,6 +18,7 @@ export default class LocalWebService {
LocalWebService._vscodeExtensionPath = extensionPath;
LocalWebService._staticContentPath = path.join(extensionPath, LocalWebService._htmlContentLocation);
this.app.use(express.static(LocalWebService.staticContentPath));
this.app.use( bodyParser.json() );
this.app.set('view engine', 'ejs');
Utils.logDebug(Constants.msgLocalWebserviceStaticContent + LocalWebService.staticContentPath);
}
@ -42,6 +44,11 @@ export default class LocalWebService {
this.app.get(segment, handler);
}
addPostHandler(type: Interfaces.ContentType, handler: (req, res) => void): void {
let segment = '/' + Interfaces.ContentTypes[type];
this.app.post(segment, handler);
}
start(): void {
const port = this.app.listen(0).address().port; // 0 = listen on a random port
Utils.logDebug(Constants.msgLocalWebserviceStarted + port);

Просмотреть файл

@ -9,6 +9,10 @@ import { BatchSummary, QueryExecuteParams, QueryExecuteRequest,
QueryExecuteCompleteNotificationResult, QueryExecuteSubsetResult,
QueryExecuteSubsetParams, QueryDisposeParams, QueryExecuteSubsetRequest,
QueryDisposeRequest } from '../models/contracts/queryExecute';
import { ISlickRange } from '../models/interfaces';
const ncp = require('copy-paste');
export interface IResultSet {
columns: string[];
@ -165,4 +169,36 @@ export default class QueryRunner {
});
});
}
public copyResults(selection: ISlickRange[], batchId: number, resultId: number): Promise<void> {
const self = this;
return new Promise<void>((resolve, reject) => {
let copyString = '';
// create a mapping of the ranges to get promises
let tasks = selection.map((range, i) => {
return () => {
return self.getRows(range.fromRow, range.toRow - range.fromRow + 1, batchId, resultId).then((result) => {
// iterate over the rows to paste into the copy string
for (let row of result.resultSubset.rows) {
// iterate over the cells we want from that row
for (let cell = range.fromCell; cell <= range.toCell; cell++) {
copyString += row[cell] + '\t';
}
copyString += '\r\n';
}
});
};
});
let p = tasks[0]();
for (let i = 1; 1 < tasks.length; i++) {
p = p.then(tasks[i]);
}
p.then(() => {
ncp.copy(copyString, () => {
resolve();
});
});
});
}
}

Просмотреть файл

@ -22,6 +22,7 @@ export const outputContentTypeResultsetMeta = 'resultsetsMeta';
export const outputContentTypeColumns = 'columns';
export const outputContentTypeRows = 'rows';
export const outputContentTypeSaveResults = 'saveResults';
export const outputContentTypeCopy = 'copyResults';
export const outputServiceLocalhost = 'http://localhost:';
export const msgContentProviderSqlOutputHtml = 'sqlOutput.ejs';

Просмотреть файл

@ -9,9 +9,17 @@ export enum ContentType {
ResultsetsMeta = 2,
Columns = 3,
Rows = 4,
SaveResults = 5
SaveResults = 5,
Copy = 6
};
export interface ISlickRange {
fromCell: number;
fromRow: number;
toCell: number;
toRow: number;
}
export enum AuthenticationTypes {
Integrated = 1,
SqlLogin = 2,
@ -24,7 +32,8 @@ export const ContentTypes = [
Constants.outputContentTypeResultsetMeta,
Constants.outputContentTypeColumns,
Constants.outputContentTypeRows,
Constants.outputContentTypeSaveResults
Constants.outputContentTypeSaveResults,
Constants.outputContentTypeCopy
];
/**

Просмотреть файл

@ -103,6 +103,17 @@ export class SqlOutputContentProvider implements vscode.TextDocumentContentProvi
res.send();
});
this._service.addPostHandler(Interfaces.ContentType.Copy, function(req, res): void {
let uri = decodeURI(req.query.uri);
let resultId = req.query.resultId;
let batchId = req.query.batchId;
let selection: Interfaces.ISlickRange[] = req.body;
self._queryResultsMap.get(uri).copyResults(selection, batchId, resultId).then(() => {
res.status = 200;
res.send();
});
});
// start express server on localhost and listen on a random port
try {
this._service.start();

Просмотреть файл

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {Component, OnInit, Inject, forwardRef, ViewChild} from '@angular/core';
import {Component, OnInit, Inject, forwardRef, ViewChild, ViewChildren, QueryList} from '@angular/core';
import {IColumnDefinition} from './slickgrid/ModelInterfaces';
import {IObservableCollection} from './slickgrid/BaseLibrary';
import {IGridDataRow} from './slickgrid/SharedControlInterfaces';
@ -48,8 +48,10 @@ export class AppComponent implements OnInit {
private messages: string[] = [];
private selected: SelectedTab;
private windowSize = 50;
private c_key = 67;
public SelectedTab = SelectedTab;
@ViewChild(ContextMenu) contextMenu: ContextMenu;
@ViewChildren(SlickGrid) slickgrids: QueryList<SlickGrid>;
constructor(@Inject(forwardRef(() => DataService)) private dataService: DataService) {}
@ -170,4 +172,14 @@ export class AppComponent implements OnInit {
tabChange(to: SelectedTab): void {
this.selected = to;
}
/**
* Handles keyboard events on angular, currently only needed for copy-paste
*/
onKey(e: any, batchId: number, resultId: number, index: number): void {
if ((e.ctrlKey || e.metaKey) && e.which === this.c_key) {
let selection = this.slickgrids.toArray()[index].getSelectedRanges();
this.dataService.copyResults(selection, batchId, resultId);
}
}
}

Просмотреть файл

@ -1,12 +1,13 @@
<div class="fullsize box">
<tabs class="boxRow content box" [selected]="selected" (tabChange)="tabChange($event)">
<tab [id]="SelectedTab.Results" tabTitle="Results" [show]="dataSets.length > 0" class="boxRow content box">
<slick-grid *ngFor="let dataSet of dataSets"
<slick-grid *ngFor="let dataSet of dataSets; let i = index"
[columnDefinitions]="dataSet.columnDefinitions"
[dataRows]="dataSet.dataRows"
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId)"
showDataTypeIcon="false"
showHeader="true"
(keydown)="onKey($event, dataSet.batchId, dataSet.resultId, i)"
class="boxRow content box slickgrid">
</slick-grid>
<context-menu (clickEvent)="handleContextClick($event)"></context-menu>

Просмотреть файл

@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import {Injectable, Inject, forwardRef} from '@angular/core';
import {Http} from '@angular/http';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Rx';
import { IDbColumn, ResultSetSubset, IGridBatchMetaData } from './../interfaces';
import {ISlickRange} from './SlickGrid/SelectionModel';
/**
* Service which performs the http requests to get the data resultsets from the server.
@ -213,4 +214,17 @@ export class DataService {
self.http.get('/saveResults?'
+ '&uri=' + self.uri + '&format=' + format + '&batchIndex=' + batchIndex + '&resultSetNo=' + resultSetNumber).subscribe();
}
/**
* Sends a copy request
* @param selection The selection range to copy
* @param batchId The batch id of the result to copy from
* @param resultId The result id of the result to copy from
*/
copyResults(selection: ISlickRange[], batchId: number, resultId: number): void {
const self = this;
let headers = new Headers();
let url = '/copyResults?' + '&uri=' + self.uri + '&batchId=' + batchId + '&resultId=' + resultId;
self.http.post(url, selection, { headers: headers }).subscribe();
}
}

Просмотреть файл

@ -9,7 +9,7 @@
});
function DragRowSelectionModel(options) {
const left_arrow = 37, up_arrow = 38, right_arrow = 39, down_arrow = 40, a_key = 65;
const left_arrow = 37, up_arrow = 38, right_arrow = 39, down_arrow = 40, a_key = 65, c_key = 67;
var _grid;
var _dragStart;
var _dragRow;
@ -106,7 +106,7 @@
_ranges = [new Slick.Range(0, 0, _grid.getDataLength() - 1, _grid.getColumns().length)]
setSelectedRanges(_ranges);
e.preventDefault();
- e.stopPropagation();
e.stopPropagation();
}
// do we have a context to navigate on
else if (activeCell) {
@ -127,7 +127,7 @@
_grid.setActiveCell(activeCell.row, activeCell.cell - 1);
setSelectedRanges(_ranges);
e.preventDefault();
- e.stopPropagation();
e.stopPropagation();
// up arrow
} else if (e.which == up_arrow && activeCell.row > 0) {
if (e.shiftKey) {
@ -143,7 +143,7 @@
_grid.setActiveCell(activeCell.row - 1, activeCell.cell);
setSelectedRanges(_ranges);
e.preventDefault();
- e.stopPropagation();
e.stopPropagation();
// right arrow
} else if (e.which == right_arrow && activeCell.cell < _grid.getColumns().length) {
if (e.shiftKey) {
@ -159,7 +159,7 @@
_grid.setActiveCell(activeCell.row, activeCell.cell + 1);
setSelectedRanges(_ranges);
e.preventDefault();
- e.stopPropagation();
e.stopPropagation();
// down arrow
} else if (e.which == down_arrow && activeCell.row < _grid.getDataLength()) {
if (e.shiftKey) {
@ -175,7 +175,7 @@
_grid.setActiveCell(activeCell.row + 1, activeCell.cell);
setSelectedRanges(_ranges);
e.preventDefault();
- e.stopPropagation();
e.stopPropagation();
}
}
}

Просмотреть файл

@ -32,6 +32,10 @@ export class SelectionModel implements ISlickSelectionModel {
this.updateSelectedRanges(ranges);
}
getSelectedRanges(): ISlickRange[] {
return this._rowSelectionModel.getSelectedRanges();
}
changeSelectedRanges(selections: ISelectionRange[]): void {
let slickRange = (selections || []).map(s =>
this._slickRangeFactory(s.startRow, s.startColumn, s.endRow - 1, s.endColumn - 1)
@ -173,6 +177,7 @@ export interface ISlickSelectionModel {
init(grid: any): void;
destroy(): void;
setSelectedRanges(ranges: ISlickRange[]): void;
getSelectedRanges(): ISlickRange[];
}
export interface ISlickEventHandler {

Просмотреть файл

@ -13,6 +13,7 @@ import {IGridDataRow} from './SharedControlInterfaces';
import {IColumnDefinition} from './ModelInterfaces';
import {LocalizationService} from './LocalizationService';
import {GridSyncService} from './GridSyncService';
import {ISlickRange} from './SelectionModel';
enum FieldType {
String = 0,
@ -319,6 +320,10 @@ export class SlickGrid implements OnChanges, OnInit, OnDestroy, DoCheck {
}
}
public getSelectedRanges(): ISlickRange[] {
return this._gridSyncService.selectionModel.getSelectedRanges();
}
/* tslint:disable:member-ordering */
private getColumnEditor = (column: any): any => {
let columnId = column.id;

Просмотреть файл

@ -7,6 +7,9 @@ import SqlToolsServerClient from './../src/languageservice/serviceclient';
import { QueryExecuteParams } from './../src/models/contracts/queryExecute';
import VscodeWrapper from './../src/controllers/vscodeWrapper';
import StatusView from './../src/views/statusView';
import { ISlickRange } from './../src/models/interfaces';
const ncp = require('copy-paste');
suite('Query Runner tests', () => {
@ -169,4 +172,47 @@ suite('Query Runner tests', () => {
testVscodeWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
});
});
test('Correctly copy pastes a selection', () => {
const TAB = '\t';
const CLRF = '\r\n';
const finalString = '1' + TAB + '2' + TAB + CLRF +
'3' + TAB + '4' + TAB + CLRF +
'5' + TAB + '6' + TAB + CLRF +
'7' + TAB + '8' + TAB + CLRF +
'9' + TAB + '10' + TAB + CLRF;
let testuri = 'test';
let testresult = {
message: '',
resultSubset: {
rowCount: 5,
rows: [
['1', '2'],
['3', '4'],
['5', '6'],
['7', '8'],
['9', '10']
]
}
};
let testRange: ISlickRange[] = [{fromCell: 0, fromRow: 0, toCell: 1, toRow: 4}];
testSqlToolsServerClient.setup(x => x.sendRequest(TypeMoq.It.isAny(),
TypeMoq.It.isAny())).callback(() => {
// testing
}).returns(() => { return Promise.resolve(testresult); });
testStatusView.setup(x => x.executingQuery(TypeMoq.It.isAnyString()));
let queryRunner = new QueryRunner(
undefined,
testStatusView.object,
testSqlOutputContentProvider.object,
testSqlToolsServerClient.object,
testQueryNotificationHandler.object,
testVscodeWrapper.object
);
queryRunner.uri = testuri;
return queryRunner.copyResults(testRange, 0, 0).then(() => {
let pasteContents = ncp.paste();
assert.equal(pasteContents, finalString);
});
});
});