Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

Commit

Permalink
Added support for text snippet
Browse files Browse the repository at this point in the history
  • Loading branch information
Anantachai Saothong (Manta) committed Mar 26, 2017
1 parent 76382c6 commit c81a53d
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 16 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Haste

**Haste** is a powerful **VS Code** extension for creating file-based snippets, such as `import` and `require` statements in JavaScript.
**Haste** is a powerful **VS Code** extension for creating file-based context-aware snippets, such as `import` and `require` statements in JavaScript.

This extension is heavily inspired by [**Quick Require**](https://marketplace.visualstudio.com/items?itemName=milkmidi.vs-code-quick-require), but it is written from scratch because the latter one supported only `import` and `require` in JavaScript and could not be customized at all. For example, in some JavaScript convention, you might want to omit the JavaScript file extension (`.js`) and the semi-colon (`;`) at the end of the line, hence it becomes `import MyFile from './MyFile'`.

Expand Down
6 changes: 2 additions & 4 deletions edge/FilePattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ export default class FilePattern implements vscode.Disposable {
this.inclusionList = multiPaths.filter(item => item.startsWith('!') === false)
this.exclusionList = _.difference(multiPaths, this.inclusionList).map(item => _.trimStart(item, '!'))


const endOfLine = vscode.workspace.getConfiguration('files').get<string>('eol')
this.interpolate = _.template(_.isArray(config.code) ? config.code.join(endOfLine) : config.code)
this.interpolate = _.template(_.isArray(config.code) ? config.code.join(Shared.getEndOfLine()) : config.code)
}

check(document: vscode.TextDocument): boolean {
Expand All @@ -38,7 +36,7 @@ export default class FilePattern implements vscode.Disposable {
minimatch,
path,
activeDocument: document,
activeFile: new FileInfo(document.fileName),
activeFileInfo: new FileInfo(document.fileName),
}))
} catch (ex) {
console.error(ex)
Expand Down
9 changes: 5 additions & 4 deletions edge/NodePattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as vscode from 'vscode'
import * as _ from 'lodash'
import { match as minimatch } from 'minimatch'

import * as Shared from './Shared'

export default class NodePattern {
private config: NodeConfiguration
readonly interpolate: (object) => string
Expand All @@ -13,11 +15,10 @@ export default class NodePattern {
constructor(config: NodeConfiguration) {
this.config = config

const endOfLine = vscode.workspace.getConfiguration('files').get<string>('eol')
this.interpolate = _.template(_.isArray(config.code) ? config.code.join(endOfLine) : config.code)
this.interpolate = _.template(_.isArray(config.code) ? config.code.join(Shared.getEndOfLine()) : config.code)
}

match(givenPath: string): boolean {
return minimatch([givenPath], this.config.name).length > 0
match(moduleName: string): boolean {
return minimatch([moduleName], this.config.name).length > 0
}
}
5 changes: 5 additions & 0 deletions edge/Shared.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as _ from 'lodash'
import * as babylon from 'babylon'
import * as vscode from 'vscode'

export const PATH_SEPARATOR_FOR_WINDOWS = /\\/g

Expand Down Expand Up @@ -90,3 +91,7 @@ export function findInCodeTree(source: object, target: object) {
return undefined
}
}

export function getEndOfLine() {
return vscode.workspace.getConfiguration('files').get<string>('eol')
}
10 changes: 10 additions & 0 deletions edge/TextItem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as vscode from 'vscode'

export default class TextItem implements vscode.QuickPickItem {
readonly label: string
readonly description: string = ''

constructor(name: string) {
this.label = name
}
}
66 changes: 66 additions & 0 deletions edge/TextPattern.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as vscode from 'vscode'
import * as path from 'path'
import * as _ from 'lodash'
import { match as minimatch } from 'minimatch'

import * as Shared from './Shared'
import FileInfo from './FileInfo'

export default class TextPattern {
private config: TextConfiguration
readonly interpolate: (object) => string

get name() {
return this.config.name
}

constructor(config: TextConfiguration) {
this.config = config

// Escape `${0:var}` to `$\{0:${var}\}`
let block = _.isArray(config.code) ? config.code.join(Shared.getEndOfLine()) : config.code
block = block.split(/\$\{/).map((chunk, order, array) => {
if (order === 0 || /^\d+:/.test(chunk) === false) {
return chunk
}

let index = chunk.length
while (--index && index >= 0) {
if (chunk[index] === '}' && index > 0 && chunk[index - 1] !== '\\') {
break
}
}

if (index >= 0) {
const colon = chunk.indexOf(':')
return '$\\{' + chunk.substring(0, colon) + ':${' + chunk.substring(colon + 1, index).trim() + '}\\}' + chunk.substring(index + 1)
} else {
return chunk
}
}).join('')

const interpolate = _.template(block)
this.interpolate = (scope) => {
// Unescape `$\{...\}`
return interpolate(scope).replace(/\$\\\{/g, '${').replace(/\\\}/g, '}')
}
}

check(document: vscode.TextDocument): boolean {
if (this.config.when) {
try {
return Boolean(_.template('${' + this.config.when + '}')({
_, // Lodash
minimatch,
path,
activeDocument: document,
activeFileInfo: new FileInfo(document.fileName),
}))
} catch (ex) {
console.error(ex)
return false
}
}
return true
}
}
31 changes: 28 additions & 3 deletions edge/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@ import * as Shared from './Shared'
import FileInfo from './FileInfo'
import FilePattern from './FilePattern'
import NodePattern from './NodePattern'
import TextPattern from './TextPattern'
import FileItem from './FileItem'
import NodeItem from './NodeItem'
import TextItem from './TextItem'

const fileCache = new Map<string, FileItem>()
const nodeCache = new Map<string, NodeItem>()

export function activate(context: vscode.ExtensionContext) {
let filePatterns: Array<FilePattern>
let nodePatterns: Array<NodePattern>
let textPatterns: Array<TextPattern>
let jsParserPlugins: Array<string>

function loadLocalConfiguration() {
const config = vscode.workspace.getConfiguration('haste')
filePatterns = config.get<Array<FileConfiguration>>('files', []).map(stub => new FilePattern(stub))
nodePatterns = config.get<Array<NodeConfiguration>>('nodes', []).map(stub => new NodePattern(stub))
textPatterns = config.get<Array<TextConfiguration>>('texts', []).map(stub => new TextPattern(stub))
jsParserPlugins = config.get<Array<string>>('javascript.parser.plugins')
}

Expand Down Expand Up @@ -67,7 +71,7 @@ export function activate(context: vscode.ExtensionContext) {
}

// Add node modules which will be shown in VS Code picker
if (fs.existsSync(path.join(vscode.workspace.rootPath, 'package.json'))) {
if (/^(java|type)script\w*/.test(currentDocument.languageId) && fs.existsSync(path.join(vscode.workspace.rootPath, 'package.json'))) {
const packageJson = require(path.join(vscode.workspace.rootPath, 'package.json'))

items = items.concat(_.chain([_.keys(packageJson.devDependencies), _.keys(packageJson.dependencies)])
Expand All @@ -89,6 +93,11 @@ export function activate(context: vscode.ExtensionContext) {
)
}

items = items.concat(textPatterns
.filter(pattern => pattern.check(currentDocument))
.map(pattern => new TextItem(pattern.name))
)

// Stop processing if the current editor is not active
const editor = vscode.window.activeTextEditor
if (!editor) {
Expand Down Expand Up @@ -137,7 +146,7 @@ export function activate(context: vscode.ExtensionContext) {
}

// Create a snippet
let snippet = ''
let snippet: string = ''
let insertAt: string
if (select instanceof NodeItem) {
const pattern = nodePatterns.find(pattern => pattern.match(select.name))
Expand Down Expand Up @@ -188,9 +197,25 @@ export function activate(context: vscode.ExtensionContext) {
selectFileHasDefaultExport: selectCodeTree === null || Shared.findInCodeTree(selectCodeTree, Shared.EXPORT_DEFAULT) !== undefined || Shared.findInCodeTree(selectCodeTree, Shared.MODULE_EXPORTS) !== undefined,
...Shared,
})

} else if (select instanceof TextItem) {
const pattern = textPatterns.find(pattern => pattern.name === select.label)

snippet = pattern.interpolate({
_, // Lodash
minimatch,
path,
activeDocument: currentDocument,
activeFileInfo: currentFileInfo,
...Shared,
})

// Insert a snippet to the current viewing document
// Does not support tab-stop-and-placeholder, for example `${1:index}`
return editor.insertSnippet(new vscode.SnippetString(snippet))
}

// Write a snippet to the current viewing document
// Insert a snippet to the current viewing document
editor.edit(worker => {
let position: vscode.Position
if (insertAt === 'beforeFirstImport' && existingImports.length > 0) {
Expand Down
6 changes: 6 additions & 0 deletions edge/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ interface NodeConfiguration {
when?: string
insertAt: string
}

interface TextConfiguration {
name: string
code: string | string[]
when?: string
}
38 changes: 34 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,12 @@
"properties": {
"haste.files": {
"type": "array",
"description": "An object containing file constraints.",
"description": "An object containing file snippets.",
"items": {
"type": "object",
"required": [
"path"
"path",
"code"
],
"properties": {
"path": {
Expand Down Expand Up @@ -111,11 +112,12 @@
},
"haste.nodes": {
"type": "array",
"description": "An object containing node module constraints.",
"description": "An object containing node module snippets.",
"items": {
"type": "object",
"required": [
"name"
"name",
"code"
],
"properties": {
"name": {
Expand Down Expand Up @@ -149,6 +151,34 @@
}
]
},
"haste.texts": {
"type": "array",
"description": "An object containing context-aware snippets.",
"items": {
"type": "object",
"required": [
"name",
"code"
],
"properties": {
"name": {
"type": "string",
"description": "A name of this snippet."
},
"when": {
"type": "string",
"description": "A JavaScript boolean expression to control when this pattern is available against the current viewing document."
},
"code": {
"type": [
"string",
"array"
],
"description": "A snippet to be inserted to the current viewing document."
}
}
}
},
"haste.javascript.parser.plugins": {
"type": "array",
"items": {
Expand Down

0 comments on commit c81a53d

Please sign in to comment.