Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc/blocks #727

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d81d5e7
Add @types + tslint devDependencies
aminya Mar 15, 2020
a144b31
Create tsconfig.json
aminya Mar 15, 2020
8fa91ad
Create tslint.json
aminya Mar 15, 2020
7db483e
Adding prettier
aminya Mar 15, 2020
1982483
Add Eslint
aminya Mar 16, 2020
b61e7b7
Use GithubActions instead of Travis/Appveyor
aminya Mar 16, 2020
cdea936
Adding Scripts
aminya Mar 19, 2020
20e34ab
Update package-lock.json
aminya Mar 22, 2020
f51d8fc
gitignore + Local conversion:
aminya Mar 29, 2020
7fc7bfc
Update package.json
aminya Apr 13, 2020
8ec60c8
Copy misc/blocks to lib_src
aminya Mar 29, 2020
7058d61
js-to-ts-converter
aminya Mar 29, 2020
8afd835
blocks in in gitignore, tsconfig, package
aminya Mar 29, 2020
105a627
ES6 tesconfig
aminya Apr 13, 2020
d908c85
prettier_src
aminya Mar 29, 2020
620118c
ed -> editor
aminya Mar 29, 2020
57fe8e9
sel -> selection
aminya Mar 29, 2020
d0ff519
editor: TextEditor
aminya Mar 29, 2020
c808056
selection: Selection
aminya Mar 29, 2020
2b0c922
LineInfo interface
aminya Mar 29, 2020
49a474f
getScopesArray() instead of .scopes
aminya Mar 29, 2020
2822096
getLine types
aminya Mar 29, 2020
4b44978
isBlank types
aminya Mar 29, 2020
c40a78f
isEnd types + using lineInfo.line
aminya Mar 29, 2020
33d97af
isStringEnd types + lineInfo.scope
aminya Mar 29, 2020
8d42c5b
isCont types + lineInfo.scope + lineInfo.line
aminya Mar 29, 2020
c1b9b44
isStart types
aminya Mar 29, 2020
1c73461
walkBack types
aminya Mar 29, 2020
7ea55cb
walkForward types
aminya Mar 29, 2020
e85798f
getRange types
aminya Mar 29, 2020
6e2f471
getRange undefined return in other path
aminya Mar 29, 2020
1afa561
moveNext types
aminya Mar 29, 2020
5fb7501
getHeadBufferPosition todo
aminya Mar 29, 2020
2e6d3d6
getLocalContext types
aminya Mar 29, 2020
1196b8d
redundant escape in regex
aminya Mar 29, 2020
ce68b3a
TODO Fix RangeCompatible types
aminya Mar 29, 2020
8a492b6
add types to doctrings
aminya Apr 14, 2020
6e602af
npm run build
aminya Apr 14, 2020
cbc897e
Remove TypeScript footprint
aminya Apr 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 157 additions & 60 deletions lib/misc/blocks.js
Original file line number Diff line number Diff line change
@@ -1,71 +1,118 @@
'use babel'
// TODO: docstrings
// TODO: Fix RangeCompatible types

import { forLines } from './scopes'
/**
* interface LineInfo {
* scope: readonly string[]
* line: string
* }
*/

export function getLine (ed, l) {

/**
*
* @param {TextEditor} editor
* @param {number} l
* @returns {LineInfo}
*/
export function getLine(editor, l) {
return {
scope: ed.scopeDescriptorForBufferPosition([l, 0]).scopes,
line: ed.getTextInBufferRange([[l, 0], [l, Infinity]])
scope: editor.scopeDescriptorForBufferPosition([l, 0]).getScopesArray(),
line: editor.getTextInBufferRange([
[l, 0],
[l, Infinity],
]),
}
}

function isBlank ({line, scope}, allowDocstrings = false) {
/**
*
* @param {LineInfo.line} line
* @param {LineInfo.scope} scope
* @param {boolean} allowDocstrings
*/
function isBlank({ line, scope }, allowDocstrings = false) {
for (const s of scope) {
if (/\bcomment\b/.test(s) || (!allowDocstrings && /\bdocstring\b/.test(s))) {
return true
}
}
return /^\s*(#.*)?$/.test(line)
}
function isEnd ({ line, scope }) {
if (isStringEnd({ line, scope })) {

/**
*
* @param {LineInfo} lineInfo
*/
function isEnd(lineInfo) {
if (isStringEnd(lineInfo)) {
return true
}
return /^(end\b|\)|\]|\})/.test(line)
return /^(end\b|\)|]|})/.test(lineInfo.line)
}
function isStringEnd ({ line, scope }) {
scope = scope.join(' ')
return /\bstring\.multiline\.end\b/.test(scope) ||
(/\bstring\.end\b/.test(scope) && /\bbacktick\b/.test(scope))

/**
*
* @param {LineInfo} lineInfo
*/
function isStringEnd(lineInfo) {
const scope = lineInfo.scope.join(' ')
return /\bstring\.multiline\.end\b/.test(scope) || (/\bstring\.end\b/.test(scope) && /\bbacktick\b/.test(scope))
}
function isCont ({ line, scope }) {
scope = scope.join(' ')
if (/\bstring\b/.test(scope) && !(/\bpunctuation\.definition\.string\b/.test(scope))) {

/**
*
* @param {LineInfo} lineInfo
*/
function isCont(lineInfo) {
const scope = lineInfo.scope.join(' ')
if (/\bstring\b/.test(scope) && !/\bpunctuation\.definition\.string\b/.test(scope)) {
return true
}

return line.match(/^(else|elseif|catch|finally)\b/)
return lineInfo.line.match(/^(else|elseif|catch|finally)\b/)
}
function isStart (lineInfo) {

/**
*
* @param {LineInfo} lineInfo
*/
function isStart(lineInfo) {
return !(/^\s/.test(lineInfo.line) || isBlank(lineInfo) || isEnd(lineInfo) || isCont(lineInfo))
}

function walkBack(ed, row) {
while ((row > 0) && !isStart(getLine(ed, row))) {
/**
*
* @param {TextEditor} editor
* @param {number} row
*/
function walkBack(editor, row) {
while (row > 0 && !isStart(getLine(editor, row))) {
row--
}
return row
}

function walkForward (ed, start) {
/**
*
* @param {TextEditor} editor
* @param {number} start
*/
function walkForward(editor, start) {
let end = start
let mark = start
while (mark < ed.getLastBufferRow()) {
while (mark < editor.getLastBufferRow()) {
mark++
const lineInfo = getLine(ed, mark)

const lineInfo = getLine(editor, mark)
if (isStart(lineInfo)) {
break
}
if (isEnd(lineInfo)) {
// An `end` only counts when there still are unclosed blocks (indicated by `forLines`
// returning a non-empty array).
// If the line closes a multiline string we also take that as ending the block.
if (
!(forLines(ed, start, mark-1).length === 0) ||
isStringEnd(lineInfo)
) {
if (!(forLines(editor, start, mark - 1).length === 0) || isStringEnd(lineInfo)) {
end = mark
}
} else if (!(isBlank(lineInfo) || isStart(lineInfo))) {
Expand All @@ -75,74 +122,119 @@ function walkForward (ed, start) {
return end
}

function getRange (ed, row) {
const start = walkBack(ed, row)
const end = walkForward(ed, start)
/**
*
* @param {TextEditor} editor
* @param {number} row
* @returns {[[number, number], [number, number]] | undefined}
*/
function getRange(editor, row) {
const start = walkBack(editor, row)
const end = walkForward(editor, start)
if (start <= row && row <= end) {
return [[start, 0], [end, Infinity]]
return [
[start, 0],
[end, Infinity],
]
} else {
return undefined // TODO: make sure returned range from getRanges is not undefined
}
}

function getSelection (ed, sel) {
const {start, end} = sel.getBufferRange()
const range = [[start.row, start.column], [end.row, end.column]]
while (isBlank(getLine(ed, range[0][0]), true) && (range[0][0] <= range[1][0])) {
/**
*
* @param {TextEditor} editor
* @param {Selection} selection
*/
function getSelection(editor, selection) {
const { start, end } = selection.getBufferRange()
const range = [
[start.row, start.column],
[end.row, end.column],
]
while (isBlank(getLine(editor, range[0][0]), true) && range[0][0] <= range[1][0]) {
range[0][0]++
range[0][1] = 0
}
while (isBlank(getLine(ed, range[1][0]), true) && (range[1][0] >= range[0][0])) {
while (isBlank(getLine(editor, range[1][0]), true) && range[1][0] >= range[0][0]) {
range[1][0]--
range[1][1] = Infinity
}
return range
}

export function moveNext (ed, sel, range) {
/**
*
* @param {TextEditor} editor
* @param {Selection} selection
* @param {[[number, number], [number, number]]} range
*/
export function moveNext(editor, selection, range) {
// Ensure enough room at the end of the buffer
const row = range[1][0]
let last
while ((last = ed.getLastBufferRow()) < (row+2)) {
if ((last !== row) && !isBlank(getLine(ed, last))) {
while ((last = editor.getLastBufferRow()) < row + 2) {
if (last !== row && !isBlank(getLine(editor, last))) {
break
}
sel.setBufferRange([[last, Infinity], [last, Infinity]])
sel.insertText('\n')
selection.setBufferRange([
[last, Infinity],
[last, Infinity],
])
selection.insertText('\n')
}
// Move the cursor
let to = row + 1
while ((to < ed.getLastBufferRow()) && isBlank(getLine(ed, to))) {
while (to < editor.getLastBufferRow() && isBlank(getLine(editor, to))) {
to++
}
to = walkForward(ed, to)
return sel.setBufferRange([[to, Infinity], [to, Infinity]])
to = walkForward(editor, to)
return selection.setBufferRange([
[to, Infinity],
[to, Infinity],
])
}

function getRanges (ed) {
const ranges = ed.getSelections().map(sel => {
/**
*
* @param {TextEditor} editor
*/
function getRanges(editor) {
const ranges = editor.getSelections().map((selection) => {
return {
selection: sel,
range: sel.isEmpty() ?
getRange(ed, sel.getHeadBufferPosition().row) :
getSelection(ed, sel)
selection: selection,
range: selection.isEmpty()
? getRange(editor, selection.getHeadBufferPosition().row)
: getSelection(editor, selection),
}
// TODO: replace with getBufferRowRange? (getHeadBufferPosition isn't a public API)
})
return ranges.filter(({ range }) => {
return range && ed.getTextInBufferRange(range).trim()
return range && editor.getTextInBufferRange(range).trim()
})
}

export function get (ed) {
return getRanges(ed).map(({ range, selection }) => {
/**
*
* @param {TextEditor} editor
*/
export function get(editor) {
return getRanges(editor).map(({ range, selection }) => {
return {
range,
selection,
line: range[0][0],
text: ed.getTextInBufferRange(range)
text: editor.getTextInBufferRange(range),
}
})
}

export function getLocalContext (editor, row) {
/**
*
* @param {TextEditor} editor
* @param {number} row
*/
export function getLocalContext(editor, row) {
const range = getRange(editor, row)
const context = range ? editor.getTextInBufferRange(range) : ''
// NOTE:
Expand All @@ -152,16 +244,21 @@ export function getLocalContext (editor, row) {
const startRow = range ? range[0][0] : 0
return {
context,
startRow
startRow,
}
}

export function select (ed = atom.workspace.getActiveTextEditor()) {
if (!ed) return
return ed.mutateSelectedText(selection => {
const range = getRange(ed, selection.getHeadBufferPosition().row)
/**
*
* @param {TextEditor | undefined} editor
*/
export function select(editor = atom.workspace.getActiveTextEditor()) {
if (!editor) return
return editor.mutateSelectedText((selection) => {
const range = getRange(editor, selection.getHeadBufferPosition().row)
if (range) {
selection.setBufferRange(range)
}
})
// TODO: replace with getBufferRowRange? (getHeadBufferPosition isn't a public API)
}