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/scopes #728

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 42 additions & 21 deletions lib/misc/scopes.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
/** @babel */

import { Point, Range } from 'atom'

const juliaScopes = ['source.julia', 'source.embedded.julia']
const openers = [
'if', 'while', 'for', 'begin', 'function', 'macro', 'module', 'baremodule', 'type', 'immutable',
'struct', 'mutable struct', 'try', 'let', 'do', 'quote', 'abstract type', 'primitive type'
]
const reopeners = [ 'else', 'elseif', 'catch', 'finally' ]
const reopeners = ['else', 'elseif', 'catch', 'finally']

function isKeywordScope (scopes) {
/**
*
* @param {readonly string[]} scopes
*/
function isKeywordScope(scopes) {
// Skip 'source.julia'
return scopes.slice(1).some(scope => {
return scopes.slice(1).some((scope) => {
return scope.indexOf('keyword') > -1
})
}

export function isStringScope (scopes) {
/**
*
* @param {readonly string[]} scopes
*/
export function isStringScope(scopes) {
let isString = false
let isInterp = false
for (const scope of scopes) {
Expand All @@ -30,19 +36,22 @@ export function isStringScope (scopes) {
return isString && !isInterp
}

function forRange (editor, range) {
/**
*
* @param {TextEditor} editor
* @param {RangeCompatible} range
*/
function forRange(editor, range) {
// this should happen here and not a top-level so that we aren't relying on
// Atom to load packages in a specific order:
const juliaGrammar = atom.grammars.grammarForScopeName('source.julia')

if (juliaGrammar === undefined) return []

const scopes = []
let n_parens = 0
let n_brackets = 0
const text = editor.getTextInBufferRange(range)
juliaGrammar.tokenizeLines(text).forEach(lineTokens => {
lineTokens.forEach(token => {
juliaGrammar.tokenizeLines(text).forEach((lineTokens) => {
lineTokens.forEach((token) => {
const { value } = token
if (!isStringScope(token.scopes)) {
if (n_parens > 0 && value === ')') {
Expand All @@ -63,9 +72,8 @@ function forRange (editor, range) {
return
}
}
if (!(isKeywordScope(token.scopes))) return
if (!isKeywordScope(token.scopes)) return
if (!(n_parens === 0 && n_brackets === 0)) return

const reopen = reopeners.includes(value)
if (value === 'end' || reopen) scopes.pop()
if (openers.includes(value) || reopen) scopes.push(value)
Expand All @@ -74,16 +82,26 @@ function forRange (editor, range) {
return scopes
}

export function forLines (editor, start, end) {
/**
*
* @param {TextEditor} editor
* @param {number} start
* @param {number} end
*/
export function forLines(editor, start, end) {
const startPoint = new Point(start, 0)
const endPoint = new Point(end, Infinity)
const range = new Range(startPoint, endPoint)
return forRange(editor, range)
}

export function isCommentScope (scopes) {
/**
*
* @param {readonly string[]} scopes
*/
export function isCommentScope(scopes) {
// Skip 'source.julia'
return scopes.slice(1).some(scope => {
return scopes.slice(1).some((scope) => {
return scope.indexOf('comment') > -1
})
}
Expand All @@ -92,14 +110,17 @@ export function isCommentScope (scopes) {
* Returns `true` if the scope at `bufferPosition` in `editor` is valid code scope to be inspected.
* Supposed to be used within Atom-IDE integrations, whose `grammarScopes` setting doesn't support
* embedded scopes by default.
*
* @param {TextEditor} editor
* @param {PointCompatible} bufferPosition
*/
export function isValidScopeToInspect (editor, bufferPosition) {
export function isValidScopeToInspect(editor, bufferPosition) {
const scopes = editor
.scopeDescriptorForBufferPosition(bufferPosition)
.getScopesArray()
return scopes.some(scope => {
return scopes.some((scope) => {
return juliaScopes.includes(scope)
}) ?
!isCommentScope(scopes) && !isStringScope(scopes) :
false
})
? !isCommentScope(scopes) && !isStringScope(scopes)
: false
}
145 changes: 145 additions & 0 deletions lib_src/misc/scopes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/** @babel */

import { Point, Range, RangeCompatible, TextEditor, PointCompatible } from "atom"

const juliaScopes = ["source.julia", "source.embedded.julia"]
const openers = [
"if",
"while",
"for",
"begin",
"function",
"macro",
"module",
"baremodule",
"type",
"immutable",
"struct",
"mutable struct",
"try",
"let",
"do",
"quote",
"abstract type",
"primitive type"
]
const reopeners = ["else", "elseif", "catch", "finally"]

/**
*
* @param {readonly string[]} scopes
*/
function isKeywordScope(scopes: readonly string[]) {
// Skip 'source.julia'
return scopes.slice(1).some(scope => {
return scope.indexOf("keyword") > -1
})
}

/**
*
* @param {readonly string[]} scopes
*/
export function isStringScope(scopes: readonly string[]) {
let isString = false
let isInterp = false
for (const scope of scopes) {
if (scope.indexOf("string") > -1) {
isString = true
}
if (scope.indexOf("interpolation") > -1) {
isInterp = true
}
}
return isString && !isInterp
}

/**
*
* @param {TextEditor} editor
* @param {RangeCompatible} range
*/
function forRange(editor: TextEditor, range: RangeCompatible) {
// this should happen here and not a top-level so that we aren't relying on
// Atom to load packages in a specific order:
const juliaGrammar = atom.grammars.grammarForScopeName("source.julia")

if (juliaGrammar === undefined) return []

const scopes: string[] = []
let n_parens = 0
let n_brackets = 0
const text = editor.getTextInBufferRange(range)
juliaGrammar.tokenizeLines(text).forEach(lineTokens => {
lineTokens.forEach(token => {
const { value } = token
if (!isStringScope(token.scopes)) {
if (n_parens > 0 && value === ")") {
n_parens -= 1
scopes.splice(scopes.lastIndexOf("paren"), 1)
return
} else if (n_brackets > 0 && value === "]") {
n_brackets -= 1
scopes.splice(scopes.lastIndexOf("bracket"), 1)
return
} else if (value === "(") {
n_parens += 1
scopes.push("paren")
return
} else if (value === "[") {
n_brackets += 1
scopes.push("bracket")
return
}
}
if (!isKeywordScope(token.scopes)) return
if (!(n_parens === 0 && n_brackets === 0)) return

const reopen = reopeners.includes(value)
if (value === "end" || reopen) scopes.pop()
if (openers.includes(value) || reopen) scopes.push(value)
})
})
return scopes
}

/**
*
* @param {TextEditor} editor
* @param {number} start
* @param {number} end
*/
export function forLines(editor: TextEditor, start: number, end: number) {
const startPoint = new Point(start, 0)
const endPoint = new Point(end, Infinity)
const range = new Range(startPoint, endPoint)
return forRange(editor, range)
}

/**
*
* @param {readonly string[]} scopes
*/
export function isCommentScope(scopes: readonly string[]) {
// Skip 'source.julia'
return scopes.slice(1).some(scope => {
return scope.indexOf("comment") > -1
})
}

/**
* Returns `true` if the scope at `bufferPosition` in `editor` is valid code scope to be inspected.
* Supposed to be used within Atom-IDE integrations, whose `grammarScopes` setting doesn't support
* embedded scopes by default.
*
* @param {TextEditor} editor
* @param {PointCompatible} bufferPosition
*/
export function isValidScopeToInspect(editor: TextEditor, bufferPosition: PointCompatible) {
const scopes = editor.scopeDescriptorForBufferPosition(bufferPosition).getScopesArray()
return scopes.some(scope => {
return juliaScopes.includes(scope)
})
? !isCommentScope(scopes) && !isStringScope(scopes)
: false
}