Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

LSP snippets #288

Closed
wants to merge 81 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
a1ff497
Start on LSP syntax parser
Aerijo Feb 5, 2019
bb03336
Update grammar for 95% compliance
Aerijo Feb 6, 2019
08019c5
Ony escape necessary characters
Aerijo Feb 6, 2019
675b7d4
return regex
Aerijo Feb 6, 2019
df90657
formatting and tweaks
Aerijo Feb 6, 2019
2cf43b8
remove unused file
Aerijo Feb 6, 2019
bd5700b
Merge branch 'master' of github.com:Aerijo/snippets into lsp-snippets
Aerijo Feb 6, 2019
8f022fc
Add variable support
Aerijo Feb 7, 2019
1c42d1b
Hah, pass all specs
Aerijo Feb 7, 2019
6cc9446
Pass editor and cursor to variable resolver
Aerijo Feb 7, 2019
9d32670
remove debugger
Aerijo Feb 7, 2019
c60b3ad
Resolve all the things
Aerijo Feb 7, 2019
4be086c
Fix selection contents & line numbers
Aerijo Feb 7, 2019
4863ca6
remove debugger
Aerijo Feb 7, 2019
139e08a
Apply (some) variable transforms
Aerijo Feb 8, 2019
fc7d227
Refrain from assigning to constant
Aerijo Feb 8, 2019
b9d967a
Take previous advice
Aerijo Feb 8, 2019
f9a2d72
...
Aerijo Feb 8, 2019
7c5cc77
Resolve global replacement properly
Aerijo Feb 8, 2019
5746276
Informative error when failing to transform variable
Aerijo Feb 8, 2019
85107f4
Actually throw an error
Aerijo Feb 8, 2019
c9b2996
general idea
Aerijo Feb 8, 2019
82625e4
Mockup choices presentation
Aerijo Feb 8, 2019
31fe36e
don't save config change
Aerijo Feb 9, 2019
3a29fd9
Refactor
Aerijo Feb 10, 2019
443bd62
remove debugger and pass entire acc to variable resolver
Aerijo Feb 10, 2019
49f7e05
Add default params fallback
Aerijo Feb 10, 2019
6542e76
Add in implicit end stop by default
Aerijo Feb 10, 2019
e002906
Fix implicit end stops & catch transform errors
Aerijo Feb 11, 2019
81dbaa0
rename driver service
Aerijo Feb 11, 2019
c64242e
:art:
Aerijo Feb 11, 2019
b797802
fix resolver service
Aerijo Feb 11, 2019
75a0641
Consistent transform behaviour
Aerijo Feb 11, 2019
ce4d69e
Start messing with history grouping
Aerijo Feb 11, 2019
417dd93
Hack in forced undo barrier
Aerijo Feb 13, 2019
bea05d9
extract to method
Aerijo Feb 13, 2019
b156bb2
Maybe this?
Aerijo Feb 13, 2019
77e8bb1
Use checkpoint instead
Aerijo Feb 13, 2019
30cd9fb
Try more stuff
Aerijo Feb 13, 2019
7cb2c8f
Improvements
Aerijo Feb 13, 2019
b8730af
clean end tabstop logic
Aerijo Feb 13, 2019
0544e51
also make it correct -_-
Aerijo Feb 13, 2019
231e617
Support if/else syntax
Aerijo Feb 13, 2019
c86b630
Fix tabstop position on undo/redo
Aerijo Feb 14, 2019
09d866d
comment the change
Aerijo Feb 14, 2019
cf1441f
Expand and pass around resolver classes
Aerijo Feb 14, 2019
b6bbe9f
remove debugger
Aerijo Feb 14, 2019
80fb5ca
fix typo
Aerijo Feb 17, 2019
a6d94e9
Merge branch 'master' of https://github.com/atom/snippets into lsp-sn…
Aerijo Apr 16, 2019
3c721d7
implicit returns strike again
Aerijo Apr 16, 2019
e7432ed
Style & logic tweaks
Aerijo Apr 16, 2019
dae7f48
use single quotes
Aerijo Apr 16, 2019
df13d06
but not for JSON files
Aerijo Apr 16, 2019
0d8d640
translate snippet tests
Aerijo Apr 17, 2019
ba1bc74
Don't load language-javascript
Aerijo Apr 17, 2019
cea1151
vaguely better undo/redo behaviour
Aerijo Apr 18, 2019
c77cf00
fix
Aerijo Apr 19, 2019
01db360
Remove debugger & tweak gotoPreviousStop
Aerijo Apr 19, 2019
ac06db5
bug fixes
Aerijo Apr 19, 2019
b847db1
Specs and bug fixes
Aerijo Apr 19, 2019
1cb4511
Test misc cases
Aerijo Apr 19, 2019
7934443
remove console log
Aerijo Apr 19, 2019
429a127
start testing snippet body text
Aerijo Apr 19, 2019
5f20163
relax undefined check
Aerijo Apr 20, 2019
43bae26
Make tests more robust and flexible
Aerijo Apr 20, 2019
260d437
more specs
Aerijo Apr 20, 2019
60f3af0
:art:
Aerijo Apr 20, 2019
687b174
fix undo specs
Aerijo Apr 20, 2019
fb4b3e9
Fix yet another undo bug
Aerijo Apr 20, 2019
be3d800
extract transaction logic
Aerijo Apr 20, 2019
9a0e477
don't add implicit endTabStop if already ending with tabstop
Aerijo Jun 18, 2019
d812cf0
Bring the changes from #281 into #288.
savetheclocktower Jun 17, 2019
70ef2f9
remove unused import
Aerijo Jun 18, 2019
77eaef8
Merge branch 'master' of github.com:atom/snippets into lsp-snippets
Aerijo Jun 18, 2019
417da46
add back two removed tests
Aerijo Jun 18, 2019
7f05767
remove unused imports
Aerijo Jun 18, 2019
874aafa
move bundled snippets to the conventional folder
Aerijo Jun 18, 2019
00ffa45
Merge branch 'lsp-snippets' of https://github.com/Aerijo/snippets int…
Aerijo Jul 1, 2019
64606bc
alternate if-else syntax
Aerijo Jul 1, 2019
6222c42
reorder alternate
Aerijo Jul 1, 2019
0deaa1d
adjust spec
Aerijo Jul 1, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 0 additions & 9 deletions lib/editor-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ class EditorStore {
this.editor = editor
this.buffer = this.editor.getBuffer()
this.observer = null
this.checkpoint = null
this.expansions = []
this.existingHistoryProvider = null
}
Expand Down Expand Up @@ -52,14 +51,6 @@ class EditorStore {
this.observer = null
return true
}

makeCheckpoint () {
const existing = this.checkpoint
if (existing) {
this.buffer.groupChangesSinceCheckpoint(existing)
}
this.checkpoint = this.buffer.createCheckpoint()
}
}

EditorStore.store = new WeakMap()
Expand Down
8 changes: 4 additions & 4 deletions lib/helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
/** @babel */
const path = require('path')

import path from 'path'

export function getPackageRoot() {
function getPackageRoot() {
const {resourcePath} = atom.getLoadSettings()
const currentFileWasRequiredFromSnapshot = !path.isAbsolute(__dirname)
if (currentFileWasRequiredFromSnapshot) {
Expand All @@ -11,3 +9,5 @@ export function getPackageRoot() {
return path.resolve(__dirname, '..')
}
}

module.exports = {getPackageRoot}
86 changes: 10 additions & 76 deletions lib/insertion.js
Original file line number Diff line number Diff line change
@@ -1,93 +1,27 @@
const ESCAPES = {
u: (flags) => {
flags.lowercaseNext = false
flags.uppercaseNext = true
},
l: (flags) => {
flags.uppercaseNext = false
flags.lowercaseNext = true
},
U: (flags) => {
flags.lowercaseAll = false
flags.uppercaseAll = true
},
L: (flags) => {
flags.uppercaseAll = false
flags.lowercaseAll = true
},
E: (flags) => {
flags.uppercaseAll = false
flags.lowercaseAll = false
},
r: (flags, result) => {
result.push('\\r')
},
n: (flags, result) => {
result.push('\\n')
},
$: (flags, result) => {
result.push('$')
}
}

function transformText (str, flags) {
if (flags.uppercaseAll) {
return str.toUpperCase()
} else if (flags.lowercaseAll) {
return str.toLowerCase()
} else if (flags.uppercaseNext) {
flags.uppercaseNext = false
return str.replace(/^./, s => s.toUpperCase())
} else if (flags.lowercaseNext) {
return str.replace(/^./, s => s.toLowerCase())
}
return str
}
const {transformWithSubstitution} = require('./util')

class Insertion {
constructor ({ range, substitution }) {
constructor ({range, substitution, references, choices=[], transformResolver}) {
this.range = range
this.substitution = substitution
if (substitution) {
if (substitution.replace === undefined) {
substitution.replace = ''
}
this.replacer = this.makeReplacer(substitution.replace)
this.references = references
if (substitution && substitution.replace === undefined) {
substitution.replace = ''
}
this.choices = choices
this.transformResolver = transformResolver
}

isTransformation () {
return !!this.substitution
}

makeReplacer (replace) {
return function replacer (...match) {
let flags = {
uppercaseAll: false,
lowercaseAll: false,
uppercaseNext: false,
lowercaseNext: false
}
replace = [...replace]
let result = []
replace.forEach(token => {
if (typeof token === 'string') {
result.push(transformText(token, flags))
} else if (token.escape) {
ESCAPES[token.escape](flags, result)
} else if (token.backreference) {
let transformed = transformText(match[token.backreference], flags)
result.push(transformed)
}
})
return result.join('')
}
isChoice () {
return this.choices.length > 0
}

transform (input) {
let { substitution } = this
if (!substitution) { return input }
return input.replace(substitution.find, this.replacer)
return transformWithSubstitution(input, this.substitution, this.transformResolver)
}
}

Expand Down
223 changes: 223 additions & 0 deletions lib/resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
const path = require('path')


class ValueResolver {
constructor (resolvers = new Map) {
this.resolvers = resolvers
}

add (varName, resolver) {
this.resolvers.set(varName, resolver)
}

/*
Params depend on context. VariableResolver can expect the following, but TransformResolver will likely get a restricted number
(VariableResolver) params = {
variable: the variable name this was called with
editor: the TextEditor we are expanding in
cursor: the cursor we are expanding from
indent: the indent of the original cursor line. Automatically applied to all text (post variable transformation)
selectionRange: the original selection range of the cursor. This has been modified on the actual cursor to now select the prefix
startPosition: the cursor selection start position, after being adjusted to select the prefix
row: the row the start of the variable will be inserted on (final) <- final for snippet body creation. Does not account for changes when the user starts typing
column: the column the start of the variable will be inserted on (final; accounts for indent)
}

(TransformResolver) params = {
input: the text to be transformed
transform: the transform this was called with
}
*/
resolve (name, params) {
const resolver = this.resolvers.get(name)
if (resolver) {
return {
hasResolver: true,
value: resolver(params)
}
}
return {
hasResolver: false,
value: undefined
}
}
}

class VariableResolver extends ValueResolver {
constructor (resolvers = new Map) {
super(new Map([
['CLIPBOARD', resolveClipboard],

['TM_SELECTED_TEXT', resolveSelected],
['TM_CURRENT_LINE', resolveCurrentLine],
['TM_CURRENT_WORD', resolveCurrentWord],
['TM_LINE_INDEX', resolveLineIndex],
['TM_LINE_NUMBER', resolveLineNumber],
['TM_FILENAME', resolveFileName],
['TM_FILENAME_BASE', resolveFileNameBase],
['TM_DIRECTORY', resolveFileDirectory],
['TM_FILEPATH', resolveFilePath],

['CURRENT_YEAR', resolveYear],
['CURRENT_YEAR_SHORT', resolveYearShort],
['CURRENT_MONTH', resolveMonth],
['CURRENT_MONTH_NAME', resolveMonthName],
['CURRENT_MONTH_NAME_SHORT', resolveMonthNameShort],
['CURRENT_DATE', resolveDate],
['CURRENT_DAY_NAME', resolveDayName],
['CURRENT_DAY_NAME_SHORT', resolveDayNameShort],
['CURRENT_HOUR', resolveHour],
['CURRENT_MINUTE', resolveMinute],
['CURRENT_SECOND', resolveSecond],

['BLOCK_COMMENT_START', resolveBlockCommentStart],
['BLOCK_COMMENT_END', resolveBlockCommentEnd],
['LINE_COMMENT', resolveLineComment],

...resolvers
]))
}
}

function resolveClipboard () {
return atom.clipboard.read()
}

function resolveSelected ({editor, selectionRange}) {
if (!selectionRange || selectionRange.isEmpty()) return undefined
return editor.getTextInBufferRange(selectionRange)
}

function resolveCurrentLine ({editor, cursor}) {
return editor.lineTextForBufferRow(cursor.getBufferRow())
}

function resolveCurrentWord ({editor, cursor}) {
return editor.getTextInBufferRange(cursor.getCurrentWordBufferRange())
}

function resolveLineIndex ({cursor}) {
return `${cursor.getBufferRow()}`
}

function resolveLineNumber ({cursor}) {
return `${cursor.getBufferRow() + 1}`
}

function resolveFileName ({editor}) {
return editor.getTitle()
}

function resolveFileNameBase ({editor}) {
const fileName = resolveFileName({editor})
if (!fileName) { return undefined }

const index = fileName.lastIndexOf('.')
if (index >= 0) {
return fileName.slice(0, index)
}

return fileName
}

function resolveFileDirectory ({editor}) {
const filePath = editor.getPath()
if (filePath === undefined) return undefined
return path.dirname(filePath)
}

function resolveFilePath ({editor}) {
return editor.getPath()
}


// TODO: Use correct locale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is VSCode's behavior here? I can't think of anyplace in Atom that envisions non-English locales, but I'm curious about what would need to happen for this TODO to be done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really, I have no idea how any locale stuff works. So that was to make sure any final result is intentional, not accidental

function resolveYear () {
return new Date().toLocaleString('en-us', {year: 'numeric'})
}

function resolveYearShort () { // last two digits of year
return new Date().toLocaleString('en-us', {year: '2-digit'})
}

function resolveMonth () {
return new Date().toLocaleString('en-us', {month: '2-digit'})
}

function resolveMonthName () {
return new Date().toLocaleString('en-us', {month: 'long'})
}

function resolveMonthNameShort () {
return new Date().toLocaleString('en-us', {month: 'short'})
}

function resolveDate () {
return new Date().toLocaleString('en-us', {day: '2-digit'})
}

function resolveDayName () {
return new Date().toLocaleString('en-us', {weekday: 'long'})
}

function resolveDayNameShort () {
return new Date().toLocaleString('en-us', {weekday: 'short'})
}

function resolveHour () {
return new Date().toLocaleString('en-us', {hour12: false, hour: '2-digit'})
}

function resolveMinute () {
return new Date().toLocaleString('en-us', {minute: '2-digit'})
}

function resolveSecond () {
return new Date().toLocaleString('en-us', {second: '2-digit'})
}

// TODO: wait for https://github.com/atom/atom/issues/18812
// Could make a start with what we have; one of the two should be available
function getEditorCommentStringsForPoint (_editor, _point) {
return {line: '//', start: '/*', end: '*/'}
}

function resolveBlockCommentStart ({editor, cursor}) {
const delims = getEditorCommentStringsForPoint(editor, cursor.getBufferPosition())
return delims.start
}

function resolveBlockCommentEnd ({editor, cursor}) {
const delims = getEditorCommentStringsForPoint(editor, cursor.getBufferPosition())
return delims.end
}

function resolveLineComment ({editor, cursor}) {
const delims = getEditorCommentStringsForPoint(editor, cursor.getBufferPosition())
return delims.line
}

class TransformResolver extends ValueResolver {
constructor (resolvers = new Map) {
super(new Map([
['upcase', transformUpcase],
['downcase', transformDowncase],
['capitalize', transformCapitalize],
...resolvers
]))
}
}

function transformUpcase ({input}) {
return input.toLocaleUpperCase()
}

function transformDowncase ({input}) {
return input.toLocaleLowerCase()
}

function transformCapitalize ({input}) {
return input ? input[0].toLocaleUpperCase() + input.substr(1) : ''
}

module.exports = { ValueResolver, VariableResolver, TransformResolver }