Skip to content

Commit

Permalink
Add decorator provider
Browse files Browse the repository at this point in the history
  • Loading branch information
mattbierner committed May 13, 2017
1 parent 367010a commit 4cc940b
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 87 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Change Log

## 0.1.0 - May 13, 2017
* Continue tweaking when emojis are shown.
* Added support for showing emoji decorators inline for `:rocket:` sytax. Can also view large previews of the emojis by hovering

## 0.1.0 - May 12, 2017
- Tweak when completions are shown to not get in the way as much. They will no longer show when the colon is prefixed by a letter such as: `a:|`. In these cases, there must also be a letter after the colon before we show suggestions: `a:b|`
- Added `showOnColon` setting to disable automatically showing suggestions when you type a colon.
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Adds suggestions and autocomplete for emoji.
Adds suggestions and autocomplete for emoji to VS Code.

![Example](/media/example.gif?raw=true)

Expand All @@ -9,9 +9,9 @@ Inspired by the [Atom autocomplete+ emojis suggestions plugin][atom].
- Quickly insert emoji using the `:smile:` syntax supported by Github and many other sites
- Insert emoji markup by typing `::`
- Enable and control emoji completion settings per language
- See emoji previews of `:smile:` style markup inline

[List of supported emoji][cheat]
:cry:

## General Configuration

Expand All @@ -21,6 +21,7 @@ Inspired by the [Atom autocomplete+ emojis suggestions plugin][atom].

- `emojisense.showOnColon`: Should emoji completions automatically be shown when you type a colon? Enabled by default. If you disable `showOnColon`, it is recomended that you have `quickSuggestions` enabled.

- `emojisense.emojiDecoratorsEnabled`: Show emoji decorators inline for `:smile_cat:` markup in a file? Enabled by default in markdown.

## Per Language Configuration

Expand All @@ -41,7 +42,8 @@ You can also change the settings for each language. Here's the default emojisens
"emojisense.languages": {
"markdown": true,
"plaintext": {
"markupCompletionsEnabled": false
"markupCompletionsEnabled": false,
"emojisense.emojiDecoratorsEnabled": false
},
"git-commit": true
}
Expand Down
Binary file modified media/example.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"name": "emojisense",
"displayName": ":emojisense:",
"description": "Adds suggestions and autocomplete for emoji",
"version": "0.1.0",
"version": "0.2.0",
"publisher": "bierner",
"icon": "media/icon.svg",
"license": "mit",
"license": "MIT",
"keywords": [
"emoji",
"emojis",
Expand Down Expand Up @@ -38,17 +38,17 @@
},
"emojisense.markupCompletionsEnabled": {
"type": "boolean",
"description": "Enable completions that insert emoji markdown, i.e. ::smile_cat -> :smile_cat:",
"description": "Enable completions that insert emoji markup, i.e. ::smile_cat -> :smile_cat:",
"default": true
},
"emojisense.showOnColon": {
"type": "boolean",
"description": "Should emoji completions automatically be shown when you type a colon?",
"default": true
},
"emojisense.hoverEnabled": {
"emojisense.emojiDecoratorsEnabled": {
"type": "boolean",
"description": "Enable hover to preview emoji?",
"description": "Enables or disables emoji decorators for emoji markup",
"default": true
},
"emojisense.languages": {
Expand All @@ -57,7 +57,8 @@
"default": {
"markdown": true,
"plaintext": {
"markupCompletionsEnabled": false
"markupCompletionsEnabled": false,
"emojiDecoratorsEnabled": false
},
"git-commit": true
},
Expand Down Expand Up @@ -105,4 +106,4 @@
"data-uri": "0.0.3",
"gemoji": "^4.1.0"
}
}
}
102 changes: 102 additions & 0 deletions src/DecoratorProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as vscode from "vscode"
import { EmojiProvider, Emoji } from './emoji'
import Configuration from './configuration'

const Datauri = require('datauri')

export default class DecoratorProvider extends vscode.Disposable {

private readonly disposables: vscode.Disposable[]

private readonly decorationType: vscode.TextEditorDecorationType

private activeEditor: vscode.TextEditor | undefined = undefined
private timeout: any;

constructor(
private readonly emojiProvider: EmojiProvider,
private readonly config: Configuration
) {
super(() => this.dispose())
this.decorationType = vscode.window.createTextEditorDecorationType({})

this.activeEditor = vscode.window.activeTextEditor
this.setDecorators(this.activeEditor)

vscode.window.onDidChangeActiveTextEditor(editor => {
this.activeEditor = editor
if (editor) {
this.triggerUpdateDecorations()
}
}, this, this.disposables)

vscode.workspace.onDidChangeTextDocument((event: vscode.TextDocumentChangeEvent) => {
if (this.activeEditor && event.document === this.activeEditor.document) {
this.triggerUpdateDecorations();
}
}, this, this.disposables)
}

dispose() {
let d: vscode.Disposable | undefined = undefined
while ((d = this.disposables.pop())) {
d.dispose()
}
}

private triggerUpdateDecorations(): void {
if (this.timeout) {
return
}
this.timeout = setTimeout(() => {
this.setDecorators(this.activeEditor)
this.timeout = null
}, 300)
}

private setDecorators(activeEditor: vscode.TextEditor | undefined) {
if (!activeEditor || !this.config.isInlineEnabled(activeEditor.document.languageId)) {
return false
}

const regEx = /:([\w\d_\+\-]+?):/g;
const text = activeEditor.document.getText();
let match;
const d: vscode.DecorationOptions[] = []
while (match = regEx.exec(text)) {
const name = match[1]
const emoji = this.emojiProvider.lookup(name)
if (!emoji) {
continue
}

const startPos = activeEditor.document.positionAt(match.index + 1);
const endPos = activeEditor.document.positionAt(match.index + match[0].length - 1);
d.push({
range: new vscode.Range(startPos, endPos),
hoverMessage: this.hoverMessage(emoji),
renderOptions: {
after: {
contentText: emoji.emoji,
margin: '0.2em',
color: 'rgba(255, 255, 255, 0.4)'
}
}
})
}
activeEditor.setDecorations(this.decorationType, d);
}

private hoverMessage(emoji: Emoji): string {
const width = 160
const height = 160
const datauri = new Datauri();
const src = `<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="${width}px" height="${height}px" viewBox="0 0 ${width} ${height}" xml:space="preserve">
<text x="50%" y="50%" text-anchor="middle" alignment-baseline="central" font-size="120">${emoji.emoji}</text>
</svg>`;
datauri.format('.svg', src);
return `![](${datauri.content})`
}
}
7 changes: 4 additions & 3 deletions src/EmojiCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default class EmojiCompletionProvider implements CompletionItemProvider {
const preExistingMatch = pre.match(/:[\w\d_\+\-]+:$/)

// If there is a character before the color, require at least one character after it
const preMatch = preExistingMatch || pre.match(/\B:(:?)$|:(:?)([\w\d_\+\-]+?)$/)
const preMatch = preExistingMatch || pre.match(/(?:\s|^)(:(:?)$)|(:(:?)[\w\d_\+\-]+?)$/)
if (!preMatch) {
return []
}
Expand All @@ -33,10 +33,10 @@ export default class EmojiCompletionProvider implements CompletionItemProvider {
const postMatch = post.match(/[\w\d_\+\-]*?:?/)

const replacementSpan: Range = new Range(
position.translate(0, -preMatch[0].length),
position.translate(0, -(preMatch[1] || preMatch[3]).length),
postMatch ? position.translate(0, postMatch[0].length) : position)

if (pre.length >= 2 && (preMatch[1] || preMatch[2])) {
if (pre.length >= 2 && (preMatch[2] || preMatch[4])) {
return this.getMarkupEmojiCompletions(document, replacementSpan)
}

Expand Down Expand Up @@ -65,6 +65,7 @@ export default class EmojiCompletionProvider implements CompletionItemProvider {
return Array.from(this.emojiProvider.emojis).map(x => {
const item = new CompletionItem(`::${x.name}`, CompletionItemKind.Text)
item.detail = `:${x.name}:`
item.documentation = x.emoji
item.insertText = `:${x.name}:`
item.filterText = x.name
item.range = replacementSpan
Expand Down
47 changes: 0 additions & 47 deletions src/EmojiHoverProvider.ts

This file was deleted.

21 changes: 11 additions & 10 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ interface LanguageConfig {
readonly unicodeCompletionsEnabled?: boolean
readonly markupCompletionsEnabled?: boolean
readonly showOnColon?: boolean
readonly hoverEnabled?: boolean
readonly emojiDecoratorsEnabled?: boolean
}

export default class Configuration implements LanguageConfig {
Expand All @@ -13,7 +13,7 @@ export default class Configuration implements LanguageConfig {
unicodeCompletionsEnabled: boolean
markupCompletionsEnabled: boolean
showOnColon: boolean
hoverEnabled: boolean
emojiDecoratorsEnabled: boolean

constructor() {
this.updateConfiguration()
Expand All @@ -35,28 +35,29 @@ export default class Configuration implements LanguageConfig {
return this.is('showOnColon', forLanguage);
}

public isHoverEnabled(forLanguage: string): boolean {
return this.is('hoverEnabled', forLanguage);
public isInlineEnabled(forLanguage: string): boolean {
return this.is('emojiDecoratorsEnabled', forLanguage);
}

private is(setting: keyof LanguageConfig, forLanguage: string): boolean {
const languageConfig = this.getLanguageConfig(forLanguage)
if (languageConfig && typeof languageConfig[setting] !== 'undefined') {
return !!languageConfig[setting]
if (!languageConfig) {
return false
}
return this[setting]

return typeof languageConfig[setting] !== 'undefined' ? !!languageConfig[setting] : this[setting];
}

private getLanguageConfig(languageId: string): LanguageConfig {
return this.languageConfigurations.get(languageId) || {}
private getLanguageConfig(languageId: string): LanguageConfig | undefined {
return this.languageConfigurations.get(languageId)
}

public updateConfiguration(): void {
const config = vscode.workspace.getConfiguration('emojisense')
this.unicodeCompletionsEnabled = config.get<boolean>('unicodeCompletionsEnabled', true)
this.markupCompletionsEnabled = config.get<boolean>('markupCompletionsEnabled', true)
this.showOnColon = config.get<boolean>('showOnColon', true)
this.hoverEnabled = config.get<boolean>('hoverEnabled', true)
this.emojiDecoratorsEnabled = config.get<boolean>('emojiDecoratorsEnabled', true)

this.languageConfigurations = new Map()
const languagesConfig = config.get<any>('languages', {})
Expand Down
27 changes: 11 additions & 16 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,38 @@
import * as vscode from 'vscode'
import EmojiCompletionProvider from './EmojiCompletionProvider'
import EmojiHoverProvider from './EmojiHoverProvider';
import { EmojiProvider } from './emoji'
import Configuration from './configuration'
import DecoratorProvider from "./DecoratorProvider";

function registerProviders(
provider: EmojiCompletionProvider,
hoverProvider: EmojiHoverProvider,
config: Configuration
): vscode.Disposable {
const completions: vscode.Disposable[] = []
const hovers: vscode.Disposable[] = []
const disposables: vscode.Disposable[] = []
for (const language of config.languages) {
if (config.shouldShowOnColon(language)) {
completions.push(vscode.languages.registerCompletionItemProvider(language, provider, ':'))
disposables.push(vscode.languages.registerCompletionItemProvider(language, provider, ':'))
} else {
completions.push(vscode.languages.registerCompletionItemProvider(language, provider))
}

if (config.isHoverEnabled(language)) {
completions.push(vscode.languages.registerHoverProvider(language, hoverProvider))
disposables.push(vscode.languages.registerCompletionItemProvider(language, provider))
}
}

return vscode.Disposable.from(...completions, ...hovers);
return vscode.Disposable.from(...disposables);
}


export function activate(context: vscode.ExtensionContext) {
const emoji = new EmojiProvider()
const config = new Configuration()
const provider = new EmojiCompletionProvider(emoji, config)
const hoverProvider = new EmojiHoverProvider(emoji)

let providerSub = registerProviders(provider, hoverProvider, config)
let providerSub = registerProviders(provider, config)

context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => {
vscode.workspace.onDidChangeConfiguration(() => {
config.updateConfiguration()
providerSub.dispose()
providerSub = registerProviders(provider, hoverProvider, config)
}))
providerSub = registerProviders(provider, config)
}, null, context.subscriptions)

context.subscriptions.push(new DecoratorProvider(emoji, config))
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
],
"sourceMap": true,
"rootDir": ".",
"strict": true
"strict": true,
"noUnusedParameters": true
},
"exclude": [
"node_modules",
Expand Down

0 comments on commit 4cc940b

Please sign in to comment.