James Stout
6 years ago
commit
c2251d1a99
25 changed files with 5169 additions and 0 deletions
@ -0,0 +1,37 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
|
||||||
|
# Runtime data |
||||||
|
pids |
||||||
|
*.pid |
||||||
|
*.seed |
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover |
||||||
|
lib-cov |
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul |
||||||
|
coverage |
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) |
||||||
|
.grunt |
||||||
|
|
||||||
|
# node-waf configuration |
||||||
|
.lock-wscript |
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html) |
||||||
|
build/Release |
||||||
|
|
||||||
|
# Dependency directory |
||||||
|
node_modules |
||||||
|
|
||||||
|
# Optional npm cache directory |
||||||
|
.npm |
||||||
|
|
||||||
|
# Optional REPL history |
||||||
|
.node_repl_history |
||||||
|
|
||||||
|
out |
||||||
|
|
||||||
|
.vscode-test |
@ -0,0 +1,25 @@ |
|||||||
|
sudo: false |
||||||
|
|
||||||
|
language: node_js |
||||||
|
|
||||||
|
node_js: |
||||||
|
- "6" |
||||||
|
|
||||||
|
os: |
||||||
|
- osx |
||||||
|
- linux |
||||||
|
|
||||||
|
before_install: |
||||||
|
- if [ $TRAVIS_OS_NAME == "linux" ]; then |
||||||
|
export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; |
||||||
|
sudo apt-get --assume-yes install libsecret-1-0; |
||||||
|
sh -e /etc/init.d/xvfb start; |
||||||
|
sleep 3; |
||||||
|
fi |
||||||
|
|
||||||
|
install: |
||||||
|
- npm install |
||||||
|
- npm run vscode:prepublish |
||||||
|
|
||||||
|
script: |
||||||
|
- npm test --silent |
@ -0,0 +1,36 @@ |
|||||||
|
// A launch configuration that compiles the extension and then opens it inside a new window |
||||||
|
// Use IntelliSense to learn about possible attributes. |
||||||
|
// Hover to view descriptions of existing attributes. |
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 |
||||||
|
{ |
||||||
|
"version": "0.2.0", |
||||||
|
"configurations": [ |
||||||
|
{ |
||||||
|
"name": "Launch Extension", |
||||||
|
"type": "extensionHost", |
||||||
|
"request": "launch", |
||||||
|
"runtimeExecutable": "${execPath}", |
||||||
|
"args": [ |
||||||
|
"--extensionDevelopmentPath=${workspaceFolder}" |
||||||
|
], |
||||||
|
"outFiles": [ |
||||||
|
"${workspaceFolder}/out/src/**/*.js" |
||||||
|
], |
||||||
|
"preLaunchTask": "npm: watch" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"name": "Launch Tests", |
||||||
|
"type": "extensionHost", |
||||||
|
"request": "launch", |
||||||
|
"runtimeExecutable": "${execPath}", |
||||||
|
"args": [ |
||||||
|
"--extensionDevelopmentPath=${workspaceFolder}", |
||||||
|
"--extensionTestsPath=${workspaceFolder}/out/test" |
||||||
|
], |
||||||
|
"outFiles": [ |
||||||
|
"${workspaceFolder}/out/test/**/*.js" |
||||||
|
], |
||||||
|
"preLaunchTask": "npm: watch" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
// Place your settings in this file to overwrite default and user settings. |
||||||
|
{ |
||||||
|
"files.exclude": { |
||||||
|
"out": false // set this to true to hide the "out" folder with the compiled JS files |
||||||
|
}, |
||||||
|
"search.exclude": { |
||||||
|
"out": true // set this to false to include "out" folder in search results |
||||||
|
}, |
||||||
|
"spellright.language": "en", |
||||||
|
"spellright.documentTypes": [ |
||||||
|
"latex", |
||||||
|
"plaintext" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,20 @@ |
|||||||
|
// See https://go.microsoft.com/fwlink/?LinkId=733558 |
||||||
|
// for the documentation about the tasks.json format |
||||||
|
{ |
||||||
|
"version": "2.0.0", |
||||||
|
"tasks": [ |
||||||
|
{ |
||||||
|
"type": "npm", |
||||||
|
"script": "watch", |
||||||
|
"problemMatcher": "$tsc-watch", |
||||||
|
"isBackground": true, |
||||||
|
"presentation": { |
||||||
|
"reveal": "never" |
||||||
|
}, |
||||||
|
"group": { |
||||||
|
"kind": "build", |
||||||
|
"isDefault": true |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,13 @@ |
|||||||
|
.vscode/** |
||||||
|
.vscode-test/** |
||||||
|
typings/** |
||||||
|
out/test/** |
||||||
|
test/** |
||||||
|
src/** |
||||||
|
**/*.map |
||||||
|
.gitignore |
||||||
|
tsconfig.json |
||||||
|
vsc-extension-quickstart.md |
||||||
|
tsd.json |
||||||
|
*.vsix |
||||||
|
releases/** |
@ -0,0 +1,7 @@ |
|||||||
|
- VSCode Version: |
||||||
|
- OS Version: |
||||||
|
|
||||||
|
Steps to Reproduce: |
||||||
|
|
||||||
|
1. |
||||||
|
2. |
@ -0,0 +1,44 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2016 Steffen Leistner |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2018 James Stout |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
@ -0,0 +1,22 @@ |
|||||||
|
# Jekyll Utils - Visual Studio Code Extension |
||||||
|
|
||||||
|
A convenient way of creating new Jekyll post files. |
||||||
|
|
||||||
|
> Inspired by [Sidebar Enhancements](https://github.com/titoBouzout/SideBarEnhancements) for Sublime. |
||||||
|
> and [vscode-fileutils](https://github.com/sleistner/vscode-fileutils) |
||||||
|
|
||||||
|
## Commands |
||||||
|
|
||||||
|
```json |
||||||
|
[ |
||||||
|
{ |
||||||
|
"command": "fileutils.newFileAtRoot", |
||||||
|
"category": "File", |
||||||
|
"title": "New Jekyll Post File Relative to Current View" |
||||||
|
} |
||||||
|
] |
||||||
|
``` |
||||||
|
|
||||||
|
# License |
||||||
|
|
||||||
|
MIT |
After Width: | Height: | Size: 811 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,95 @@ |
|||||||
|
{ |
||||||
|
"name": "jekyll-post-file-generator", |
||||||
|
"displayName": "Jekyll Utils", |
||||||
|
"description": "A convenient way of creating new Jekyll post files", |
||||||
|
"version": "0.0.1", |
||||||
|
"license": "MIT", |
||||||
|
"publisher": "stoutyhk", |
||||||
|
"engines": { |
||||||
|
"vscode": "^1.20.0" |
||||||
|
}, |
||||||
|
"categories": [ |
||||||
|
"Other" |
||||||
|
], |
||||||
|
"icon": "images/icon.png", |
||||||
|
"galleryBanner": { |
||||||
|
"color": "#5c2d91", |
||||||
|
"theme": "dark" |
||||||
|
}, |
||||||
|
"bugs": { |
||||||
|
"url": "https://bitbucket.org/stouty/vscode-jekyll-post-file-generator/admin/issues" |
||||||
|
}, |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "https://stouty@bitbucket.org/stouty/vscode-jekyll-post-file-generator.git" |
||||||
|
}, |
||||||
|
"homepage": "https://bitbucket.org/stouty/vscode-jekyll-post-file-generator/src", |
||||||
|
"activationEvents": [ |
||||||
|
"onCommand:fileutils.newJekyllFile" |
||||||
|
], |
||||||
|
"main": "./out/src/extension", |
||||||
|
"contributes": { |
||||||
|
"commands": [ |
||||||
|
{ |
||||||
|
"command": "fileutils.newJekyllFile", |
||||||
|
"category": "File", |
||||||
|
"title": "New Jekyll Post File Relative to Current View" |
||||||
|
} |
||||||
|
], |
||||||
|
"configuration": { |
||||||
|
"type": "object", |
||||||
|
"title": "Fileutils configuration", |
||||||
|
"properties": { |
||||||
|
"fileutils.delete.useTrash": { |
||||||
|
"type": "boolean", |
||||||
|
"default": false, |
||||||
|
"description": "Move file to the recycle bin instead of deleting it permanently." |
||||||
|
}, |
||||||
|
"fileutils.delete.confirm": { |
||||||
|
"type": "boolean", |
||||||
|
"default": true, |
||||||
|
"description": "Controls if it should ask for confirmation when deleting a file." |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"vscode:prepublish": "./node_modules/.bin/tsc -p ./", |
||||||
|
"compile": "tsc -p ./", |
||||||
|
"watch": "tsc -watch -p ./", |
||||||
|
"postinstall": "node ./node_modules/vscode/bin/install", |
||||||
|
"test": "npm run compile && node ./node_modules/vscode/bin/test", |
||||||
|
"lint": "tslint -e './node_modules/**/*.ts' -e './typings/**/*.ts' './**/*.ts'", |
||||||
|
"validate": "nsp check" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/bluebird-retry": "0.11.0", |
||||||
|
"@types/chai": "^4.1.2", |
||||||
|
"@types/mocha": "^2.2.48", |
||||||
|
"@types/node": "^9.4.6", |
||||||
|
"@types/sinon": "^4.1.3", |
||||||
|
"@types/sinon-chai": "^2.7.29", |
||||||
|
"bluebird-retry": "^0.11.0", |
||||||
|
"chai": "^4.1.2", |
||||||
|
"hoek": "^5.0.3", |
||||||
|
"nsp": "^3.2.1", |
||||||
|
"precommit-hook-eslint": "^3.0.0", |
||||||
|
"sinon": "^4.4.2", |
||||||
|
"sinon-chai": "^2.14.0", |
||||||
|
"tslint": "^5.9.1", |
||||||
|
"typescript": "^2.7.2", |
||||||
|
"vscode": "^1.1.10" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"bluebird": "^3.5.1", |
||||||
|
"fs-extra": "^5.0.0", |
||||||
|
"trash": "^4.2.1", |
||||||
|
"@types/fs-extra": "^5.0.0", |
||||||
|
"@types/bluebird": "^3.5.20" |
||||||
|
}, |
||||||
|
"pre-commit": [ |
||||||
|
"lint", |
||||||
|
"validate", |
||||||
|
"test" |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,188 @@ |
|||||||
|
import * as fs from 'fs'; |
||||||
|
import * as path from 'path'; |
||||||
|
import { |
||||||
|
commands, |
||||||
|
Position, |
||||||
|
TextDocument, |
||||||
|
TextEditor, |
||||||
|
Uri, |
||||||
|
ViewColumn, |
||||||
|
window, |
||||||
|
workspace, |
||||||
|
WorkspaceConfiguration |
||||||
|
} from 'vscode'; |
||||||
|
import { FileItem } from './item'; |
||||||
|
|
||||||
|
export interface IMoveFileDialogOptions { |
||||||
|
prompt: string; |
||||||
|
showFullPath?: boolean; |
||||||
|
uri?: Uri; |
||||||
|
} |
||||||
|
|
||||||
|
export interface INewFileDialogOptions { |
||||||
|
prompt: string; |
||||||
|
relativeToRoot?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export interface INewJekyllDialogOptions { |
||||||
|
value: string; |
||||||
|
valueSelection: [number, number]; |
||||||
|
} |
||||||
|
|
||||||
|
export interface ICreateOptions { |
||||||
|
fileItem: FileItem; |
||||||
|
isDir?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export class FileController { |
||||||
|
|
||||||
|
public showNewJekyllFileDialog(options: INewJekyllDialogOptions): Promise<FileItem> { |
||||||
|
|
||||||
|
const { value, valueSelection } = options; |
||||||
|
let sourcePath = workspace.rootPath; |
||||||
|
if (this.sourcePath) { |
||||||
|
sourcePath = path.dirname(this.sourcePath); |
||||||
|
} |
||||||
|
|
||||||
|
if (!sourcePath) { |
||||||
|
return Promise.reject(null); |
||||||
|
} |
||||||
|
|
||||||
|
return Promise.resolve(window.showInputBox({ value, valueSelection })) |
||||||
|
.then((targetPath) => { |
||||||
|
|
||||||
|
if (targetPath) { |
||||||
|
targetPath = path.resolve(sourcePath, targetPath); |
||||||
|
return new FileItem(sourcePath, targetPath); |
||||||
|
} |
||||||
|
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public create(options: ICreateOptions): Promise<FileItem> { |
||||||
|
|
||||||
|
const { fileItem, isDir = false } = options; |
||||||
|
|
||||||
|
return this.ensureWritableFile(fileItem) |
||||||
|
.then(() => fileItem.create(isDir)) |
||||||
|
.catch(() => Promise.reject(`Error creating file '${fileItem.targetPath}'.`)); |
||||||
|
} |
||||||
|
|
||||||
|
public openFileInEditor(fileItem: FileItem): Promise<TextEditor> { |
||||||
|
|
||||||
|
const isDir = fs.statSync(fileItem.path).isDirectory(); |
||||||
|
|
||||||
|
if (isDir) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
return Promise.resolve(workspace.openTextDocument(fileItem.path)) |
||||||
|
.then((textDocument) => { |
||||||
|
return textDocument |
||||||
|
? Promise.resolve(textDocument) |
||||||
|
: Promise.reject(new Error('Could not open file!')); |
||||||
|
}) |
||||||
|
.then((textDocument) => window.showTextDocument(textDocument, ViewColumn.Active)) |
||||||
|
.then((editor) => { |
||||||
|
|
||||||
|
const date: Date = new Date(); |
||||||
|
let monthDay: string = date.getDate().toString(); // Get the day as a number (1-31)
|
||||||
|
let month: number = date.getMonth(); // Get the month as a number (0-11) 2018-05-05 12:29:01 +0800
|
||||||
|
let hour: string = date.getHours().toString(); // Get the hour (0-23)
|
||||||
|
let mins: string = date.getMinutes().toString(); // Get the minute (0-59)
|
||||||
|
let sec: string = date.getSeconds().toString(); // Get the second (0-59)
|
||||||
|
const tzOffset: number = date.getTimezoneOffset(); |
||||||
|
const delim: string = '-'; |
||||||
|
const delimTime: string = ':'; |
||||||
|
const delimSpace: string = ' '; |
||||||
|
|
||||||
|
const tzOffsetStr: string = ((tzOffset < 0 ? '+' : '-') |
||||||
|
+ this.pad(parseInt(Math.abs(tzOffset / 60).toString(), 10), 2) |
||||||
|
+ this.pad(Math.abs(tzOffset % 60), 2)); |
||||||
|
|
||||||
|
// console.log(tzOffsetStr);
|
||||||
|
|
||||||
|
const fullYear: string = date.getFullYear().toString(); |
||||||
|
|
||||||
|
if (monthDay.length === 1) { |
||||||
|
monthDay = '0'.concat(monthDay); |
||||||
|
} |
||||||
|
if (hour.length === 1) { |
||||||
|
hour = '0'.concat(hour); |
||||||
|
} |
||||||
|
if (mins.length === 1) { |
||||||
|
mins = '0'.concat(mins); |
||||||
|
} |
||||||
|
if (sec.length === 1) { |
||||||
|
sec = '0'.concat(sec); |
||||||
|
} |
||||||
|
|
||||||
|
month++; |
||||||
|
let monthStr: string = month.toString(); |
||||||
|
|
||||||
|
if (monthStr.length === 1) { |
||||||
|
monthStr = '0'.concat(monthStr); |
||||||
|
} |
||||||
|
// 2018-05-05 12:29:01 +0800
|
||||||
|
const outputStr: string = fullYear.concat(delim, monthStr, delim, monthDay, delimSpace, |
||||||
|
hour, delimTime, mins, delimTime, sec, delimSpace, tzOffsetStr); |
||||||
|
|
||||||
|
const frontMatter: string = '---\nlayout: single\ntitle: \ndate: '. |
||||||
|
concat(outputStr, '\ncategories: \ntags: \nexcerpt: \n---\n'); |
||||||
|
const p: Position = new Position(0, 0); |
||||||
|
editor.edit((editBuilder) => { |
||||||
|
editBuilder.insert(p, frontMatter); |
||||||
|
}); |
||||||
|
|
||||||
|
return editor |
||||||
|
? Promise.resolve(editor) |
||||||
|
: Promise.reject(new Error('Could not show document!')); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
public closeCurrentFileEditor(): Thenable<any> { |
||||||
|
return commands.executeCommand('workbench.action.closeActiveEditor'); |
||||||
|
} |
||||||
|
|
||||||
|
private get sourcePath(): string { |
||||||
|
|
||||||
|
const activeEditor: TextEditor = window.activeTextEditor; |
||||||
|
const document: TextDocument = activeEditor && activeEditor.document; |
||||||
|
|
||||||
|
return document && document.fileName; |
||||||
|
} |
||||||
|
|
||||||
|
private ensureWritableFile(fileItem: FileItem): Promise<FileItem> { |
||||||
|
|
||||||
|
if (!fileItem.exists) { |
||||||
|
return Promise.resolve(fileItem); |
||||||
|
} |
||||||
|
|
||||||
|
const message = `File '${fileItem.targetPath}' already exists.`; |
||||||
|
const action = 'Overwrite'; |
||||||
|
|
||||||
|
return Promise.resolve(window.showInformationMessage(message, { modal: true }, action)) |
||||||
|
.then((overwrite) => overwrite ? Promise.resolve(fileItem) : Promise.reject(null)); |
||||||
|
} |
||||||
|
|
||||||
|
private pad(num: number, length: number): string { |
||||||
|
let str = '' + num; |
||||||
|
while (str.length < length) { |
||||||
|
str = '0' + str; |
||||||
|
} |
||||||
|
return str; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
private get configuration(): WorkspaceConfiguration { |
||||||
|
return workspace.getConfiguration('fileutils'); |
||||||
|
} |
||||||
|
|
||||||
|
private get useTrash(): boolean { |
||||||
|
return this.configuration.get('delete.useTrash'); |
||||||
|
} |
||||||
|
|
||||||
|
private get confirmDelete(): boolean { |
||||||
|
return this.configuration.get('delete.confirm'); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
import * as fs from 'fs-extra'; |
||||||
|
import * as path from 'path'; |
||||||
|
import { workspace } from 'vscode'; |
||||||
|
|
||||||
|
// tslint:disable-next-line
|
||||||
|
const trash = require('trash'); |
||||||
|
|
||||||
|
export class FileItem { |
||||||
|
|
||||||
|
private SourcePath: string; |
||||||
|
private TargetPath: string; |
||||||
|
|
||||||
|
constructor(sourcePath: string, targetPath?: string) { |
||||||
|
this.SourcePath = sourcePath; |
||||||
|
this.TargetPath = targetPath; |
||||||
|
} |
||||||
|
|
||||||
|
get path(): string { |
||||||
|
return this.SourcePath; |
||||||
|
} |
||||||
|
|
||||||
|
get targetPath(): string { |
||||||
|
return this.TargetPath; |
||||||
|
} |
||||||
|
|
||||||
|
get exists(): boolean { |
||||||
|
return fs.existsSync(this.targetPath); |
||||||
|
} |
||||||
|
|
||||||
|
public create(isDir: boolean = false): Promise<FileItem> { |
||||||
|
|
||||||
|
const fn = isDir ? fs.ensureDir : fs.createFile; |
||||||
|
|
||||||
|
return Promise.resolve(fs.remove(this.targetPath)) |
||||||
|
.then(() => fn(this.targetPath)) |
||||||
|
.then(() => new FileItem(this.targetPath)); |
||||||
|
} |
||||||
|
|
||||||
|
private ensureDir(): Promise<any> { |
||||||
|
return Promise.resolve(fs.ensureDir(path.dirname(this.targetPath))); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,55 @@ |
|||||||
|
import { |
||||||
|
Uri, |
||||||
|
window |
||||||
|
} from 'vscode'; |
||||||
|
import { FileController } from './api/controller'; |
||||||
|
|
||||||
|
function handleError(err) { |
||||||
|
if (err) { |
||||||
|
window.showErrorMessage(err); |
||||||
|
} |
||||||
|
return err; |
||||||
|
} |
||||||
|
|
||||||
|
export interface INewFileOptions { |
||||||
|
relativeToRoot?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export interface INewFolderOptions { |
||||||
|
relativeToRoot?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const controller = new FileController(); |
||||||
|
|
||||||
|
export function newJekyllFile(options?: INewFileOptions) { |
||||||
|
|
||||||
|
const { relativeToRoot = false } = options || {}; |
||||||
|
|
||||||
|
const delim: string = '-'; |
||||||
|
const ending: string = 'xxxxx.md'; |
||||||
|
const date: Date = new Date(); |
||||||
|
let monthDay: string = date.getDate().toString(); // Get the day as a number (1-31)
|
||||||
|
let month: number = date.getMonth(); // Get the month as a number (0-11) 2018-05-05-xxxxx.md
|
||||||
|
const fullYear: string = date.getFullYear().toString(); |
||||||
|
|
||||||
|
if (monthDay.length === 1) { |
||||||
|
monthDay = '0'.concat(monthDay); |
||||||
|
} |
||||||
|
|
||||||
|
month++; |
||||||
|
let monthStr: string = month.toString(); |
||||||
|
|
||||||
|
if (monthStr.length === 1) { |
||||||
|
monthStr = '0'.concat(monthStr); |
||||||
|
} |
||||||
|
|
||||||
|
const outputStr: string = fullYear.concat(delim, monthStr, delim, monthDay, delim, ending); |
||||||
|
|
||||||
|
// console.log(outputStr);
|
||||||
|
// console.log(date);
|
||||||
|
|
||||||
|
return controller.showNewJekyllFileDialog({ value: outputStr, valueSelection: [11, 16] }) |
||||||
|
.then((fileItem) => controller.create({ fileItem })) |
||||||
|
.then((fileItem) => controller.openFileInEditor(fileItem)) |
||||||
|
.catch(handleError); |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
import { |
||||||
|
commands, |
||||||
|
ExtensionContext |
||||||
|
} from 'vscode'; |
||||||
|
import * as api from './commands'; |
||||||
|
|
||||||
|
function register(context: ExtensionContext, handler: any) { |
||||||
|
|
||||||
|
const command = `fileutils.${handler.name}`; |
||||||
|
const disposable = commands.registerCommand(command, handler); |
||||||
|
|
||||||
|
context.subscriptions.push(disposable); |
||||||
|
} |
||||||
|
|
||||||
|
export function activate(context: ExtensionContext) { |
||||||
|
|
||||||
|
register(context, api.newJekyllFile); |
||||||
|
|
||||||
|
} |
@ -0,0 +1,285 @@ |
|||||||
|
import * as retry from 'bluebird-retry'; |
||||||
|
import { |
||||||
|
expect, |
||||||
|
use as chaiUse |
||||||
|
} from 'chai'; |
||||||
|
import * as fs from 'fs-extra'; |
||||||
|
import * as os from 'os'; |
||||||
|
import * as path from 'path'; |
||||||
|
import * as sinon from 'sinon'; |
||||||
|
import * as sinonChai from 'sinon-chai'; |
||||||
|
|
||||||
|
import { |
||||||
|
commands, |
||||||
|
TextEditor, |
||||||
|
Uri, |
||||||
|
window, |
||||||
|
workspace |
||||||
|
} from 'vscode'; |
||||||
|
|
||||||
|
import { |
||||||
|
controller, |
||||||
|
newJekyllFile |
||||||
|
} from '../../src/extension/commands'; |
||||||
|
|
||||||
|
chaiUse(sinonChai); |
||||||
|
|
||||||
|
const rootDir = path.resolve(__dirname, '..', '..', '..'); |
||||||
|
const tmpDir = path.resolve(os.tmpdir(), 'vscode-fileutils-test--new-file'); |
||||||
|
|
||||||
|
const fixtureFile1 = path.resolve(rootDir, 'test', 'fixtures', 'file-1.rb'); |
||||||
|
const fixtureFile2 = path.resolve(rootDir, 'test', 'fixtures', 'file-2.rb'); |
||||||
|
|
||||||
|
const editorFile1 = path.resolve(tmpDir, 'nested-dir-1', 'nested-dir-2', 'file-1.rb'); |
||||||
|
const editorFile2 = path.resolve(tmpDir, 'file-2.rb'); |
||||||
|
|
||||||
|
const targetFile = path.resolve(`${editorFile1}.tmp`); |
||||||
|
|
||||||
|
describe('newJekyllFile', () => { |
||||||
|
|
||||||
|
beforeEach(() => Promise.all([ |
||||||
|
fs.remove(tmpDir), |
||||||
|
fs.copy(fixtureFile1, editorFile1), |
||||||
|
fs.copy(fixtureFile2, editorFile2) |
||||||
|
])); |
||||||
|
|
||||||
|
afterEach(() => fs.remove(tmpDir)); |
||||||
|
|
||||||
|
describe('with open text document', () => { |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
|
||||||
|
const openDocument = () => { |
||||||
|
const uri = Uri.file(editorFile1); |
||||||
|
return workspace.openTextDocument(uri) |
||||||
|
.then((textDocument) => window.showTextDocument(textDocument)); |
||||||
|
}; |
||||||
|
|
||||||
|
const stubShowInputBox = () => { |
||||||
|
const fileName = path.basename(targetFile); |
||||||
|
sinon.stub(window, 'showInputBox').returns(Promise.resolve(fileName)); |
||||||
|
return Promise.resolve(); |
||||||
|
}; |
||||||
|
|
||||||
|
return Promise.all([ |
||||||
|
retry(() => openDocument(), { max_tries: 4, interval: 500 }), |
||||||
|
stubShowInputBox() |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
|
||||||
|
const closeAllEditors = () => { |
||||||
|
return commands.executeCommand('workbench.action.closeAllEditors'); |
||||||
|
}; |
||||||
|
|
||||||
|
const restoreShowInputBox = () => { |
||||||
|
const stub: any = window.showInputBox; |
||||||
|
return Promise.resolve(stub.restore()); |
||||||
|
}; |
||||||
|
|
||||||
|
return Promise.all([ |
||||||
|
closeAllEditors(), |
||||||
|
restoreShowInputBox() |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('prompts for file destination', () => { |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
|
||||||
|
const delim: string = '-'; |
||||||
|
const ending: string = 'xxxxx.md'; |
||||||
|
const date: Date = new Date(); |
||||||
|
let monthDay: string = date.getDate().toString(); // Get the day as a number (1-31)
|
||||||
|
let month: number = date.getMonth(); // Get the month as a number (0-11) 2018-05-05-xxxxx.md
|
||||||
|
const fullYear: string = date.getFullYear().toString(); |
||||||
|
|
||||||
|
if (monthDay.length === 1) { |
||||||
|
monthDay = '0'.concat(monthDay); |
||||||
|
} |
||||||
|
|
||||||
|
month++; |
||||||
|
let monthStr: string = month.toString(); |
||||||
|
|
||||||
|
if (monthStr.length === 1) { |
||||||
|
monthStr = '0'.concat(monthStr); |
||||||
|
} |
||||||
|
|
||||||
|
const outputStr: string = fullYear.concat(delim, monthStr, delim, monthDay, delim, ending); |
||||||
|
|
||||||
|
const prompt = { value: outputStr, valueSelection: [11, 16] }; |
||||||
|
expect(window.showInputBox).to.have.been.calledWithExactly(prompt); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
it('create file at destination', () => { |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
const message = `${targetFile} does not exist`; |
||||||
|
// tslint:disable-next-line:no-unused-expression
|
||||||
|
expect(fs.existsSync(targetFile), message).to.be.true; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it('opens new file as active editor', () => { |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
const activeEditor: TextEditor = window.activeTextEditor; |
||||||
|
expect(activeEditor.document.fileName).to.equal(targetFile); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
// FIXME: controller#showNewFileDialog workspace root is undefined for some reason
|
||||||
|
// describe('relative to project root', () => {
|
||||||
|
|
||||||
|
// it('create file at destination', () => {
|
||||||
|
|
||||||
|
// return newFile({ relativeToRoot: true }).then(() => {
|
||||||
|
// const message = `${targetFile} does not exist`;
|
||||||
|
// expect(fs.existsSync(targetFile), message).to.be.true;
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// });
|
||||||
|
|
||||||
|
describe('when target destination exists', () => { |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
|
||||||
|
const createTargetFile = () => { |
||||||
|
return fs.copy(editorFile2, targetFile); |
||||||
|
}; |
||||||
|
|
||||||
|
const stubShowInformationMessage = () => { |
||||||
|
sinon.stub(window, 'showInformationMessage').returns(Promise.resolve(true)); |
||||||
|
return Promise.resolve(); |
||||||
|
}; |
||||||
|
|
||||||
|
return Promise.all([ |
||||||
|
createTargetFile(), |
||||||
|
stubShowInformationMessage() |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
const stub: any = window.showInformationMessage; |
||||||
|
return Promise.resolve(stub.restore()); |
||||||
|
}); |
||||||
|
|
||||||
|
it('asks to overwrite destination file', () => { |
||||||
|
|
||||||
|
const message = `File '${targetFile}' already exists.`; |
||||||
|
const action = 'Overwrite'; |
||||||
|
const options = { modal: true }; |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
expect(window.showInformationMessage).to.have.been.calledWith(message, options, action); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('responding with yes', () => { |
||||||
|
|
||||||
|
it('overwrites the existig file', () => { |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
const fileContent = fs.readFileSync(targetFile).toString(); |
||||||
|
expect(fileContent).to.equal(''); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
describe('responding with no', () => { |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
const stub: any = window.showInformationMessage; |
||||||
|
stub.returns(Promise.resolve(false)); |
||||||
|
return Promise.resolve(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('leaves existing file untouched', () => { |
||||||
|
|
||||||
|
return newJekyllFile().then(() => { |
||||||
|
const fileContent = fs.readFileSync(targetFile).toString(); |
||||||
|
expect(fileContent).to.equal('class FileTwo; end'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
describe('with no open text document', () => { |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
|
||||||
|
const closeAllEditors = () => { |
||||||
|
return commands.executeCommand('workbench.action.closeAllEditors'); |
||||||
|
}; |
||||||
|
|
||||||
|
const stubShowInputBox = () => { |
||||||
|
sinon.stub(window, 'showInputBox'); |
||||||
|
return Promise.resolve(); |
||||||
|
}; |
||||||
|
|
||||||
|
return Promise.all([ |
||||||
|
closeAllEditors(), |
||||||
|
stubShowInputBox() |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
const stub: any = window.showInputBox; |
||||||
|
return Promise.resolve(stub.restore()); |
||||||
|
}); |
||||||
|
|
||||||
|
it('ignores the command call', () => { |
||||||
|
|
||||||
|
return newJekyllFile().catch(() => { |
||||||
|
// tslint:disable-next-line:no-unused-expression
|
||||||
|
expect(window.showInputBox).to.have.not.been.called; |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
describe('error handling', () => { |
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
sinon.stub(controller, 'showNewJekyllFileDialog').returns(Promise.reject('must fail')); |
||||||
|
sinon.stub(window, 'showErrorMessage'); |
||||||
|
return Promise.resolve(); |
||||||
|
}); |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
|
||||||
|
const restoreShowNewFileDialog = () => { |
||||||
|
const stub: any = controller.showNewJekyllFileDialog; |
||||||
|
return Promise.resolve(stub.restore()); |
||||||
|
}; |
||||||
|
|
||||||
|
const restoreShowErrorMessage = () => { |
||||||
|
const stub: any = window.showErrorMessage; |
||||||
|
return Promise.resolve(stub.restore()); |
||||||
|
}; |
||||||
|
|
||||||
|
return Promise.all([ |
||||||
|
restoreShowNewFileDialog(), |
||||||
|
restoreShowErrorMessage() |
||||||
|
]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('shows an error message', () => { |
||||||
|
|
||||||
|
return newJekyllFile().catch((err) => { |
||||||
|
expect(window.showErrorMessage).to.have.been.calledWithExactly('must fail'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
}); |
@ -0,0 +1,23 @@ |
|||||||
|
//
|
||||||
|
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||||
|
//
|
||||||
|
// This file is providing the test runner to use when running extension tests.
|
||||||
|
// By default the test runner in use is Mocha based.
|
||||||
|
//
|
||||||
|
// You can provide your own test runner if you want to override it by exporting
|
||||||
|
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||||
|
// host can call to run the tests. The test runner is expected to use console.log
|
||||||
|
// to report the results back to the caller. When the tests are finished, return
|
||||||
|
// a possible error to the callback or null if none.
|
||||||
|
|
||||||
|
const testRunner = require('vscode/lib/testrunner'); // tslint:disable-line
|
||||||
|
|
||||||
|
// You can directly control Mocha options by uncommenting the following lines
|
||||||
|
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||||
|
testRunner.configure({ |
||||||
|
reporter: 'list', |
||||||
|
ui: 'bdd', |
||||||
|
useColors: true |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = testRunner; |
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"module": "commonjs", |
||||||
|
"target": "es6", |
||||||
|
"outDir": "out", |
||||||
|
"lib": [ |
||||||
|
"es6" |
||||||
|
], |
||||||
|
"sourceMap": true, |
||||||
|
"rootDir": "." |
||||||
|
}, |
||||||
|
"exclude": [ |
||||||
|
"node_modules", |
||||||
|
".vscode-test" |
||||||
|
] |
||||||
|
} |
Loading…
Reference in new issue