Fix autocomplete for multi-word names, and minor tweaks. Add testing for CodeMirror integration [#34]
This commit is contained in:
parent
ece615e2a0
commit
64e3f6aa03
|
@ -626,32 +626,44 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
const end = {type: '', suggest: '\n', then: {}};
|
const end = {type: '', suggest: '\n', then: {}};
|
||||||
const hiddenEnd = {type: '', then: {}};
|
const hiddenEnd = {type: '', then: {}};
|
||||||
|
|
||||||
function textTo(exit) {
|
function textTo(exit, suggest) {
|
||||||
return {type: 'string', then: Object.assign({'': 0}, exit)};
|
return {
|
||||||
|
type: 'string',
|
||||||
|
suggest,
|
||||||
|
then: Object.assign({'': 0}, exit),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const textToEnd = textTo({'\n': end});
|
const textToEnd = textTo({'\n': end});
|
||||||
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
const aliasListToEnd = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
'\n': end,
|
'\n': end,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||||
'as': {type: 'keyword', suggest: true, then: {
|
'as': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Agent', then: {
|
'': {type: 'variable', suggest: {known: 'Agent'}, then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 3}},
|
',': {type: 'operator', suggest: true, then: {'': 3}},
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
}};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function agentListTo(exit) {
|
function agentListTo(exit) {
|
||||||
return {type: 'variable', suggest: 'Agent', then: Object.assign({},
|
return {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: Object.assign({},
|
||||||
exit,
|
exit,
|
||||||
{
|
{
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||||
}
|
}
|
||||||
)};
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const colonTextToEnd = {
|
const colonTextToEnd = {
|
||||||
|
@ -662,32 +674,50 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
const agentListToText = agentListTo({
|
const agentListToText = agentListTo({
|
||||||
':': colonTextToEnd,
|
':': colonTextToEnd,
|
||||||
});
|
});
|
||||||
const agentList2ToText = {type: 'variable', suggest: 'Agent', then: {
|
const agentList2ToText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': agentListToText}},
|
',': {type: 'operator', suggest: true, then: {
|
||||||
|
'': agentListToText,
|
||||||
|
}},
|
||||||
':': CM_ERROR,
|
':': CM_ERROR,
|
||||||
}};
|
},
|
||||||
const singleAgentToText = {type: 'variable', suggest: 'Agent', then: {
|
};
|
||||||
|
const singleAgentToText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': CM_ERROR,
|
',': CM_ERROR,
|
||||||
':': colonTextToEnd,
|
':': colonTextToEnd,
|
||||||
}};
|
},
|
||||||
const agentToOptText = {type: 'variable', suggest: 'Agent', then: {
|
};
|
||||||
|
const agentToOptText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
':': {type: 'operator', suggest: true, then: {
|
':': {type: 'operator', suggest: true, then: {
|
||||||
'': textToEnd,
|
'': textToEnd,
|
||||||
'\n': hiddenEnd,
|
'\n': hiddenEnd,
|
||||||
}},
|
}},
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}};
|
},
|
||||||
|
};
|
||||||
const referenceName = {
|
const referenceName = {
|
||||||
':': {type: 'operator', suggest: true, then: {
|
':': {type: 'operator', suggest: true, then: {
|
||||||
'': textTo({
|
'': textTo({
|
||||||
'as': {type: 'keyword', suggest: true, then: {
|
'as': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Agent', then: {
|
'': {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
}),
|
}),
|
||||||
}},
|
}},
|
||||||
|
@ -774,7 +804,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
then: {},
|
then: {},
|
||||||
};
|
};
|
||||||
return makeOpBlock(
|
return makeOpBlock(
|
||||||
{type: 'variable', suggest: 'Agent', then},
|
{type: 'variable', suggest: {known: 'Agent'}, then},
|
||||||
then
|
then
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -868,12 +898,16 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}},
|
}},
|
||||||
'autolabel': {type: 'keyword', suggest: true, then: {
|
'autolabel': {type: 'keyword', suggest: true, then: {
|
||||||
'off': {type: 'keyword', suggest: true, then: {}},
|
'off': {type: 'keyword', suggest: true, then: {}},
|
||||||
'': textToEnd,
|
'': textTo({'\n': end}, [
|
||||||
|
{v: '<label>', suffix: '\n', q: true},
|
||||||
|
{v: '[<inc>] <label>', suffix: '\n', q: true},
|
||||||
|
{v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true},
|
||||||
|
]),
|
||||||
}},
|
}},
|
||||||
'simultaneously': {type: 'keyword', suggest: true, then: {
|
'simultaneously': {type: 'keyword', suggest: true, then: {
|
||||||
':': {type: 'operator', suggest: true, then: {}},
|
':': {type: 'operator', suggest: true, then: {}},
|
||||||
'with': {type: 'keyword', suggest: true, then: {
|
'with': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Label', then: {
|
'': {type: 'variable', suggest: {known: 'Label'}, then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
':': {type: 'operator', suggest: true, then: {}},
|
':': {type: 'operator', suggest: true, then: {}},
|
||||||
}},
|
}},
|
||||||
|
@ -897,31 +931,27 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmGetVarSuggestions(state, previous, current) {
|
function cmGetSuggestions(state, token, current) {
|
||||||
if(typeof current.suggest === 'object' && current.suggest.global) {
|
let suggestions = current.suggest;
|
||||||
return [current.suggest];
|
if(!Array.isArray(suggestions)) {
|
||||||
}
|
suggestions = [suggestions];
|
||||||
if(
|
|
||||||
typeof current.suggest !== 'string' ||
|
|
||||||
previous.suggest === current.suggest
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return state['known' + current.suggest];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmGetSuggestions(state, token, previous, current) {
|
return array.flatMap(suggestions, (suggest) => {
|
||||||
if(token === '') {
|
if(suggest === true) {
|
||||||
return cmGetVarSuggestions(state, previous, current);
|
|
||||||
} else if(current.suggest === true) {
|
|
||||||
return [cmCappedToken(token, current)];
|
return [cmCappedToken(token, current)];
|
||||||
} else if(Array.isArray(current.suggest)) {
|
} else if(typeof suggest === 'object') {
|
||||||
return current.suggest.map((v) => ({v, q: false}));
|
if(suggest.known) {
|
||||||
} else if(current.suggest) {
|
return state['known' + suggest.known] || [];
|
||||||
return [{v: current.suggest, q: false}];
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return [suggest];
|
||||||
}
|
}
|
||||||
|
} else if(typeof suggest === 'string' && suggest) {
|
||||||
|
return [{v: suggest, q: (token === '')}];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmMakeCompletions(state, path) {
|
function cmMakeCompletions(state, path) {
|
||||||
|
@ -934,7 +964,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
comp,
|
comp,
|
||||||
cmGetSuggestions(state, token, current, next),
|
cmGetSuggestions(state, token, next),
|
||||||
suggestionsEqual
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -942,8 +972,11 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSuggestion(state, locals, token, {suggest, override}) {
|
function updateSuggestion(state, locals, token, {suggest, override}) {
|
||||||
if(locals.type) {
|
let known = null;
|
||||||
if(suggest !== locals.type) {
|
if(typeof suggest === 'object' && suggest.known) {
|
||||||
|
known = suggest.known;
|
||||||
|
}
|
||||||
|
if(locals.type && known !== locals.type) {
|
||||||
if(override) {
|
if(override) {
|
||||||
locals.type = override;
|
locals.type = override;
|
||||||
}
|
}
|
||||||
|
@ -955,9 +988,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
locals.type = '';
|
locals.type = '';
|
||||||
locals.value = '';
|
locals.value = '';
|
||||||
}
|
}
|
||||||
}
|
if(known) {
|
||||||
if(typeof suggest === 'string' && state['known' + suggest]) {
|
locals.type = known;
|
||||||
locals.type = suggest;
|
|
||||||
if(locals.value) {
|
if(locals.value) {
|
||||||
locals.value += token.s;
|
locals.value += token.s;
|
||||||
}
|
}
|
||||||
|
@ -978,7 +1010,13 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
state.completions = cmMakeCompletions(state, path);
|
state.completions = cmMakeCompletions(state, path);
|
||||||
}
|
}
|
||||||
const keywordToken = token.q ? '' : token.v;
|
const keywordToken = token.q ? '' : token.v;
|
||||||
const found = current.then[keywordToken] || current.then[''];
|
let found = current.then[keywordToken];
|
||||||
|
if(found === undefined) {
|
||||||
|
found = current.then[''];
|
||||||
|
state.isVar = true;
|
||||||
|
} else {
|
||||||
|
state.isVar = token.q;
|
||||||
|
}
|
||||||
if(typeof found === 'number') {
|
if(typeof found === 'number') {
|
||||||
path.length -= found;
|
path.length -= found;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1025,6 +1063,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
completions: [],
|
completions: [],
|
||||||
nextCompletions: [],
|
nextCompletions: [],
|
||||||
valid: true,
|
valid: true,
|
||||||
|
isVar: true,
|
||||||
line: [],
|
line: [],
|
||||||
indent: 0,
|
indent: 0,
|
||||||
};
|
};
|
||||||
|
@ -1119,6 +1158,7 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
token(stream, state) {
|
token(stream, state) {
|
||||||
state.completions = state.nextCompletions;
|
state.completions = state.nextCompletions;
|
||||||
|
state.isVar = true;
|
||||||
if(stream.sol() && state.currentType === -1) {
|
if(stream.sol() && state.currentType === -1) {
|
||||||
state.line.length = 0;
|
state.line.length = 0;
|
||||||
}
|
}
|
||||||
|
@ -5646,7 +5686,8 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
const TRIMMER = /^([ \t]*)(.*)$/;
|
const TRIMMER = /^([ \t]*)(.*)$/;
|
||||||
const SQUASH_START = /^[ \t\r\n:,]/;
|
const SQUASH_START = /^[ \t\r\n:,]/;
|
||||||
const SQUASH_END = /[ \t\r\n]$/;
|
const SQUASH_END = /[ \t\r\n]$/;
|
||||||
const REQUIRED_QUOTED = /[\r\n:,"]/;
|
const ONGOING_QUOTE = /^"(\\.|[^"])*$/;
|
||||||
|
const REQUIRED_QUOTED = /[\r\n:,"<>\-~]/;
|
||||||
const QUOTE_ESCAPE = /["\\]/g;
|
const QUOTE_ESCAPE = /["\\]/g;
|
||||||
|
|
||||||
function suggestionsEqual(a, b) {
|
function suggestionsEqual(a, b) {
|
||||||
|
@ -5658,29 +5699,36 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRanges(cm, line, chFrom, chTo) {
|
function makeRangeFrom(cm, line, chFrom) {
|
||||||
const ln = cm.getLine(line);
|
const ln = cm.getLine(line);
|
||||||
const ranges = {
|
const ranges = {
|
||||||
wordFrom: {line: line, ch: chFrom},
|
word: {line: line, ch: chFrom},
|
||||||
squashFrom: {line: line, ch: chFrom},
|
squash: {line: line, ch: chFrom},
|
||||||
wordTo: {line: line, ch: chTo},
|
|
||||||
squashTo: {line: line, ch: chTo},
|
|
||||||
};
|
};
|
||||||
if(chFrom > 0 && ln[chFrom - 1] === ' ') {
|
if(chFrom > 0 && ln[chFrom - 1] === ' ') {
|
||||||
ranges.squashFrom.ch --;
|
ranges.squash.ch --;
|
||||||
}
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRangeTo(cm, line, chTo) {
|
||||||
|
const ln = cm.getLine(line);
|
||||||
|
const ranges = {
|
||||||
|
word: {line: line, ch: chTo},
|
||||||
|
squash: {line: line, ch: chTo},
|
||||||
|
};
|
||||||
if(ln[chTo] === ' ') {
|
if(ln[chTo] === ' ') {
|
||||||
ranges.squashTo.ch ++;
|
ranges.squash.ch ++;
|
||||||
}
|
}
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapQuote(entry, quote) {
|
function wrapQuote(entry, quote) {
|
||||||
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
|
if(!quote && REQUIRED_QUOTED.test(entry.v)) {
|
||||||
quote = '"';
|
quote = '"';
|
||||||
}
|
}
|
||||||
let inner = entry.v;
|
let inner = entry.v;
|
||||||
if(quote) {
|
if(quote && entry.q) {
|
||||||
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
||||||
}
|
}
|
||||||
return (entry.prefix || '') + inner + (entry.suffix || '');
|
return (entry.prefix || '') + inner + (entry.suffix || '');
|
||||||
|
@ -5688,16 +5736,27 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
function makeHintItem(entry, ranges, quote) {
|
function makeHintItem(entry, ranges, quote) {
|
||||||
const quoted = wrapQuote(entry, quote);
|
const quoted = wrapQuote(entry, quote);
|
||||||
|
const from = entry.q ? ranges.fromVar : ranges.fromKey;
|
||||||
|
if(quoted === '\n') {
|
||||||
|
return {
|
||||||
|
text: '\n',
|
||||||
|
displayText: '<END>',
|
||||||
|
className: 'pick-virtual',
|
||||||
|
from: from.squash,
|
||||||
|
to: ranges.to.squash,
|
||||||
|
displayFrom: null,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
text: quoted,
|
text: quoted,
|
||||||
displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
|
displayText: quoted.trim(),
|
||||||
className: (quoted === '\n') ? 'pick-virtual' : null,
|
className: null,
|
||||||
from: SQUASH_START.test(quoted) ?
|
from: SQUASH_START.test(quoted) ? from.squash : from.word,
|
||||||
ranges.squashFrom : ranges.wordFrom,
|
to: SQUASH_END.test(quoted) ? ranges.to.squash : ranges.to.word,
|
||||||
to: SQUASH_END.test(quoted) ?
|
displayFrom: from.word,
|
||||||
ranges.squashTo : ranges.wordTo,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getGlobals({global, prefix = '', suffix = ''}, globals) {
|
function getGlobals({global, prefix = '', suffix = ''}, globals) {
|
||||||
const identified = globals[global];
|
const identified = globals[global];
|
||||||
|
@ -5719,29 +5778,89 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPartial(cur, token) {
|
function getTokensUpTo(cm, pos) {
|
||||||
let partial = token.string;
|
const tokens = cm.getLineTokens(pos.line);
|
||||||
if(token.end > cur.ch) {
|
for(let p = 0; p < tokens.length; ++ p) {
|
||||||
partial = partial.substr(0, cur.ch - token.start);
|
if(tokens[p].end >= pos.ch) {
|
||||||
|
tokens.length = p + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVariablePartial(tokens, pos) {
|
||||||
|
let lastVariable = 0;
|
||||||
|
let partial = '';
|
||||||
|
let start = 0;
|
||||||
|
let end = 0;
|
||||||
|
tokens.forEach((token, p) => {
|
||||||
|
if(token.state.isVar) {
|
||||||
|
partial += token.string;
|
||||||
|
end = token.end;
|
||||||
|
} else {
|
||||||
|
lastVariable = p + 1;
|
||||||
|
partial = '';
|
||||||
|
start = token.end;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(end > pos.ch) {
|
||||||
|
partial = partial.substr(0, pos.ch - start);
|
||||||
}
|
}
|
||||||
const parts = TRIMMER.exec(partial);
|
const parts = TRIMMER.exec(partial);
|
||||||
partial = parts[2];
|
partial = parts[2];
|
||||||
let quote = '';
|
let quote = '';
|
||||||
if(partial[0] === '"') {
|
if(ONGOING_QUOTE.test(partial)) {
|
||||||
quote = partial[0];
|
quote = partial[0];
|
||||||
partial = partial.substr(1);
|
partial = partial.substr(1);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
partial,
|
partial,
|
||||||
quote,
|
quote,
|
||||||
from: token.start + parts[1].length,
|
from: start + parts[1].length,
|
||||||
|
valid: end >= start,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getKeywordPartial(token, pos) {
|
||||||
|
let partial = token.string;
|
||||||
|
if(token.end > pos.ch) {
|
||||||
|
partial = partial.substr(0, pos.ch - token.start);
|
||||||
|
}
|
||||||
|
const parts = TRIMMER.exec(partial);
|
||||||
|
return {
|
||||||
|
partial: parts[2],
|
||||||
|
from: token.start + parts[1].length,
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function suggestDropdownLocation(list, fromKey) {
|
||||||
|
let p = null;
|
||||||
|
list.forEach(({displayFrom}) => {
|
||||||
|
if(displayFrom) {
|
||||||
|
if(
|
||||||
|
!p ||
|
||||||
|
displayFrom.line > p.line ||
|
||||||
|
(displayFrom.line === p.line && displayFrom.ch > p.ch)
|
||||||
|
) {
|
||||||
|
p = displayFrom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return p || fromKey.word;
|
||||||
|
}
|
||||||
|
|
||||||
|
function partialMatch(v, p) {
|
||||||
|
return p.valid && v.startsWith(p.partial);
|
||||||
|
}
|
||||||
|
|
||||||
function getHints(cm, options) {
|
function getHints(cm, options) {
|
||||||
const cur = cm.getCursor();
|
const cur = cm.getCursor();
|
||||||
const token = cm.getTokenAt(cur);
|
const tokens = getTokensUpTo(cm, cur);
|
||||||
const {partial, from, quote} = getPartial(cur, token);
|
const token = array.last(tokens) || cm.getTokenAt(cur);
|
||||||
|
const pVar = getVariablePartial(tokens, cur);
|
||||||
|
const pKey = getKeywordPartial(token, cur);
|
||||||
|
|
||||||
const continuation = (cur.ch > 0 && token.state.line.length > 0);
|
const continuation = (cur.ch > 0 && token.state.line.length > 0);
|
||||||
let comp = (continuation ?
|
let comp = (continuation ?
|
||||||
|
@ -5754,31 +5873,36 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
populateGlobals(comp, cm.options.globals);
|
populateGlobals(comp, cm.options.globals);
|
||||||
|
|
||||||
const ranges = makeRanges(cm, cur.line, from, token.end);
|
const ranges = {
|
||||||
let selfValid = false;
|
fromVar: makeRangeFrom(cm, cur.line, pVar.from),
|
||||||
|
fromKey: makeRangeFrom(cm, cur.line, pKey.from),
|
||||||
|
to: makeRangeTo(cm, cur.line, token.end),
|
||||||
|
};
|
||||||
|
let selfValid = null;
|
||||||
const list = (comp
|
const list = (comp
|
||||||
.filter(({v, q}) => (q || !quote) && v.startsWith(partial))
|
.filter((o) => (
|
||||||
|
(o.q || !pVar.quote) &&
|
||||||
|
partialMatch(o.v, o.q ? pVar : pKey)
|
||||||
|
))
|
||||||
.map((o) => {
|
.map((o) => {
|
||||||
if(o.v === partial + ' ' && !options.completeSingle) {
|
if(!options.completeSingle) {
|
||||||
selfValid = true;
|
if(o.v === (o.q ? pVar : pKey).partial) {
|
||||||
|
selfValid = o;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return makeHintItem(o, ranges, quote);
|
}
|
||||||
|
return makeHintItem(o, ranges, pVar.quote);
|
||||||
})
|
})
|
||||||
.filter((opt) => (opt !== null))
|
.filter((opt) => (opt !== null))
|
||||||
);
|
);
|
||||||
if(selfValid && list.length > 0) {
|
if(selfValid && list.length > 0) {
|
||||||
list.unshift(makeHintItem(
|
list.unshift(makeHintItem(selfValid, ranges, pVar.quote));
|
||||||
{v: partial, suffix: ' ', q: false},
|
|
||||||
ranges,
|
|
||||||
quote
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
from: ranges.wordFrom,
|
from: suggestDropdownLocation(list, ranges.fromKey),
|
||||||
to: ranges.wordTo,
|
to: ranges.to.word,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -4,7 +4,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const TRIMMER = /^([ \t]*)(.*)$/;
|
const TRIMMER = /^([ \t]*)(.*)$/;
|
||||||
const SQUASH_START = /^[ \t\r\n:,]/;
|
const SQUASH_START = /^[ \t\r\n:,]/;
|
||||||
const SQUASH_END = /[ \t\r\n]$/;
|
const SQUASH_END = /[ \t\r\n]$/;
|
||||||
const REQUIRED_QUOTED = /[\r\n:,"]/;
|
const ONGOING_QUOTE = /^"(\\.|[^"])*$/;
|
||||||
|
const REQUIRED_QUOTED = /[\r\n:,"<>\-~]/;
|
||||||
const QUOTE_ESCAPE = /["\\]/g;
|
const QUOTE_ESCAPE = /["\\]/g;
|
||||||
|
|
||||||
function suggestionsEqual(a, b) {
|
function suggestionsEqual(a, b) {
|
||||||
|
@ -16,29 +17,36 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRanges(cm, line, chFrom, chTo) {
|
function makeRangeFrom(cm, line, chFrom) {
|
||||||
const ln = cm.getLine(line);
|
const ln = cm.getLine(line);
|
||||||
const ranges = {
|
const ranges = {
|
||||||
wordFrom: {line: line, ch: chFrom},
|
word: {line: line, ch: chFrom},
|
||||||
squashFrom: {line: line, ch: chFrom},
|
squash: {line: line, ch: chFrom},
|
||||||
wordTo: {line: line, ch: chTo},
|
|
||||||
squashTo: {line: line, ch: chTo},
|
|
||||||
};
|
};
|
||||||
if(chFrom > 0 && ln[chFrom - 1] === ' ') {
|
if(chFrom > 0 && ln[chFrom - 1] === ' ') {
|
||||||
ranges.squashFrom.ch --;
|
ranges.squash.ch --;
|
||||||
}
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeRangeTo(cm, line, chTo) {
|
||||||
|
const ln = cm.getLine(line);
|
||||||
|
const ranges = {
|
||||||
|
word: {line: line, ch: chTo},
|
||||||
|
squash: {line: line, ch: chTo},
|
||||||
|
};
|
||||||
if(ln[chTo] === ' ') {
|
if(ln[chTo] === ' ') {
|
||||||
ranges.squashTo.ch ++;
|
ranges.squash.ch ++;
|
||||||
}
|
}
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapQuote(entry, quote) {
|
function wrapQuote(entry, quote) {
|
||||||
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
|
if(!quote && REQUIRED_QUOTED.test(entry.v)) {
|
||||||
quote = '"';
|
quote = '"';
|
||||||
}
|
}
|
||||||
let inner = entry.v;
|
let inner = entry.v;
|
||||||
if(quote) {
|
if(quote && entry.q) {
|
||||||
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
||||||
}
|
}
|
||||||
return (entry.prefix || '') + inner + (entry.suffix || '');
|
return (entry.prefix || '') + inner + (entry.suffix || '');
|
||||||
|
@ -46,16 +54,27 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
function makeHintItem(entry, ranges, quote) {
|
function makeHintItem(entry, ranges, quote) {
|
||||||
const quoted = wrapQuote(entry, quote);
|
const quoted = wrapQuote(entry, quote);
|
||||||
|
const from = entry.q ? ranges.fromVar : ranges.fromKey;
|
||||||
|
if(quoted === '\n') {
|
||||||
|
return {
|
||||||
|
text: '\n',
|
||||||
|
displayText: '<END>',
|
||||||
|
className: 'pick-virtual',
|
||||||
|
from: from.squash,
|
||||||
|
to: ranges.to.squash,
|
||||||
|
displayFrom: null,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
text: quoted,
|
text: quoted,
|
||||||
displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
|
displayText: quoted.trim(),
|
||||||
className: (quoted === '\n') ? 'pick-virtual' : null,
|
className: null,
|
||||||
from: SQUASH_START.test(quoted) ?
|
from: SQUASH_START.test(quoted) ? from.squash : from.word,
|
||||||
ranges.squashFrom : ranges.wordFrom,
|
to: SQUASH_END.test(quoted) ? ranges.to.squash : ranges.to.word,
|
||||||
to: SQUASH_END.test(quoted) ?
|
displayFrom: from.word,
|
||||||
ranges.squashTo : ranges.wordTo,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getGlobals({global, prefix = '', suffix = ''}, globals) {
|
function getGlobals({global, prefix = '', suffix = ''}, globals) {
|
||||||
const identified = globals[global];
|
const identified = globals[global];
|
||||||
|
@ -77,29 +96,89 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPartial(cur, token) {
|
function getTokensUpTo(cm, pos) {
|
||||||
let partial = token.string;
|
const tokens = cm.getLineTokens(pos.line);
|
||||||
if(token.end > cur.ch) {
|
for(let p = 0; p < tokens.length; ++ p) {
|
||||||
partial = partial.substr(0, cur.ch - token.start);
|
if(tokens[p].end >= pos.ch) {
|
||||||
|
tokens.length = p + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVariablePartial(tokens, pos) {
|
||||||
|
let lastVariable = 0;
|
||||||
|
let partial = '';
|
||||||
|
let start = 0;
|
||||||
|
let end = 0;
|
||||||
|
tokens.forEach((token, p) => {
|
||||||
|
if(token.state.isVar) {
|
||||||
|
partial += token.string;
|
||||||
|
end = token.end;
|
||||||
|
} else {
|
||||||
|
lastVariable = p + 1;
|
||||||
|
partial = '';
|
||||||
|
start = token.end;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if(end > pos.ch) {
|
||||||
|
partial = partial.substr(0, pos.ch - start);
|
||||||
}
|
}
|
||||||
const parts = TRIMMER.exec(partial);
|
const parts = TRIMMER.exec(partial);
|
||||||
partial = parts[2];
|
partial = parts[2];
|
||||||
let quote = '';
|
let quote = '';
|
||||||
if(partial[0] === '"') {
|
if(ONGOING_QUOTE.test(partial)) {
|
||||||
quote = partial[0];
|
quote = partial[0];
|
||||||
partial = partial.substr(1);
|
partial = partial.substr(1);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
partial,
|
partial,
|
||||||
quote,
|
quote,
|
||||||
from: token.start + parts[1].length,
|
from: start + parts[1].length,
|
||||||
|
valid: end >= start,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getKeywordPartial(token, pos) {
|
||||||
|
let partial = token.string;
|
||||||
|
if(token.end > pos.ch) {
|
||||||
|
partial = partial.substr(0, pos.ch - token.start);
|
||||||
|
}
|
||||||
|
const parts = TRIMMER.exec(partial);
|
||||||
|
return {
|
||||||
|
partial: parts[2],
|
||||||
|
from: token.start + parts[1].length,
|
||||||
|
valid: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function suggestDropdownLocation(list, fromKey) {
|
||||||
|
let p = null;
|
||||||
|
list.forEach(({displayFrom}) => {
|
||||||
|
if(displayFrom) {
|
||||||
|
if(
|
||||||
|
!p ||
|
||||||
|
displayFrom.line > p.line ||
|
||||||
|
(displayFrom.line === p.line && displayFrom.ch > p.ch)
|
||||||
|
) {
|
||||||
|
p = displayFrom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return p || fromKey.word;
|
||||||
|
}
|
||||||
|
|
||||||
|
function partialMatch(v, p) {
|
||||||
|
return p.valid && v.startsWith(p.partial);
|
||||||
|
}
|
||||||
|
|
||||||
function getHints(cm, options) {
|
function getHints(cm, options) {
|
||||||
const cur = cm.getCursor();
|
const cur = cm.getCursor();
|
||||||
const token = cm.getTokenAt(cur);
|
const tokens = getTokensUpTo(cm, cur);
|
||||||
const {partial, from, quote} = getPartial(cur, token);
|
const token = array.last(tokens) || cm.getTokenAt(cur);
|
||||||
|
const pVar = getVariablePartial(tokens, cur);
|
||||||
|
const pKey = getKeywordPartial(token, cur);
|
||||||
|
|
||||||
const continuation = (cur.ch > 0 && token.state.line.length > 0);
|
const continuation = (cur.ch > 0 && token.state.line.length > 0);
|
||||||
let comp = (continuation ?
|
let comp = (continuation ?
|
||||||
|
@ -112,31 +191,36 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
populateGlobals(comp, cm.options.globals);
|
populateGlobals(comp, cm.options.globals);
|
||||||
|
|
||||||
const ranges = makeRanges(cm, cur.line, from, token.end);
|
const ranges = {
|
||||||
let selfValid = false;
|
fromVar: makeRangeFrom(cm, cur.line, pVar.from),
|
||||||
|
fromKey: makeRangeFrom(cm, cur.line, pKey.from),
|
||||||
|
to: makeRangeTo(cm, cur.line, token.end),
|
||||||
|
};
|
||||||
|
let selfValid = null;
|
||||||
const list = (comp
|
const list = (comp
|
||||||
.filter(({v, q}) => (q || !quote) && v.startsWith(partial))
|
.filter((o) => (
|
||||||
|
(o.q || !pVar.quote) &&
|
||||||
|
partialMatch(o.v, o.q ? pVar : pKey)
|
||||||
|
))
|
||||||
.map((o) => {
|
.map((o) => {
|
||||||
if(o.v === partial + ' ' && !options.completeSingle) {
|
if(!options.completeSingle) {
|
||||||
selfValid = true;
|
if(o.v === (o.q ? pVar : pKey).partial) {
|
||||||
|
selfValid = o;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return makeHintItem(o, ranges, quote);
|
}
|
||||||
|
return makeHintItem(o, ranges, pVar.quote);
|
||||||
})
|
})
|
||||||
.filter((opt) => (opt !== null))
|
.filter((opt) => (opt !== null))
|
||||||
);
|
);
|
||||||
if(selfValid && list.length > 0) {
|
if(selfValid && list.length > 0) {
|
||||||
list.unshift(makeHintItem(
|
list.unshift(makeHintItem(selfValid, ranges, pVar.quote));
|
||||||
{v: partial, suffix: ' ', q: false},
|
|
||||||
ranges,
|
|
||||||
quote
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
list,
|
list,
|
||||||
from: ranges.wordFrom,
|
from: suggestDropdownLocation(list, ranges.fromKey),
|
||||||
to: ranges.wordTo,
|
to: ranges.to.word,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,32 +23,44 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const end = {type: '', suggest: '\n', then: {}};
|
const end = {type: '', suggest: '\n', then: {}};
|
||||||
const hiddenEnd = {type: '', then: {}};
|
const hiddenEnd = {type: '', then: {}};
|
||||||
|
|
||||||
function textTo(exit) {
|
function textTo(exit, suggest) {
|
||||||
return {type: 'string', then: Object.assign({'': 0}, exit)};
|
return {
|
||||||
|
type: 'string',
|
||||||
|
suggest,
|
||||||
|
then: Object.assign({'': 0}, exit),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const textToEnd = textTo({'\n': end});
|
const textToEnd = textTo({'\n': end});
|
||||||
const aliasListToEnd = {type: 'variable', suggest: 'Agent', then: {
|
const aliasListToEnd = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
'\n': end,
|
'\n': end,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||||
'as': {type: 'keyword', suggest: true, then: {
|
'as': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Agent', then: {
|
'': {type: 'variable', suggest: {known: 'Agent'}, then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 3}},
|
',': {type: 'operator', suggest: true, then: {'': 3}},
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}},
|
}},
|
||||||
}},
|
}},
|
||||||
}};
|
},
|
||||||
|
};
|
||||||
|
|
||||||
function agentListTo(exit) {
|
function agentListTo(exit) {
|
||||||
return {type: 'variable', suggest: 'Agent', then: Object.assign({},
|
return {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: Object.assign({},
|
||||||
exit,
|
exit,
|
||||||
{
|
{
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': 1}},
|
',': {type: 'operator', suggest: true, then: {'': 1}},
|
||||||
}
|
}
|
||||||
)};
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const colonTextToEnd = {
|
const colonTextToEnd = {
|
||||||
|
@ -59,32 +71,50 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const agentListToText = agentListTo({
|
const agentListToText = agentListTo({
|
||||||
':': colonTextToEnd,
|
':': colonTextToEnd,
|
||||||
});
|
});
|
||||||
const agentList2ToText = {type: 'variable', suggest: 'Agent', then: {
|
const agentList2ToText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': {type: 'operator', suggest: true, then: {'': agentListToText}},
|
',': {type: 'operator', suggest: true, then: {
|
||||||
|
'': agentListToText,
|
||||||
|
}},
|
||||||
':': CM_ERROR,
|
':': CM_ERROR,
|
||||||
}};
|
},
|
||||||
const singleAgentToText = {type: 'variable', suggest: 'Agent', then: {
|
};
|
||||||
|
const singleAgentToText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
',': CM_ERROR,
|
',': CM_ERROR,
|
||||||
':': colonTextToEnd,
|
':': colonTextToEnd,
|
||||||
}};
|
},
|
||||||
const agentToOptText = {type: 'variable', suggest: 'Agent', then: {
|
};
|
||||||
|
const agentToOptText = {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
':': {type: 'operator', suggest: true, then: {
|
':': {type: 'operator', suggest: true, then: {
|
||||||
'': textToEnd,
|
'': textToEnd,
|
||||||
'\n': hiddenEnd,
|
'\n': hiddenEnd,
|
||||||
}},
|
}},
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}};
|
},
|
||||||
|
};
|
||||||
const referenceName = {
|
const referenceName = {
|
||||||
':': {type: 'operator', suggest: true, then: {
|
':': {type: 'operator', suggest: true, then: {
|
||||||
'': textTo({
|
'': textTo({
|
||||||
'as': {type: 'keyword', suggest: true, then: {
|
'as': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Agent', then: {
|
'': {
|
||||||
|
type: 'variable',
|
||||||
|
suggest: {known: 'Agent'},
|
||||||
|
then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
'\n': end,
|
'\n': end,
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
}},
|
}},
|
||||||
}),
|
}),
|
||||||
}},
|
}},
|
||||||
|
@ -171,7 +201,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
then: {},
|
then: {},
|
||||||
};
|
};
|
||||||
return makeOpBlock(
|
return makeOpBlock(
|
||||||
{type: 'variable', suggest: 'Agent', then},
|
{type: 'variable', suggest: {known: 'Agent'}, then},
|
||||||
then
|
then
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -265,12 +295,16 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}},
|
}},
|
||||||
'autolabel': {type: 'keyword', suggest: true, then: {
|
'autolabel': {type: 'keyword', suggest: true, then: {
|
||||||
'off': {type: 'keyword', suggest: true, then: {}},
|
'off': {type: 'keyword', suggest: true, then: {}},
|
||||||
'': textToEnd,
|
'': textTo({'\n': end}, [
|
||||||
|
{v: '<label>', suffix: '\n', q: true},
|
||||||
|
{v: '[<inc>] <label>', suffix: '\n', q: true},
|
||||||
|
{v: '[<inc 1,0.01>] <label>', suffix: '\n', q: true},
|
||||||
|
]),
|
||||||
}},
|
}},
|
||||||
'simultaneously': {type: 'keyword', suggest: true, then: {
|
'simultaneously': {type: 'keyword', suggest: true, then: {
|
||||||
':': {type: 'operator', suggest: true, then: {}},
|
':': {type: 'operator', suggest: true, then: {}},
|
||||||
'with': {type: 'keyword', suggest: true, then: {
|
'with': {type: 'keyword', suggest: true, then: {
|
||||||
'': {type: 'variable', suggest: 'Label', then: {
|
'': {type: 'variable', suggest: {known: 'Label'}, then: {
|
||||||
'': 0,
|
'': 0,
|
||||||
':': {type: 'operator', suggest: true, then: {}},
|
':': {type: 'operator', suggest: true, then: {}},
|
||||||
}},
|
}},
|
||||||
|
@ -294,31 +328,27 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmGetVarSuggestions(state, previous, current) {
|
function cmGetSuggestions(state, token, current) {
|
||||||
if(typeof current.suggest === 'object' && current.suggest.global) {
|
let suggestions = current.suggest;
|
||||||
return [current.suggest];
|
if(!Array.isArray(suggestions)) {
|
||||||
}
|
suggestions = [suggestions];
|
||||||
if(
|
|
||||||
typeof current.suggest !== 'string' ||
|
|
||||||
previous.suggest === current.suggest
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return state['known' + current.suggest];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmGetSuggestions(state, token, previous, current) {
|
return array.flatMap(suggestions, (suggest) => {
|
||||||
if(token === '') {
|
if(suggest === true) {
|
||||||
return cmGetVarSuggestions(state, previous, current);
|
|
||||||
} else if(current.suggest === true) {
|
|
||||||
return [cmCappedToken(token, current)];
|
return [cmCappedToken(token, current)];
|
||||||
} else if(Array.isArray(current.suggest)) {
|
} else if(typeof suggest === 'object') {
|
||||||
return current.suggest.map((v) => ({v, q: false}));
|
if(suggest.known) {
|
||||||
} else if(current.suggest) {
|
return state['known' + suggest.known] || [];
|
||||||
return [{v: current.suggest, q: false}];
|
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return [suggest];
|
||||||
}
|
}
|
||||||
|
} else if(typeof suggest === 'string' && suggest) {
|
||||||
|
return [{v: suggest, q: (token === '')}];
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function cmMakeCompletions(state, path) {
|
function cmMakeCompletions(state, path) {
|
||||||
|
@ -331,7 +361,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
comp,
|
comp,
|
||||||
cmGetSuggestions(state, token, current, next),
|
cmGetSuggestions(state, token, next),
|
||||||
suggestionsEqual
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -339,8 +369,11 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSuggestion(state, locals, token, {suggest, override}) {
|
function updateSuggestion(state, locals, token, {suggest, override}) {
|
||||||
if(locals.type) {
|
let known = null;
|
||||||
if(suggest !== locals.type) {
|
if(typeof suggest === 'object' && suggest.known) {
|
||||||
|
known = suggest.known;
|
||||||
|
}
|
||||||
|
if(locals.type && known !== locals.type) {
|
||||||
if(override) {
|
if(override) {
|
||||||
locals.type = override;
|
locals.type = override;
|
||||||
}
|
}
|
||||||
|
@ -352,9 +385,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
locals.type = '';
|
locals.type = '';
|
||||||
locals.value = '';
|
locals.value = '';
|
||||||
}
|
}
|
||||||
}
|
if(known) {
|
||||||
if(typeof suggest === 'string' && state['known' + suggest]) {
|
locals.type = known;
|
||||||
locals.type = suggest;
|
|
||||||
if(locals.value) {
|
if(locals.value) {
|
||||||
locals.value += token.s;
|
locals.value += token.s;
|
||||||
}
|
}
|
||||||
|
@ -375,7 +407,13 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
state.completions = cmMakeCompletions(state, path);
|
state.completions = cmMakeCompletions(state, path);
|
||||||
}
|
}
|
||||||
const keywordToken = token.q ? '' : token.v;
|
const keywordToken = token.q ? '' : token.v;
|
||||||
const found = current.then[keywordToken] || current.then[''];
|
let found = current.then[keywordToken];
|
||||||
|
if(found === undefined) {
|
||||||
|
found = current.then[''];
|
||||||
|
state.isVar = true;
|
||||||
|
} else {
|
||||||
|
state.isVar = token.q;
|
||||||
|
}
|
||||||
if(typeof found === 'number') {
|
if(typeof found === 'number') {
|
||||||
path.length -= found;
|
path.length -= found;
|
||||||
} else {
|
} else {
|
||||||
|
@ -422,6 +460,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
completions: [],
|
completions: [],
|
||||||
nextCompletions: [],
|
nextCompletions: [],
|
||||||
valid: true,
|
valid: true,
|
||||||
|
isVar: true,
|
||||||
line: [],
|
line: [],
|
||||||
indent: 0,
|
indent: 0,
|
||||||
};
|
};
|
||||||
|
@ -516,6 +555,7 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
token(stream, state) {
|
token(stream, state) {
|
||||||
state.completions = state.nextCompletions;
|
state.completions = state.nextCompletions;
|
||||||
|
state.isVar = true;
|
||||||
if(stream.sol() && state.currentType === -1) {
|
if(stream.sol() && state.currentType === -1) {
|
||||||
state.line.length = 0;
|
state.line.length = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,521 @@
|
||||||
|
defineDescribe('Code Mirror Mode', [
|
||||||
|
'./SequenceDiagram',
|
||||||
|
'cm/lib/codemirror-real',
|
||||||
|
], (
|
||||||
|
SequenceDiagram,
|
||||||
|
CodeMirror
|
||||||
|
) => {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
SequenceDiagram.registerCodeMirrorMode(CodeMirror);
|
||||||
|
|
||||||
|
const cm = new CodeMirror(null, {
|
||||||
|
value: '',
|
||||||
|
mode: 'sequence',
|
||||||
|
globals: {
|
||||||
|
themes: ['Theme', 'Other Theme'],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function getTokens(line) {
|
||||||
|
return cm.getLineTokens(line).map((token) => ({
|
||||||
|
v: token.string,
|
||||||
|
type: token.type,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('colouring', () => {
|
||||||
|
it('highlights comments', () => {
|
||||||
|
cm.getDoc().setValue('# foo');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: '# foo', type: 'comment'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights valid keywords', () => {
|
||||||
|
cm.getDoc().setValue('terminators cross');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'terminators', type: 'keyword'},
|
||||||
|
{v: ' cross', type: 'keyword'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights comments after content', () => {
|
||||||
|
cm.getDoc().setValue('terminators cross # foo');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'terminators', type: 'keyword'},
|
||||||
|
{v: ' cross', type: 'keyword'},
|
||||||
|
{v: ' # foo', type: 'comment'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights invalid lines', () => {
|
||||||
|
cm.getDoc().setValue('terminators huh');
|
||||||
|
expect(getTokens(0)[1].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights incomplete lines', () => {
|
||||||
|
cm.getDoc().setValue('terminators');
|
||||||
|
expect(getTokens(0)[0].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights free text', () => {
|
||||||
|
cm.getDoc().setValue('title my free text');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'title', type: 'keyword'},
|
||||||
|
{v: ' my', type: 'string'},
|
||||||
|
{v: ' free', type: 'string'},
|
||||||
|
{v: ' text', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights quoted text', () => {
|
||||||
|
cm.getDoc().setValue('title "my free text"');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'title', type: 'keyword'},
|
||||||
|
{v: ' "my free text"', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights agent names', () => {
|
||||||
|
cm.getDoc().setValue('A -> B');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'A', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not consider quoted tokens as keywords', () => {
|
||||||
|
cm.getDoc().setValue('A "->" -> B');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'A', type: 'variable'},
|
||||||
|
{v: ' "->"', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights agent aliasing syntax', () => {
|
||||||
|
cm.getDoc().setValue('define A as B, C as D');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'define', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ' as', type: 'keyword'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
{v: ',', type: 'operator'},
|
||||||
|
{v: ' C', type: 'variable'},
|
||||||
|
{v: ' as', type: 'keyword'},
|
||||||
|
{v: ' D', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights multi-word agent names', () => {
|
||||||
|
cm.getDoc().setValue('Foo Bar -> Zig Zag');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'Foo', type: 'variable'},
|
||||||
|
{v: ' Bar', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' Zig', type: 'variable'},
|
||||||
|
{v: ' Zag', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights connection operators without spaces', () => {
|
||||||
|
cm.getDoc().setValue('abc->xyz');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'abc', type: 'variable'},
|
||||||
|
{v: '->', type: 'keyword'},
|
||||||
|
{v: 'xyz', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights the lost message operator without spaces', () => {
|
||||||
|
cm.getDoc().setValue('abc-xxyz');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'abc', type: 'variable'},
|
||||||
|
{v: '-x', type: 'keyword'},
|
||||||
|
{v: 'xyz', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognises agent flags', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> *Bar');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'Foo', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' *', type: 'operator'},
|
||||||
|
{v: 'Bar', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects missing agent names', () => {
|
||||||
|
cm.getDoc().setValue('+ -> Bar');
|
||||||
|
expect(getTokens(0)[2].type).toContain('line-error');
|
||||||
|
|
||||||
|
cm.getDoc().setValue('Bar -> +');
|
||||||
|
expect(getTokens(0)[2].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognises found messages', () => {
|
||||||
|
cm.getDoc().setValue('* -> Bar');
|
||||||
|
expect(getTokens(0)[2].type).not.toContain('line-error');
|
||||||
|
|
||||||
|
cm.getDoc().setValue('Bar <- *');
|
||||||
|
expect(getTokens(0)[2].type).not.toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognises combined agent flags', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> +*Bar');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'Foo', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' +', type: 'operator'},
|
||||||
|
{v: '*', type: 'operator'},
|
||||||
|
{v: 'Bar', type: 'variable'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows messages after connections', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> Bar: hello');
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'Foo', type: 'variable'},
|
||||||
|
{v: ' ->', type: 'keyword'},
|
||||||
|
{v: ' Bar', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hello', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('recognises invalid agent flag combinations', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> *!Bar');
|
||||||
|
expect(getTokens(0)[3].type).toContain('line-error');
|
||||||
|
|
||||||
|
cm.getDoc().setValue('Foo -> +-Bar');
|
||||||
|
expect(getTokens(0)[3].type).toContain('line-error');
|
||||||
|
|
||||||
|
cm.getDoc().setValue('Foo -> +*-Bar');
|
||||||
|
expect(getTokens(0)[4].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights block statements', () => {
|
||||||
|
cm.getDoc().setValue(
|
||||||
|
'if\n' +
|
||||||
|
'if something\n' +
|
||||||
|
'else if another thing\n' +
|
||||||
|
'else\n' +
|
||||||
|
'end\n' +
|
||||||
|
'repeat a few times'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'if', type: 'keyword'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(1)).toEqual([
|
||||||
|
{v: 'if', type: 'keyword'},
|
||||||
|
{v: ' something', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(2)).toEqual([
|
||||||
|
{v: 'else', type: 'keyword'},
|
||||||
|
{v: ' if', type: 'keyword'},
|
||||||
|
{v: ' another', type: 'string'},
|
||||||
|
{v: ' thing', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(3)).toEqual([
|
||||||
|
{v: 'else', type: 'keyword'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(4)).toEqual([
|
||||||
|
{v: 'end', type: 'keyword'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(5)).toEqual([
|
||||||
|
{v: 'repeat', type: 'keyword'},
|
||||||
|
{v: ' a', type: 'string'},
|
||||||
|
{v: ' few', type: 'string'},
|
||||||
|
{v: ' times', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows colons in block statements', () => {
|
||||||
|
cm.getDoc().setValue(
|
||||||
|
'if: something\n' +
|
||||||
|
'else if: another thing\n' +
|
||||||
|
'repeat: a few times'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'if', type: 'keyword'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' something', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(1)).toEqual([
|
||||||
|
{v: 'else', type: 'keyword'},
|
||||||
|
{v: ' if', type: 'keyword'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' another', type: 'string'},
|
||||||
|
{v: ' thing', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(2)).toEqual([
|
||||||
|
{v: 'repeat', type: 'keyword'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' a', type: 'string'},
|
||||||
|
{v: ' few', type: 'string'},
|
||||||
|
{v: ' times', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights note statements', () => {
|
||||||
|
cm.getDoc().setValue(
|
||||||
|
'note over A: hi\n' +
|
||||||
|
'note over A, B: hi\n' +
|
||||||
|
'note left of A, B: hi\n' +
|
||||||
|
'note right of A, B: hi\n' +
|
||||||
|
'note between A, B: hi'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'note', type: 'keyword'},
|
||||||
|
{v: ' over', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(1)).toEqual([
|
||||||
|
{v: 'note', type: 'keyword'},
|
||||||
|
{v: ' over', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ',', type: 'operator'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(2)).toEqual([
|
||||||
|
{v: 'note', type: 'keyword'},
|
||||||
|
{v: ' left', type: 'keyword'},
|
||||||
|
{v: ' of', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ',', type: 'operator'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(3)).toEqual([
|
||||||
|
{v: 'note', type: 'keyword'},
|
||||||
|
{v: ' right', type: 'keyword'},
|
||||||
|
{v: ' of', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ',', type: 'operator'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(getTokens(4)).toEqual([
|
||||||
|
{v: 'note', type: 'keyword'},
|
||||||
|
{v: ' between', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ',', type: 'operator'},
|
||||||
|
{v: ' B', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects notes between a single agent', () => {
|
||||||
|
cm.getDoc().setValue('note between A: hi');
|
||||||
|
expect(getTokens(0)[3].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('highlights state statements', () => {
|
||||||
|
cm.getDoc().setValue('state over A: hi');
|
||||||
|
|
||||||
|
expect(getTokens(0)).toEqual([
|
||||||
|
{v: 'state', type: 'keyword'},
|
||||||
|
{v: ' over', type: 'keyword'},
|
||||||
|
{v: ' A', type: 'variable'},
|
||||||
|
{v: ':', type: 'operator'},
|
||||||
|
{v: ' hi', type: 'string'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('rejects state over multiple agents', () => {
|
||||||
|
cm.getDoc().setValue('state over A, B: hi');
|
||||||
|
expect(getTokens(0)[3].type).toContain('line-error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('autocomplete', () => {
|
||||||
|
function getHints(pos, {completeSingle = true} = {}) {
|
||||||
|
const hintFn = cm.getHelpers(pos, 'hint')[0];
|
||||||
|
cm.setCursor(pos);
|
||||||
|
return hintFn(cm, Object.assign({completeSingle}, cm.options));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHintTexts(pos, options) {
|
||||||
|
const hints = getHints(pos, options);
|
||||||
|
return hints.list.map((hint) => hint.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('suggests commands when used at the start of a line', () => {
|
||||||
|
cm.getDoc().setValue('');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 0});
|
||||||
|
expect(hints).toContain('theme ');
|
||||||
|
expect(hints).toContain('title ');
|
||||||
|
expect(hints).toContain('headers ');
|
||||||
|
expect(hints).toContain('terminators ');
|
||||||
|
expect(hints).toContain('define ');
|
||||||
|
expect(hints).toContain('begin ');
|
||||||
|
expect(hints).toContain('end ');
|
||||||
|
expect(hints).toContain('if ');
|
||||||
|
expect(hints).toContain('else\n');
|
||||||
|
expect(hints).toContain('else if: ');
|
||||||
|
expect(hints).toContain('repeat ');
|
||||||
|
expect(hints).toContain('note ');
|
||||||
|
expect(hints).toContain('state over ');
|
||||||
|
expect(hints).toContain('text ');
|
||||||
|
expect(hints).toContain('autolabel ');
|
||||||
|
expect(hints).toContain('simultaneously ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known header types', () => {
|
||||||
|
cm.getDoc().setValue('headers ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 8});
|
||||||
|
expect(hints).toEqual([
|
||||||
|
'none\n',
|
||||||
|
'cross\n',
|
||||||
|
'box\n',
|
||||||
|
'fade\n',
|
||||||
|
'bar\n',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known terminator types', () => {
|
||||||
|
cm.getDoc().setValue('terminators ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 12});
|
||||||
|
expect(hints).toEqual([
|
||||||
|
'none\n',
|
||||||
|
'cross\n',
|
||||||
|
'box\n',
|
||||||
|
'fade\n',
|
||||||
|
'bar\n',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests useful autolabel values', () => {
|
||||||
|
cm.getDoc().setValue('autolabel ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 10});
|
||||||
|
expect(hints).toContain('off\n');
|
||||||
|
expect(hints).toContain('"<label>"\n');
|
||||||
|
expect(hints).toContain('"[<inc>] <label>"\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests note positioning', () => {
|
||||||
|
cm.getDoc().setValue('note ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 5});
|
||||||
|
expect(hints).toEqual([
|
||||||
|
'over ',
|
||||||
|
'left of ',
|
||||||
|
'left: ',
|
||||||
|
'right of ',
|
||||||
|
'right: ',
|
||||||
|
'between ',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters suggestions', () => {
|
||||||
|
cm.getDoc().setValue('term');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 4});
|
||||||
|
expect(hints).toEqual(['terminators ']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known agent names and flags', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 7});
|
||||||
|
expect(hints).toEqual([
|
||||||
|
'+ ',
|
||||||
|
'- ',
|
||||||
|
'* ',
|
||||||
|
'! ',
|
||||||
|
'Foo ',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('only suggests valid flag combinations', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> + ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 10});
|
||||||
|
expect(hints).toContain('* ');
|
||||||
|
expect(hints).not.toContain('! ');
|
||||||
|
expect(hints).not.toContain('+ ');
|
||||||
|
expect(hints).not.toContain('- ');
|
||||||
|
expect(hints).toContain('Foo ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known agent names at the start of lines', () => {
|
||||||
|
cm.getDoc().setValue('Foo -> Bar\n');
|
||||||
|
const hints = getHintTexts({line: 1, ch: 0});
|
||||||
|
expect(hints).toContain('Foo ');
|
||||||
|
expect(hints).toContain('Bar ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known labels', () => {
|
||||||
|
cm.getDoc().setValue('Abc:\nsimultaneously with ');
|
||||||
|
const hints = getHintTexts({line: 1, ch: 20});
|
||||||
|
expect(hints).toEqual(['Abc ']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests known themes', () => {
|
||||||
|
cm.getDoc().setValue('theme ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 6});
|
||||||
|
expect(hints).toEqual(['Theme\n', 'Other Theme\n']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests filtered multi-word themes', () => {
|
||||||
|
cm.getDoc().setValue('theme Other ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 12});
|
||||||
|
expect(hints).toContain('Other Theme\n');
|
||||||
|
expect(hints).not.toContain('Theme\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests multi-word agents', () => {
|
||||||
|
cm.getDoc().setValue('Zig Zag -> Meh\nFoo Bar -> ');
|
||||||
|
const hints = getHintTexts({line: 1, ch: 11});
|
||||||
|
expect(hints).toContain('Zig Zag ');
|
||||||
|
expect(hints).toContain('Meh ');
|
||||||
|
expect(hints).toContain('Foo Bar ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests filtered multi-word agents', () => {
|
||||||
|
cm.getDoc().setValue('Zig Zag -> Meh\nFoo Bar -> Foo ');
|
||||||
|
const hints = getHintTexts({line: 1, ch: 15});
|
||||||
|
expect(hints).toContain('Foo Bar ');
|
||||||
|
expect(hints).not.toContain('Zig Zag ');
|
||||||
|
expect(hints).not.toContain('Meh ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('suggests quoted names where required', () => {
|
||||||
|
cm.getDoc().setValue('"Zig -> Zag" -> ');
|
||||||
|
const hints = getHintTexts({line: 0, ch: 16});
|
||||||
|
expect(hints).toContain('"Zig -> Zag" ');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('filters quoted names ignoring quotes', () => {
|
||||||
|
cm.getDoc().setValue('"Zig -> Zag" -> Zig');
|
||||||
|
let hints = getHintTexts({line: 0, ch: 19});
|
||||||
|
expect(hints).toContain('"Zig -> Zag" ');
|
||||||
|
|
||||||
|
cm.getDoc().setValue('"Zig -> Zag" -> Zag');
|
||||||
|
hints = getHintTexts({line: 0, ch: 19});
|
||||||
|
expect(hints).not.toContain('"Zig -> Zag" ');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -13,6 +13,7 @@ define([
|
||||||
'sequence/LabelPatternParser_spec',
|
'sequence/LabelPatternParser_spec',
|
||||||
'sequence/Generator_spec',
|
'sequence/Generator_spec',
|
||||||
'sequence/Renderer_spec',
|
'sequence/Renderer_spec',
|
||||||
|
'sequence/CodeMirrorMode_spec',
|
||||||
'sequence/themes/Basic_spec',
|
'sequence/themes/Basic_spec',
|
||||||
'sequence/themes/Monospace_spec',
|
'sequence/themes/Monospace_spec',
|
||||||
'sequence/themes/Chunky_spec',
|
'sequence/themes/Chunky_spec',
|
||||||
|
|
6
test.htm
6
test.htm
|
@ -60,6 +60,12 @@
|
||||||
<meta name="cdn-cm/addon/edit/trailingspace" content="stubs/codemirror-trailingspace">
|
<meta name="cdn-cm/addon/edit/trailingspace" content="stubs/codemirror-trailingspace">
|
||||||
<meta name="cdn-cm/addon/comment/comment" content="stubs/codemirror-comment">
|
<meta name="cdn-cm/addon/comment/comment" content="stubs/codemirror-comment">
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="cdn-cm/lib/codemirror-real"
|
||||||
|
content="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.31.0/codemirror.min.js"
|
||||||
|
data-integrity="sha256-eue5ceZRwKVQ1OXOZSyU7MXCTZMlqsPi/TOIqh1Vlzo="
|
||||||
|
>
|
||||||
|
|
||||||
<!-- test files defined in scripts/specs.js -->
|
<!-- test files defined in scripts/specs.js -->
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
Loading…
Reference in New Issue