Skip to content

Commit

Permalink
feat: [jetbrains] ✨ Add generator for jetbrain's live template
Browse files Browse the repository at this point in the history
  • Loading branch information
hikerpig committed Jan 23, 2020
1 parent cd27032 commit 7da6bf9
Show file tree
Hide file tree
Showing 9 changed files with 329 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ endsnippet
- vscode, [Visual Studio Code](https://code.visualstudio.com/docs/editor/userdefinedsnippets)
- atom, [Atom](https://flight-manual.atom.io/using-atom/sections/snippets/)
- sublime , [Sublime Text](http://www.sublimetext.info/docs/en/extensibility/snippets.html)
- jetbrains , [JetBrains live template](https://www.jetbrains.com/help/idea/using-live-templates.html)

## Usage

Expand Down
4 changes: 4 additions & 0 deletions packages/jetbrains/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
describe('jetbrains tests', () => {
it('setup done', () => {
})
})
38 changes: 38 additions & 0 deletions packages/jetbrains/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@unisnips/jetbrains",
"version": "0.5.1-alpha.0",
"description": "Utilities for generating JetBrains live templates in unisnips project",
"keywords": [
"unisnips",
"snippets",
"live-template",
"jetbrains"
],
"author": "hikperig <hikerpigwinnie@gmail.com>",
"license": "MIT",
"main": "lib/index.js",
"typings": "lib/index.d.ts",
"files": [
"lib",
"LICENSE"
],
"repository": {
"type": "git",
"url": "git@github.com:hikerpig/unisnips.git"
},
"scripts": {
"build": "tsc --pretty",
"dev": "tsc --watch",
"test": "jest"
},
"dependencies": {
},
"devDependencies": {
"jest": "*",
"typescript": "~3.7.2"
},
"sideEffects": false,
"publishConfig": {
"access": "public"
}
}
232 changes: 232 additions & 0 deletions packages/jetbrains/src/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import path from 'path'
import {
SnippetDefinition,
GenerateOptions,
SnippetPlaceholder,
PlaceholderReplacement,
UnisnipsGenerator,
applyReplacements,
} from '@unisnips/core'

type ResultPair = {
variable: JetBrainsVariable
replacement: PlaceholderReplacement
}

function detectVariableReplacements(placeholders: SnippetPlaceholder[]): ResultPair[] {
const resultPairs: ResultPair[] = []
placeholders.forEach(placeholder => {
const { valueType, variable, description, index } = placeholder
let newDesc: string
let jbVariable: JetBrainsVariable
if (valueType === 'positional') {
// TODO: transform ?
if (placeholder.transform) {
const transform = placeholder.transform
const transformStr = ['', transform.search, transform.replace, transform.options].join('/')
newDesc = `$\{${index}${transformStr}\}`
} else {
newDesc = `$${index}$`
jbVariable = {
name: index.toString(),
defaultValue: index.toString(),
alwaysStopAt: true,
}
}
} else if (valueType === 'variable') {
// if (variable.type === 'builtin') {
// newDesc = variable.name
// }
} else if (valueType === 'script') {
console.warn('[jetbrains] script placeholder is not supported')
}
if (jbVariable) {
const replacement: PlaceholderReplacement = {
type: 'string',
placeholder,
replaceContent: newDesc,
}
resultPairs.push({
variable: jbVariable,
replacement,
})
}
})
return resultPairs
}

type JetBrainsVariable = {
name: string
expression?: string
defaultValue: string
alwaysStopAt?: boolean
}

type JetBrainsSnippetItem = {
name: string
value: string
description: string
variables?: JetBrainsVariable[]
contexts?: string[]
}

type XMLAttribute = {
name: string
value: string | boolean | number
}

type XMLNode = {
tagName: string
attributes: XMLAttribute[]
children?: XMLNode[]
}

function indent(str: string, cols: number) {
let prefix = ''
for (let i = 0; i < cols; i++) {
prefix += ' '
}
return str
.split('\n')
.map(l => {
return prefix + l
})
.join('\n')
}

function nodeTreeToXml(root: XMLNode, level = 0): string {
const attrStr = root.attributes
.filter(attr => attr.value !== undefined)
.map(attr => {
return `${attr.name}="${attr.value.toString()}"`
})
.join(' ')
const childrenStr = root.children
? root.children
.map(child => {
return nodeTreeToXml(child, 1)
})
.join('\n')
: ''

if (childrenStr) {
return indent(
`<${root.tagName} ${attrStr}>
${childrenStr}
</${root.tagName}>`,
level * 2,
)
} else {
return indent(`<${root.tagName} ${attrStr} />`, level * 2)
}
}

const JB_LANG_MAP: { [key: string]: string } = {
other: 'OTHER',
sh: 'SHELL_SCRIPT',
bash: 'SHELL_SCRIPT',
javascript: 'JAVA_SCRIPT',
typescript: 'TypeScript',
css: 'CSS',
python: 'Python',
xml: 'XML',
html: 'HTML',
sql: 'SQL',
}

function attr(name: string, value: string | boolean) {
return { name, value }
}

function makeTag(name: string, attrs: XMLAttribute[], children?: XMLNode[]): XMLNode {
return {
tagName: name,
attributes: attrs,
children,
}
}

/* eslint-disable prettier/prettier */
function snippetItemToXml(item: JetBrainsSnippetItem) {
const uniqueVariableMap: {[key: string]: JetBrainsVariable} = {}
if (item.variables) {
item.variables.forEach((variable) => {
const k = [variable.name, variable.defaultValue, variable.expression].join('_')
if (!uniqueVariableMap[k]) {
uniqueVariableMap[k] = variable
}
})
}

const variableNodes = Object.values(uniqueVariableMap).map(variable => {
return makeTag('variable', [
attr('name', variable.name),
attr('expression', variable.expression),
attr('defaultValue', variable.defaultValue),
attr('alwaysStopAt', variable.alwaysStopAt),
])
})

const contextNodes = (item.contexts || []).map(contextName => {
return makeTag(
'context',
[],
[makeTag('option', [attr('name', contextName), attr('value', 'true')])],
)
})

const escapedValue = item.value.replace(/\n/g, '&#10;')
const template = makeTag(
'template',
[attr('name', item.name), attr('description', item.description), attr('value', escapedValue)],
[...variableNodes, ...contextNodes],
)
return nodeTreeToXml(template)
}
/* eslint-enable prettier/prettier */

export const generateSnippets: UnisnipsGenerator['generateSnippets'] = (
defs: SnippetDefinition[],
opts: GenerateOptions = {},
) => {
const segs: string[] = []
let langName: string
let groupName = 'unisnips'
if (opts.snippetsFilePath) {
langName = path.basename(opts.snippetsFilePath).replace(/\..*/, '')
groupName = `unisnips-${langName}`
}

defs.forEach(def => {
const pairs = detectVariableReplacements(def.placeholders)
const replacements = pairs.map(o => o.replacement)
const variables: JetBrainsVariable[] = []
pairs.forEach(({ variable }) => {
variables.push(variable)
})
const newBody = applyReplacements(def, replacements)
const item: JetBrainsSnippetItem = {
name: def.trigger,
value: newBody,
description: def.description,
variables,
}
let jbContextName = JB_LANG_MAP.other
if (langName) {
jbContextName = JB_LANG_MAP[langName]
}
if (jbContextName) {
item.contexts = [jbContextName]
}
segs.push(snippetItemToXml(item))
})
const snippetsContent = segs.join('\n')
const content = [
`<templateSet group="${groupName}">`,
`${indent(snippetsContent, 2)}`,
'</templateSet>',
].join('\n')
return {
content,
}
}
9 changes: 9 additions & 0 deletions packages/jetbrains/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { UnisnipsGenerator } from '@unisnips/core'

import { generateSnippets } from './generate'

const PLUGIN_JETBRAINS: UnisnipsGenerator = {
generateSnippets,
}

export default PLUGIN_JETBRAINS
9 changes: 9 additions & 0 deletions packages/jetbrains/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib"
},
"include": [
"src/**/*"
]
}
31 changes: 31 additions & 0 deletions packages/unisnips/__tests__/to-jetbrains.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import outdent from 'outdent'

import { ULTI_SNIPPETS } from '../../../tools/test-tool/src/ultisnips'

import { convert } from '../src/index'

import { ParseOptions } from '@unisnips/core'

describe('convert to jetbrains live template', () => {
const convertToJetBrains = (inputContent: string, opts: ParseOptions = {}) => {
return convert({
target: 'jetbrains',
inputContent,
...opts,
})
}

it('generate right placeholder', () => {
const { content } = convertToJetBrains(ULTI_SNIPPETS.SIMPLE)
// console.log(content)
expect(content).toEqual(outdent`
<templateSet group="unisnips">
<template name="subsec" description="seperator" value="---------------- $1$ ----------------------&#10;----------------end $1$ -------------------">
<variable name="1" defaultValue="1" alwaysStopAt="true" />
<context >
<option name="OTHER" value="true" />
</context>
</template>
</templateSet>`)
})
})
1 change: 1 addition & 0 deletions packages/unisnips/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"dependencies": {
"@unisnips/atom": "^0.5.1-alpha.0",
"@unisnips/core": "^0.5.1-alpha.0",
"@unisnips/jetbrains": "^0.5.1-alpha.0",
"@unisnips/sublime": "^0.5.1-alpha.0",
"@unisnips/ultisnips": "^0.5.1-alpha.0",
"@unisnips/vscode": "^0.5.1-alpha.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/unisnips/src/services/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import {
} from '@unisnips/core'

import PLUGIN_ULTISNIPS from '@unisnips/ultisnips'

import PLUGIN_VSCODE from '@unisnips/vscode'
import PLUGIN_ATOM from '@unisnips/atom'
import PLUGIN_SUBLIME from '@unisnips/sublime'
import PLUGIN_JETBRAINS from '@unisnips/jetbrains'

const UNISNIPS_SUPPORTED_SOURCES = {
ultisnips: 'ultisnips',
Expand All @@ -19,6 +21,7 @@ const UNISNIPS_SUPPORTED_TARGETS = {
vscode: 'vscode',
atom: 'atom',
sublime: 'sublime',
jetbrains: 'jetbrains',
}

class PluginManager {
Expand Down Expand Up @@ -52,6 +55,7 @@ pluginManager.registerParser(UNISNIPS_SUPPORTED_SOURCES.ultisnips, PLUGIN_ULTISN
pluginManager.registerGenerator(UNISNIPS_SUPPORTED_TARGETS.vscode, PLUGIN_VSCODE)
pluginManager.registerGenerator(UNISNIPS_SUPPORTED_TARGETS.atom, PLUGIN_ATOM)
pluginManager.registerGenerator(UNISNIPS_SUPPORTED_TARGETS.sublime, PLUGIN_SUBLIME)
pluginManager.registerGenerator(UNISNIPS_SUPPORTED_TARGETS.jetbrains, PLUGIN_JETBRAINS)
// ----------------end Register plugins -------------------

type UnisnipsParseOptions = ParseOptions & {
Expand Down

0 comments on commit 7da6bf9

Please sign in to comment.