Skip to content

Commit f8d096a

Browse files
committed
support snippets.addFiletypes command
Closes #121
1 parent 91953ea commit f8d096a

File tree

12 files changed

+181
-104
lines changed

12 files changed

+181
-104
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
# 3.2
1+
# 3.3.0
2+
3+
- Add command `snippets.addFiletypes` for add additional snippets filetypes.
4+
- Support variable `b:coc_snippets_filetypes` on buffer create.
5+
6+
# 3.2.0
27

38
- Add execContext-option to enable context-execution for completion.
49
- Fix filepath on vim built with win32unix.

Readme.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ some regex patterns can't be supported by javascript, including
173173
- Use `:CocCommand snippets.editSnippets` to edit user's ultisnips snippets of
174174
current document filetype.
175175
- Use `:CocCommand snippets.openOutput` to open output channel of snippets.
176+
- Use `:CocCommand snippets.addFiletypes` to add additional filetypes of current
177+
buffer.
176178

177179
## Options
178180

@@ -212,6 +214,10 @@ some regex patterns can't be supported by javascript, including
212214

213215
## F.A.Q
214216

217+
**Q:** How to add snippet filetypes on buffer create?
218+
219+
**A:** Use autocmd like: `autocmd BufRead *.md let b:coc_snippets_filetypes = ['tex']`
220+
215221
**Q:** How to trigger snippets completion when type special characters?
216222

217223
**A:** Use configuration like: `"snippets.triggerCharacters": ["'"]`.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
{
4040
"title": "Create MassCode snippet for current filetype.",
4141
"command": "snippets.editMassCodeSnippets"
42+
},
43+
{
44+
"title": "Add additional snippet filetypes to current buffer.",
45+
"command": "snippets.addFiletypes"
4246
}
4347
],
4448
"configuration": {

src/baseProvider.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Document, OutputChannel, Position } from 'coc.nvim'
22
import minimatch from 'minimatch'
33
import { Config, Snippet, SnippetEdit } from './types'
4-
import { distinct } from './util'
4+
import { distinct, getSnippetFiletype } from './util'
55

66
export default abstract class BaseProvider {
77
constructor(protected config: Config, protected channel: OutputChannel) {
@@ -17,6 +17,15 @@ export default abstract class BaseProvider {
1717
return true
1818
}
1919

20+
// load new snippets by filetype
21+
public async loadSnippetsByFiletype(_filetype: string): Promise<void> {
22+
}
23+
24+
protected getDocumentSnippets(doc: Document): Snippet[] {
25+
let filetype = getSnippetFiletype(doc)
26+
return this.getSnippets(filetype)
27+
}
28+
2029
protected isIgnored(filepath: string): boolean {
2130
let ignored = false
2231
let { excludes } = this.config
@@ -69,6 +78,11 @@ export default abstract class BaseProvider {
6978
this.message('Info ', msg, data)
7079
}
7180

81+
82+
protected warn(msg: string, data?: any) {
83+
this.message('Warn ', msg, data)
84+
}
85+
7286
protected error(msg: string, data?: any) {
7387
this.message('Error', msg, data)
7488
}

src/index.ts

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { commands, Disposable, events, ExtensionContext, languages, listManager, Position, Range, snippetManager, TextEdit, Uri, window, workspace } from 'coc.nvim'
1+
import { commands, Disposable, events, Document, ExtensionContext, languages, listManager, Position, Range, snippetManager, Uri, window, workspace } from 'coc.nvim'
22
import merge from 'merge'
33
import path from 'path'
44
import { registerLanguageProvider } from './languages'
@@ -7,25 +7,23 @@ import { MassCodeProvider } from './massCodeProvider'
77
import { ProviderManager } from './provider'
88
import { SnipmateProvider } from './snipmateProvider'
99
import { TextmateProvider } from './textmateProvider'
10-
import { SnippetEditWithSource, UltiSnippetOption, UltiSnipsConfig } from './types'
10+
import { UltiSnipsConfig } from './types'
1111
import { getSnippetsDirectory, UltiSnippetsProvider } from './ultisnipsProvider'
12-
import { sameFile, waitDocument } from './util'
12+
import { addFiletypes, getAdditionalFiletype, getSnippetFiletype, insertSnippetEdit, sameFile, waitDocument } from './util'
1313

1414
interface API {
1515
expandable: () => Promise<boolean>
1616
}
1717

18-
19-
async function insertSnippetEdit(edit: SnippetEditWithSource) {
20-
let ultisnips = edit.source == 'ultisnips' || edit.source == 'snipmate'
21-
let option: UltiSnippetOption
22-
if (ultisnips) {
23-
option = {
24-
regex: edit.regex,
25-
context: edit.context
26-
}
18+
function checkBufferVariable(doc: Document): void {
19+
let filetypes = doc.getVar('snippets_filetypes', undefined) as string[]
20+
if (!Array.isArray(filetypes)) filetypes = undefined
21+
if (!filetypes) {
22+
let arr = getAdditionalFiletype(doc.bufnr)
23+
if (arr) doc.buffer.setVar('coc_snippets_filetypes', arr, true)
24+
} else if (filetypes.length > 0) {
25+
addFiletypes(doc.bufnr, filetypes)
2726
}
28-
await commands.executeCommand('editor.action.insertSnippet', TextEdit.replace(edit.range, edit.newText), option)
2927
}
3028

3129
function enableSnippetsFiletype(subscriptions: Disposable[]) {
@@ -34,15 +32,17 @@ function enableSnippetsFiletype(subscriptions: Disposable[]) {
3432
if (doc.uri.endsWith('.snippets')) {
3533
doc.buffer.setOption('filetype', 'snippets', true)
3634
}
35+
checkBufferVariable(doc)
3736
})
3837
workspace.onDidOpenTextDocument(async document => {
3938
if (document.uri.endsWith('.snippets')) {
4039
let doc = workspace.getDocument(document.uri)
41-
if (!doc) return
42-
let { buffer } = doc
43-
await buffer.setOption('filetype', 'snippets')
40+
let buf = nvim.createBuffer(doc.bufnr)
41+
buf.setOption('filetype', 'snippets', true)
4442
}
43+
checkBufferVariable(workspace.getDocument(document.bufnr))
4544
}, null, subscriptions)
45+
4646
const rtp = workspace.env.runtimepath
4747
let paths = rtp.split(',')
4848
let idx = paths.findIndex(s => /^ultisnips$/i.test(path.basename(s)))
@@ -98,6 +98,20 @@ export async function activate(context: ExtensionContext): Promise<API> {
9898
const manager = new ProviderManager(channel, subscriptions)
9999

100100
enableSnippetsFiletype(subscriptions)
101+
subscriptions.push(commands.registerCommand('snippets.addFiletypes', async (...args: string[]) => {
102+
if (args.length === 0) {
103+
let res = await window.requestInput('Filetype to add', '', { position: 'center' })
104+
if (res == '') return
105+
args = res.split('.')
106+
}
107+
let filetypes = args.join('.').split('.')
108+
let buf = await nvim.buffer
109+
addFiletypes(buf.id, filetypes)
110+
let curr = getAdditionalFiletype(buf.id)
111+
buf.setVar('coc_snippets_filetypes', curr, true)
112+
manager.loadSnippetsByFiletype(filetypes.join('.'))
113+
}))
114+
101115
let excludes = configuration.get<string[]>('excludePatterns', [])
102116
if (!Array.isArray(excludes)) excludes = []
103117
excludes = excludes.map(p => workspace.expand(p))
@@ -153,11 +167,8 @@ export async function activate(context: ExtensionContext): Promise<API> {
153167
trace: configuration.get<boolean>('massCode.trace', false),
154168
excludes
155169
}
156-
157170
let provider = new MassCodeProvider(channel, config)
158-
159171
manager.regist(provider, 'massCode')
160-
161172
subscriptions.push(commands.registerCommand('snippets.editMassCodeSnippets', provider.createSnippet.bind(provider)))
162173
}
163174

@@ -237,7 +248,8 @@ export async function activate(context: ExtensionContext): Promise<API> {
237248
window.showErrorMessage('Document not found')
238249
return
239250
}
240-
let files = await manager.getSnippetFiles(doc.filetype)
251+
let filetype = getSnippetFiletype(doc)
252+
let files = await manager.getSnippetFiles(filetype)
241253
if (!files.length) {
242254
window.showWarningMessage('No related snippet file found')
243255
return

src/list/snippet.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ Author Qiming Zhao <chemzqm@gmail> (https://github.com/chemzqm)
55
import { BasicList, ListContext, ListItem, Location, Mru, Position, Range, Uri, workspace } from 'coc.nvim'
66
import os from 'os'
77
import { ProviderManager } from '../provider'
8+
import { getSnippetFiletype } from '../util'
89

910
export default class SnippetsList extends BasicList {
1011
public readonly name = 'snippets'
1112
public readonly description = 'snippets list'
1213
constructor(nvim: any, private manager: ProviderManager) {
13-
super(nvim)
14+
super()
1415
this.addLocationActions()
1516
}
1617

@@ -21,7 +22,8 @@ export default class SnippetsList extends BasicList {
2122
let buf = await window.buffer
2223
let doc = workspace.getDocument(buf.id)
2324
if (!doc) return []
24-
let snippets = this.manager.getSnippets(doc.filetype)
25+
let filetype = getSnippetFiletype(doc)
26+
let snippets = this.manager.getSnippets(filetype)
2527
let res: ListItem[] = []
2628
for (let snip of snippets) {
2729
let pos: Position = Position.create(snip.lnum, 0)

src/massCodeProvider.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,9 @@ export class MassCodeProvider extends BaseProvider {
8080

8181
public async getTriggerSnippets(document: Document, position: Position, autoTrigger?: boolean): Promise<SnippetEdit[]> {
8282
if (autoTrigger) return []
83-
8483
const line = document.getline(position.line)
8584
if (line.length == 0) return []
86-
const snippets = this.getSnippets(document.filetype).filter(s => {
85+
const snippets = this.getDocumentSnippets(document).filter(s => {
8786
if (autoTrigger && !s.autoTrigger) return false
8887
let match = getMatched(s, line)
8988
if (match == null) return false

src/provider.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { CancellationToken, CompletionItem, CompletionItemKind, CompletionItemProvider, Disposable, Document, InsertTextFormat, OutputChannel, Position, Range, snippetManager, window, workspace } from 'coc.nvim'
22
import path from 'path'
33
import BaseProvider from './baseProvider'
4-
import { Snippet, SnippetEditWithSource, VimCompletionContext, TriggerKind } from './types'
5-
import { characterIndex, markdownBlock } from './util'
4+
import { Snippet, SnippetEditWithSource, TriggerKind, VimCompletionContext } from './types'
5+
import { characterIndex, getSnippetFiletype, markdownBlock } from './util'
66

77
export class ProviderManager implements CompletionItemProvider {
88
private providers: Map<string, BaseProvider> = new Map()
99
constructor(
1010
private channel: OutputChannel,
11-
subscriptions: Disposable[]
11+
private subscriptions: Disposable[]
1212
) {
13-
subscriptions.push(Disposable.create(() => {
13+
this.subscriptions.push(Disposable.create(() => {
1414
this.providers.clear()
1515
}))
1616
}
@@ -28,12 +28,31 @@ export class ProviderManager implements CompletionItemProvider {
2828

2929
public async init(): Promise<void> {
3030
let providers = Array.from(this.providers.values())
31-
await Promise.all(providers.map(provider => {
32-
return provider.init()
33-
})).catch(e => {
31+
await Promise.allSettled(providers.map(provider => provider.init().catch(e => {
3432
workspace.nvim.echoError(e)
35-
this.appendError('init', e)
33+
this.appendError('Error on provider init:', e)
34+
})))
35+
36+
let promises: Promise<void>[] = []
37+
workspace.documents.forEach(doc => {
38+
let filetype = getSnippetFiletype(doc)
39+
promises.push(this.loadSnippetsByFiletype(filetype))
3640
})
41+
workspace.onDidOpenTextDocument(async doc => {
42+
let filetype = getSnippetFiletype({ bufnr: doc.bufnr, filetype: doc.languageId })
43+
await this.loadSnippetsByFiletype(filetype)
44+
}, null, this.subscriptions)
45+
await Promise.allSettled(promises)
46+
}
47+
48+
public async loadSnippetsByFiletype(filetype: string): Promise<void> {
49+
let promises: Promise<void>[] = []
50+
for (let [name, provider] of this.providers.entries()) {
51+
promises.push(provider.loadSnippetsByFiletype(filetype).catch(e => {
52+
this.appendError(`Error on load "${name}" snippets:`, e)
53+
}))
54+
}
55+
await Promise.allSettled(promises)
3756
}
3857

3958
public getSnippets(filetype: string): Snippet[] {
@@ -117,7 +136,8 @@ export class ProviderManager implements CompletionItemProvider {
117136
let doc = workspace.getDocument(document.uri)
118137
if (!doc) return []
119138
let bufnr = doc.bufnr
120-
let snippets = this.getSnippets(doc.filetype)
139+
let filetype = getSnippetFiletype(doc)
140+
let snippets = this.getSnippets(filetype)
121141
let currline = doc.getline(position.line, true)
122142
let { input, col, line, colnr } = context.option
123143
let character = characterIndex(line, col)

src/snipmateProvider.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function convertBody(body: string): string {
2727
export class SnipmateProvider extends BaseProvider {
2828
private fileItems: FileItem[] = []
2929
private snippetFiles: SnipmateFile[] = []
30+
private loadedLanguageIds: Set<string> = new Set()
3031
constructor(
3132
channel: OutputChannel,
3233
protected config: SnipmateConfig,
@@ -50,7 +51,7 @@ export class SnipmateProvider extends BaseProvider {
5051
public async init(): Promise<void> {
5152
let { nvim } = workspace
5253
let author = await nvim.getVar('snips_author')
53-
if (!author) await nvim.setVar('snips_author', this.config.author)
54+
if (!author) nvim.setVar('snips_author', this.config.author, true)
5455
this.fileItems = await this.loadAllSnippetFiles()
5556
workspace.onDidRuntimePathChange(async e => {
5657
for (let rtp of e) {
@@ -65,18 +66,14 @@ export class SnipmateProvider extends BaseProvider {
6566
}
6667
}
6768
}, null, this.subscriptions)
68-
for (let filetype of workspace.filetypes) {
69-
await this.loadByFiletype(filetype)
70-
}
71-
workspace.onDidOpenTextDocument(async e => {
72-
let doc = workspace.getDocument(e.bufnr)
73-
await this.loadByFiletype(doc.filetype)
74-
}, null, this.subscriptions)
7569
}
7670

77-
private async loadByFiletype(filetype: string): Promise<void> {
71+
public async loadSnippetsByFiletype(filetype: string): Promise<void> {
7872
let filetypes = filetype ? this.getFiletypes(filetype) : []
7973
filetypes.push('_')
74+
filetypes = filetypes.filter(filetype => !this.loadedLanguageIds.has(filetype))
75+
if (filetypes.length == 0) return
76+
filetypes.forEach(filetype => this.loadedLanguageIds.add(filetype))
8077
for (let item of this.fileItems) {
8178
if (!filetypes.includes(item.filetype)) continue
8279
await this.loadSnippetsFromFile(item.filetype, item.filepath)
@@ -90,18 +87,20 @@ export class SnipmateProvider extends BaseProvider {
9087
if (idx !== -1) this.fileItems.splice(idx, 1)
9188
if (this.isIgnored(filepath)) return
9289
let res = await this.parseSnippetsFile(filetype, filepath)
93-
this.snippetFiles.push({ filepath, filetype, snippets: res.snippets })
94-
this.info(`Loaded ${res.snippets.length} ${filetype} snipmate snippets from: ${filepath}`)
95-
if (res.extends.length) {
96-
let fts = res.extends
97-
let curr = this.config.extends[filetype] || []
98-
for (let ft of fts) {
99-
await this.loadByFiletype(ft)
100-
if (!curr.includes(ft)) {
101-
curr.push(ft)
90+
if (this.snippetFiles.findIndex(o => sameFile(o.filepath, filepath)) == -1) {
91+
this.snippetFiles.push({ filepath, filetype, snippets: res.snippets })
92+
this.info(`Loaded ${res.snippets.length} ${filetype} snipmate snippets from: ${filepath}`)
93+
if (res.extends.length) {
94+
let fts = res.extends
95+
let curr = this.config.extends[filetype] || []
96+
for (let ft of fts) {
97+
await this.loadSnippetsByFiletype(ft)
98+
if (!curr.includes(ft)) {
99+
curr.push(ft)
100+
}
102101
}
102+
this.config.extends[filetype] = curr
103103
}
104-
this.config.extends[filetype] = curr
105104
}
106105
}
107106

@@ -183,7 +182,7 @@ export class SnipmateProvider extends BaseProvider {
183182

184183
public async getTriggerSnippets(document: Document, position: Position, autoTrigger: boolean): Promise<SnippetEdit[]> {
185184
if (autoTrigger) return []
186-
let snippets = this.getSnippets(document.filetype)
185+
let snippets = this.getDocumentSnippets(document)
187186
let line = document.getline(position.line)
188187
line = line.slice(0, position.character)
189188
if (!line) return []

0 commit comments

Comments
 (0)