Add autocomplete support for quoted names, and remove single quotes syntax [#34]
This commit is contained in:
parent
394dcb0e42
commit
ece615e2a0
39
README.md
39
README.md
|
@ -22,7 +22,7 @@ Goblin -> Bowie: What babe?
|
||||||
Bowie -> Goblin: The babe with the power
|
Bowie -> Goblin: The babe with the power
|
||||||
Goblin -> Bowie: What power?
|
Goblin -> Bowie: What power?
|
||||||
note right of Bowie, Goblin: Most people get muddled here!
|
note right of Bowie, Goblin: Most people get muddled here!
|
||||||
Bowie -> Goblin: 'The power of voodoo'
|
Bowie -> Goblin: "The power of voodoo"
|
||||||
Goblin -> Bowie: "Who-do?"
|
Goblin -> Bowie: "Who-do?"
|
||||||
Bowie -> Goblin: You do!
|
Bowie -> Goblin: You do!
|
||||||
Goblin -> Bowie: Do what?
|
Goblin -> Bowie: Do what?
|
||||||
|
@ -85,7 +85,7 @@ note over Foo, Bar: "Foo and Bar
|
||||||
on multiple lines"
|
on multiple lines"
|
||||||
note between Foo, Bar: Link
|
note between Foo, Bar: Link
|
||||||
|
|
||||||
text right: 'Comments\nOver here!'
|
text right: "Comments\nOver here!"
|
||||||
|
|
||||||
state over Foo: Foo is ponderous
|
state over Foo: Foo is ponderous
|
||||||
```
|
```
|
||||||
|
@ -133,19 +133,19 @@ A <- ]: Profit!
|
||||||
<img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="200" align="right" />
|
<img src="screenshots/MultilineText.png" alt="Multiline Text preview" width="200" align="right" />
|
||||||
|
|
||||||
```
|
```
|
||||||
title 'My Multiline
|
title "My Multiline
|
||||||
Title'
|
Title"
|
||||||
|
|
||||||
note over Foo: 'Also possible\nwith escapes'
|
note over Foo: "Also possible\nwith escapes"
|
||||||
|
|
||||||
Foo -> Bar: 'Lines of text\non this arrow'
|
Foo -> Bar: "Lines of text\non this arrow"
|
||||||
|
|
||||||
if 'Even multiline\ninside conditions like this'
|
if "Even multiline\ninside conditions like this"
|
||||||
Foo -> 'Multiline\nagent'
|
Foo -> "Multiline\nagent"
|
||||||
end
|
end
|
||||||
|
|
||||||
state over Foo: 'Newlines here,
|
state over Foo: "Newlines here,
|
||||||
too!'
|
too!"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Themes
|
### Themes
|
||||||
|
@ -206,13 +206,13 @@ A <- B: than writing the whole name
|
||||||
<img src="screenshots/Markdown.png" alt="Markdown preview" width="200" align="right" />
|
<img src="screenshots/Markdown.png" alt="Markdown preview" width="200" align="right" />
|
||||||
|
|
||||||
```
|
```
|
||||||
define 'Name with
|
define "Name with
|
||||||
**bold** and _italic_' as A
|
**bold** and _italic_" as A
|
||||||
define 'Also `code`
|
define "Also `code`
|
||||||
and ~strikeout~' as B
|
and ~strikeout~" as B
|
||||||
|
|
||||||
A -> B: '_**basic markdown
|
A -> B: "_**basic markdown
|
||||||
is supported!**_'
|
is supported!**_"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Alternative Agent Ordering
|
### Alternative Agent Ordering
|
||||||
|
@ -270,17 +270,16 @@ Comments begin with a `#` and end at the next newline:
|
||||||
Meta data can be provided with particular keywords:
|
Meta data can be provided with particular keywords:
|
||||||
|
|
||||||
```
|
```
|
||||||
title 'My title here'
|
title "My title here"
|
||||||
```
|
```
|
||||||
|
|
||||||
Quoting strings is usually optional, for example these are the same:
|
Quoting strings is usually optional, for example these are the same:
|
||||||
|
|
||||||
```
|
```
|
||||||
title 'My title here'
|
|
||||||
title "My title here"
|
title "My title here"
|
||||||
title My title here
|
title My title here
|
||||||
title "My title" here
|
title "My title" here
|
||||||
title "My" 'title' "here"
|
title "My" "title" "here"
|
||||||
```
|
```
|
||||||
|
|
||||||
Each non-metadata line represents a step in the sequence, in order.
|
Each non-metadata line represents a step in the sequence, in order.
|
||||||
|
@ -293,7 +292,7 @@ Foo Bar -> Zig Zag: Do a thing
|
||||||
|
|
||||||
# With quotes, this is the same as:
|
# With quotes, this is the same as:
|
||||||
|
|
||||||
'Foo Bar' -> 'Zig Zag': 'Do a thing'
|
"Foo Bar" -> "Zig Zag": "Do a thing"
|
||||||
```
|
```
|
||||||
|
|
||||||
Blocks surround steps, and can nest:
|
Blocks surround steps, and can nest:
|
||||||
|
|
|
@ -606,6 +606,15 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
const CM_ERROR = {type: 'error line-error', then: {'': 0}};
|
const CM_ERROR = {type: 'error line-error', then: {'': 0}};
|
||||||
|
|
||||||
|
function suggestionsEqual(a, b) {
|
||||||
|
return (
|
||||||
|
(a.v === b.v) &&
|
||||||
|
(a.prefix === b.prefix) &&
|
||||||
|
(a.suffix === b.suffix) &&
|
||||||
|
(a.q === b.q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const makeCommands = ((() => {
|
const makeCommands = ((() => {
|
||||||
// The order of commands inside "then" blocks directly influences the
|
// The order of commands inside "then" blocks directly influences the
|
||||||
// order they are displayed to the user in autocomplete menus.
|
// order they are displayed to the user in autocomplete menus.
|
||||||
|
@ -882,9 +891,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
function cmCappedToken(token, current) {
|
function cmCappedToken(token, current) {
|
||||||
if(Object.keys(current.then).length > 0) {
|
if(Object.keys(current.then).length > 0) {
|
||||||
return token + ' ';
|
return {v: token, suffix: ' ', q: false};
|
||||||
} else {
|
} else {
|
||||||
return token + '\n';
|
return {v: token, suffix: '\n', q: false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,9 +916,9 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
} else if(current.suggest === true) {
|
} else if(current.suggest === true) {
|
||||||
return [cmCappedToken(token, current)];
|
return [cmCappedToken(token, current)];
|
||||||
} else if(Array.isArray(current.suggest)) {
|
} else if(Array.isArray(current.suggest)) {
|
||||||
return current.suggest;
|
return current.suggest.map((v) => ({v, q: false}));
|
||||||
} else if(current.suggest) {
|
} else if(current.suggest) {
|
||||||
return [current.suggest];
|
return [{v: current.suggest, q: false}];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -925,7 +934,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
comp,
|
comp,
|
||||||
cmGetSuggestions(state, token, current, next)
|
cmGetSuggestions(state, token, current, next),
|
||||||
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return comp;
|
return comp;
|
||||||
|
@ -939,7 +949,8 @@ define('sequence/CodeMirrorMode',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
state['known' + locals.type],
|
state['known' + locals.type],
|
||||||
[locals.value + ' ']
|
[{v: locals.value, suffix: ' ', q: true}],
|
||||||
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
locals.type = '';
|
locals.type = '';
|
||||||
locals.value = '';
|
locals.value = '';
|
||||||
|
@ -1153,14 +1164,6 @@ define('sequence/Tokeniser',['./CodeMirrorMode'], (CMMode) => {
|
||||||
escapeWith: unescape,
|
escapeWith: unescape,
|
||||||
baseToken: {q: true},
|
baseToken: {q: true},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
start: /'/y,
|
|
||||||
end: /'/y,
|
|
||||||
escape: /\\(.)/y,
|
|
||||||
escapeWith:
|
|
||||||
unescape,
|
|
||||||
baseToken: {q: true},
|
|
||||||
},
|
|
||||||
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
|
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
|
||||||
{
|
{
|
||||||
start: /(?=[\-~<])/y,
|
start: /(?=[\-~<])/y,
|
||||||
|
@ -5643,6 +5646,17 @@ 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 QUOTE_ESCAPE = /["\\]/g;
|
||||||
|
|
||||||
|
function suggestionsEqual(a, b) {
|
||||||
|
return (
|
||||||
|
(a.v === b.v) &&
|
||||||
|
(a.prefix === b.prefix) &&
|
||||||
|
(a.suffix === b.suffix) &&
|
||||||
|
(a.q === b.q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function makeRanges(cm, line, chFrom, chTo) {
|
function makeRanges(cm, line, chFrom, chTo) {
|
||||||
const ln = cm.getLine(line);
|
const ln = cm.getLine(line);
|
||||||
|
@ -5661,13 +5675,27 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeHintItem(text, ranges) {
|
function wrapQuote(entry, quote) {
|
||||||
|
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
|
||||||
|
quote = '"';
|
||||||
|
}
|
||||||
|
let inner = entry.v;
|
||||||
|
if(quote) {
|
||||||
|
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
||||||
|
}
|
||||||
|
return (entry.prefix || '') + inner + (entry.suffix || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeHintItem(entry, ranges, quote) {
|
||||||
|
const quoted = wrapQuote(entry, quote);
|
||||||
return {
|
return {
|
||||||
text: text,
|
text: quoted,
|
||||||
displayText: (text === '\n') ? '<END>' : text.trim(),
|
displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
|
||||||
className: (text === '\n') ? 'pick-virtual' : null,
|
className: (quoted === '\n') ? 'pick-virtual' : null,
|
||||||
from: SQUASH_START.test(text) ? ranges.squashFrom : ranges.wordFrom,
|
from: SQUASH_START.test(quoted) ?
|
||||||
to: SQUASH_END.test(text) ? ranges.squashTo : ranges.wordTo,
|
ranges.squashFrom : ranges.wordFrom,
|
||||||
|
to: SQUASH_END.test(quoted) ?
|
||||||
|
ranges.squashTo : ranges.wordTo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5676,14 +5704,14 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
if(!identified) {
|
if(!identified) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return identified.map((item) => (prefix + item + suffix));
|
return identified.map((item) => ({v: item, prefix, suffix, q: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateGlobals(suggestions, globals = {}) {
|
function populateGlobals(suggestions, globals = {}) {
|
||||||
for(let i = 0; i < suggestions.length;) {
|
for(let i = 0; i < suggestions.length;) {
|
||||||
if(typeof suggestions[i] === 'object') {
|
if(suggestions[i].global) {
|
||||||
const identified = getGlobals(suggestions[i], globals);
|
const identified = getGlobals(suggestions[i], globals);
|
||||||
array.mergeSets(suggestions, identified);
|
array.mergeSets(suggestions, identified, suggestionsEqual);
|
||||||
suggestions.splice(i, 1);
|
suggestions.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
++ i;
|
++ i;
|
||||||
|
@ -5691,16 +5719,29 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHints(cm, options) {
|
function getPartial(cur, token) {
|
||||||
const cur = cm.getCursor();
|
|
||||||
const token = cm.getTokenAt(cur);
|
|
||||||
let partial = token.string;
|
let partial = token.string;
|
||||||
if(token.end > cur.ch) {
|
if(token.end > cur.ch) {
|
||||||
partial = partial.substr(0, cur.ch - token.start);
|
partial = partial.substr(0, cur.ch - token.start);
|
||||||
}
|
}
|
||||||
const parts = TRIMMER.exec(partial);
|
const parts = TRIMMER.exec(partial);
|
||||||
partial = parts[2];
|
partial = parts[2];
|
||||||
const from = token.start + parts[1].length;
|
let quote = '';
|
||||||
|
if(partial[0] === '"') {
|
||||||
|
quote = partial[0];
|
||||||
|
partial = partial.substr(1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
partial,
|
||||||
|
quote,
|
||||||
|
from: token.start + parts[1].length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHints(cm, options) {
|
||||||
|
const cur = cm.getCursor();
|
||||||
|
const token = cm.getTokenAt(cur);
|
||||||
|
const {partial, from, quote} = getPartial(cur, token);
|
||||||
|
|
||||||
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 ?
|
||||||
|
@ -5716,18 +5757,22 @@ define('sequence/CodeMirrorHints',['core/ArrayUtilities'], (array) => {
|
||||||
const ranges = makeRanges(cm, cur.line, from, token.end);
|
const ranges = makeRanges(cm, cur.line, from, token.end);
|
||||||
let selfValid = false;
|
let selfValid = false;
|
||||||
const list = (comp
|
const list = (comp
|
||||||
.filter((opt) => opt.startsWith(partial))
|
.filter(({v, q}) => (q || !quote) && v.startsWith(partial))
|
||||||
.map((opt) => {
|
.map((o) => {
|
||||||
if(opt === partial + ' ' && !options.completeSingle) {
|
if(o.v === partial + ' ' && !options.completeSingle) {
|
||||||
selfValid = true;
|
selfValid = true;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return makeHintItem(opt, ranges);
|
return makeHintItem(o, ranges, quote);
|
||||||
})
|
})
|
||||||
.filter((opt) => (opt !== null))
|
.filter((opt) => (opt !== null))
|
||||||
);
|
);
|
||||||
if(selfValid && list.length > 0) {
|
if(selfValid && list.length > 0) {
|
||||||
list.unshift(makeHintItem(partial + ' ', ranges));
|
list.unshift(makeHintItem(
|
||||||
|
{v: partial, suffix: ' ', q: false},
|
||||||
|
ranges,
|
||||||
|
quote
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
File diff suppressed because one or more lines are too long
30
library.htm
30
library.htm
|
@ -196,7 +196,7 @@ note over Foo, Bar: "Foo and Bar
|
||||||
on multiple lines"
|
on multiple lines"
|
||||||
note between Foo, Bar: Link
|
note between Foo, Bar: Link
|
||||||
|
|
||||||
text right: 'Comments\nOver here!'
|
text right: "Comments\nOver here!"
|
||||||
|
|
||||||
state over Foo: Foo is ponderous
|
state over Foo: Foo is ponderous
|
||||||
</pre>
|
</pre>
|
||||||
|
@ -235,19 +235,19 @@ A <- ]: Profit!
|
||||||
|
|
||||||
<h3 id="MultilineText">Multiline Text</h3>
|
<h3 id="MultilineText">Multiline Text</h3>
|
||||||
<pre class="example" data-lang="sequence">
|
<pre class="example" data-lang="sequence">
|
||||||
title 'My Multiline
|
title "My Multiline
|
||||||
Title'
|
Title"
|
||||||
|
|
||||||
note over Foo: 'Also possible\nwith escapes'
|
note over Foo: "Also possible\nwith escapes"
|
||||||
|
|
||||||
Foo -> Bar: 'Lines of text\non this arrow'
|
Foo -> Bar: "Lines of text\non this arrow"
|
||||||
|
|
||||||
if 'Even multiline\ninside conditions like this'
|
if "Even multiline\ninside conditions like this"
|
||||||
Foo -> 'Multiline\nagent'
|
Foo -> "Multiline\nagent"
|
||||||
end
|
end
|
||||||
|
|
||||||
state over Foo: 'Newlines here,
|
state over Foo: "Newlines here,
|
||||||
too!'
|
too!"
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h3 id="Themes">Themes</h3>
|
<h3 id="Themes">Themes</h3>
|
||||||
|
@ -305,13 +305,13 @@ A <- B: than writing the whole name
|
||||||
|
|
||||||
<h3 id="Markdown">Markdown</h3>
|
<h3 id="Markdown">Markdown</h3>
|
||||||
<pre class="example" data-lang="sequence">
|
<pre class="example" data-lang="sequence">
|
||||||
define 'Name with
|
define "Name with
|
||||||
**bold** and _italic_' as A
|
**bold** and _italic_" as A
|
||||||
define 'Also `code`
|
define "Also `code`
|
||||||
and ~strikeout~' as B
|
and ~strikeout~" as B
|
||||||
|
|
||||||
A -> B: '_**basic markdown
|
A -> B: "_**basic markdown
|
||||||
is supported!**_'
|
is supported!**_"
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3>
|
<h3 id="AlternativeAgentOrdering">Alternative Agent Ordering</h3>
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
'Bowie -> Goblin: The babe with the power\n' +
|
'Bowie -> Goblin: The babe with the power\n' +
|
||||||
'Goblin -> Bowie: What power?\n' +
|
'Goblin -> Bowie: What power?\n' +
|
||||||
'note right of Bowie, Goblin: Most people get muddled here!\n' +
|
'note right of Bowie, Goblin: Most people get muddled here!\n' +
|
||||||
'Bowie -> Goblin: \'The power of voodoo\'\n' +
|
'Bowie -> Goblin: "The power of voodoo"\n' +
|
||||||
'Goblin -> Bowie: "Who-do?"\n' +
|
'Goblin -> Bowie: "Who-do?"\n' +
|
||||||
'Bowie -> Goblin: You do!\n' +
|
'Bowie -> Goblin: You do!\n' +
|
||||||
'Goblin -> Bowie: Do what?\n' +
|
'Goblin -> Bowie: Do what?\n' +
|
||||||
|
|
|
@ -4,6 +4,17 @@ 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 QUOTE_ESCAPE = /["\\]/g;
|
||||||
|
|
||||||
|
function suggestionsEqual(a, b) {
|
||||||
|
return (
|
||||||
|
(a.v === b.v) &&
|
||||||
|
(a.prefix === b.prefix) &&
|
||||||
|
(a.suffix === b.suffix) &&
|
||||||
|
(a.q === b.q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function makeRanges(cm, line, chFrom, chTo) {
|
function makeRanges(cm, line, chFrom, chTo) {
|
||||||
const ln = cm.getLine(line);
|
const ln = cm.getLine(line);
|
||||||
|
@ -22,13 +33,27 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeHintItem(text, ranges) {
|
function wrapQuote(entry, quote) {
|
||||||
|
if(!quote && entry.q && REQUIRED_QUOTED.test(entry.v)) {
|
||||||
|
quote = '"';
|
||||||
|
}
|
||||||
|
let inner = entry.v;
|
||||||
|
if(quote) {
|
||||||
|
inner = quote + inner.replace(QUOTE_ESCAPE, '\\$&') + quote;
|
||||||
|
}
|
||||||
|
return (entry.prefix || '') + inner + (entry.suffix || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeHintItem(entry, ranges, quote) {
|
||||||
|
const quoted = wrapQuote(entry, quote);
|
||||||
return {
|
return {
|
||||||
text: text,
|
text: quoted,
|
||||||
displayText: (text === '\n') ? '<END>' : text.trim(),
|
displayText: (quoted === '\n') ? '<END>' : quoted.trim(),
|
||||||
className: (text === '\n') ? 'pick-virtual' : null,
|
className: (quoted === '\n') ? 'pick-virtual' : null,
|
||||||
from: SQUASH_START.test(text) ? ranges.squashFrom : ranges.wordFrom,
|
from: SQUASH_START.test(quoted) ?
|
||||||
to: SQUASH_END.test(text) ? ranges.squashTo : ranges.wordTo,
|
ranges.squashFrom : ranges.wordFrom,
|
||||||
|
to: SQUASH_END.test(quoted) ?
|
||||||
|
ranges.squashTo : ranges.wordTo,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,14 +62,14 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
if(!identified) {
|
if(!identified) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return identified.map((item) => (prefix + item + suffix));
|
return identified.map((item) => ({v: item, prefix, suffix, q: true}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function populateGlobals(suggestions, globals = {}) {
|
function populateGlobals(suggestions, globals = {}) {
|
||||||
for(let i = 0; i < suggestions.length;) {
|
for(let i = 0; i < suggestions.length;) {
|
||||||
if(typeof suggestions[i] === 'object') {
|
if(suggestions[i].global) {
|
||||||
const identified = getGlobals(suggestions[i], globals);
|
const identified = getGlobals(suggestions[i], globals);
|
||||||
array.mergeSets(suggestions, identified);
|
array.mergeSets(suggestions, identified, suggestionsEqual);
|
||||||
suggestions.splice(i, 1);
|
suggestions.splice(i, 1);
|
||||||
} else {
|
} else {
|
||||||
++ i;
|
++ i;
|
||||||
|
@ -52,16 +77,29 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHints(cm, options) {
|
function getPartial(cur, token) {
|
||||||
const cur = cm.getCursor();
|
|
||||||
const token = cm.getTokenAt(cur);
|
|
||||||
let partial = token.string;
|
let partial = token.string;
|
||||||
if(token.end > cur.ch) {
|
if(token.end > cur.ch) {
|
||||||
partial = partial.substr(0, cur.ch - token.start);
|
partial = partial.substr(0, cur.ch - token.start);
|
||||||
}
|
}
|
||||||
const parts = TRIMMER.exec(partial);
|
const parts = TRIMMER.exec(partial);
|
||||||
partial = parts[2];
|
partial = parts[2];
|
||||||
const from = token.start + parts[1].length;
|
let quote = '';
|
||||||
|
if(partial[0] === '"') {
|
||||||
|
quote = partial[0];
|
||||||
|
partial = partial.substr(1);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
partial,
|
||||||
|
quote,
|
||||||
|
from: token.start + parts[1].length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHints(cm, options) {
|
||||||
|
const cur = cm.getCursor();
|
||||||
|
const token = cm.getTokenAt(cur);
|
||||||
|
const {partial, from, quote} = getPartial(cur, token);
|
||||||
|
|
||||||
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 ?
|
||||||
|
@ -77,18 +115,22 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
const ranges = makeRanges(cm, cur.line, from, token.end);
|
const ranges = makeRanges(cm, cur.line, from, token.end);
|
||||||
let selfValid = false;
|
let selfValid = false;
|
||||||
const list = (comp
|
const list = (comp
|
||||||
.filter((opt) => opt.startsWith(partial))
|
.filter(({v, q}) => (q || !quote) && v.startsWith(partial))
|
||||||
.map((opt) => {
|
.map((o) => {
|
||||||
if(opt === partial + ' ' && !options.completeSingle) {
|
if(o.v === partial + ' ' && !options.completeSingle) {
|
||||||
selfValid = true;
|
selfValid = true;
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return makeHintItem(opt, ranges);
|
return makeHintItem(o, ranges, quote);
|
||||||
})
|
})
|
||||||
.filter((opt) => (opt !== null))
|
.filter((opt) => (opt !== null))
|
||||||
);
|
);
|
||||||
if(selfValid && list.length > 0) {
|
if(selfValid && list.length > 0) {
|
||||||
list.unshift(makeHintItem(partial + ' ', ranges));
|
list.unshift(makeHintItem(
|
||||||
|
{v: partial, suffix: ' ', q: false},
|
||||||
|
ranges,
|
||||||
|
quote
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -3,6 +3,15 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
const CM_ERROR = {type: 'error line-error', then: {'': 0}};
|
const CM_ERROR = {type: 'error line-error', then: {'': 0}};
|
||||||
|
|
||||||
|
function suggestionsEqual(a, b) {
|
||||||
|
return (
|
||||||
|
(a.v === b.v) &&
|
||||||
|
(a.prefix === b.prefix) &&
|
||||||
|
(a.suffix === b.suffix) &&
|
||||||
|
(a.q === b.q)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const makeCommands = ((() => {
|
const makeCommands = ((() => {
|
||||||
// The order of commands inside "then" blocks directly influences the
|
// The order of commands inside "then" blocks directly influences the
|
||||||
// order they are displayed to the user in autocomplete menus.
|
// order they are displayed to the user in autocomplete menus.
|
||||||
|
@ -279,9 +288,9 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
|
|
||||||
function cmCappedToken(token, current) {
|
function cmCappedToken(token, current) {
|
||||||
if(Object.keys(current.then).length > 0) {
|
if(Object.keys(current.then).length > 0) {
|
||||||
return token + ' ';
|
return {v: token, suffix: ' ', q: false};
|
||||||
} else {
|
} else {
|
||||||
return token + '\n';
|
return {v: token, suffix: '\n', q: false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,9 +313,9 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
} else if(current.suggest === true) {
|
} else if(current.suggest === true) {
|
||||||
return [cmCappedToken(token, current)];
|
return [cmCappedToken(token, current)];
|
||||||
} else if(Array.isArray(current.suggest)) {
|
} else if(Array.isArray(current.suggest)) {
|
||||||
return current.suggest;
|
return current.suggest.map((v) => ({v, q: false}));
|
||||||
} else if(current.suggest) {
|
} else if(current.suggest) {
|
||||||
return [current.suggest];
|
return [{v: current.suggest, q: false}];
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -322,7 +331,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
comp,
|
comp,
|
||||||
cmGetSuggestions(state, token, current, next)
|
cmGetSuggestions(state, token, current, next),
|
||||||
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return comp;
|
return comp;
|
||||||
|
@ -336,7 +346,8 @@ define(['core/ArrayUtilities'], (array) => {
|
||||||
}
|
}
|
||||||
array.mergeSets(
|
array.mergeSets(
|
||||||
state['known' + locals.type],
|
state['known' + locals.type],
|
||||||
[locals.value + ' ']
|
[{v: locals.value, suffix: ' ', q: true}],
|
||||||
|
suggestionsEqual
|
||||||
);
|
);
|
||||||
locals.type = '';
|
locals.type = '';
|
||||||
locals.value = '';
|
locals.value = '';
|
||||||
|
|
|
@ -23,14 +23,6 @@ define(['./CodeMirrorMode'], (CMMode) => {
|
||||||
escapeWith: unescape,
|
escapeWith: unescape,
|
||||||
baseToken: {q: true},
|
baseToken: {q: true},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
start: /'/y,
|
|
||||||
end: /'/y,
|
|
||||||
escape: /\\(.)/y,
|
|
||||||
escapeWith:
|
|
||||||
unescape,
|
|
||||||
baseToken: {q: true},
|
|
||||||
},
|
|
||||||
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
|
{start: /(?=[^ \t\r\n:+\-~*!<>,])/y, end: /(?=[ \t\r\n:+\-~*!<>,])|$/y},
|
||||||
{
|
{
|
||||||
start: /(?=[\-~<])/y,
|
start: /(?=[\-~<])/y,
|
||||||
|
|
|
@ -102,7 +102,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses quoted strings as single tokens', () => {
|
it('parses quoted strings as single tokens', () => {
|
||||||
const input = 'foo "zig zag" \'abc def\'';
|
const input = 'foo "zig zag" "abc def"';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
token({s: '', v: 'foo', q: false}),
|
token({s: '', v: 'foo', q: false}),
|
||||||
|
@ -111,6 +111,16 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not consider single quotes as quotes', () => {
|
||||||
|
const input = 'foo \'zig zag\'';
|
||||||
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
expect(tokens).toEqual([
|
||||||
|
token({s: '', v: 'foo', q: false}),
|
||||||
|
token({s: ' ', v: '\'zig', q: false}),
|
||||||
|
token({s: ' ', v: 'zag\'', q: false}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('stores character positions around quoted strings', () => {
|
it('stores character positions around quoted strings', () => {
|
||||||
const input = '"foo bar"';
|
const input = '"foo bar"';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
|
@ -130,7 +140,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores quotes within comments', () => {
|
it('ignores quotes within comments', () => {
|
||||||
const input = 'foo # bar "\'baz\nzig';
|
const input = 'foo # bar "baz\nzig';
|
||||||
const tokens = tokeniser.tokenise(input);
|
const tokens = tokeniser.tokenise(input);
|
||||||
expect(tokens).toEqual([
|
expect(tokens).toEqual([
|
||||||
token({s: '', v: 'foo'}),
|
token({s: '', v: 'foo'}),
|
||||||
|
|
Loading…
Reference in New Issue