c(e))}})}),e(["sequence/SequenceDiagram"],e=>{"use strict";const t=window.define;t&&t.amd?t(()=>e):(document.addEventListener("DOMContentLoaded",()=>{e.convertAll()},{once:!0}),window.CodeMirror&&e.registerCodeMirrorMode(window.CodeMirror),window.SequenceDiagram=e)},null,!0),n("standalone",function(){})}();
\ No newline at end of file
diff --git a/library.htm b/library.htm
index b844bc1..206e3b3 100644
--- a/library.htm
+++ b/library.htm
@@ -196,7 +196,7 @@ note over Foo, Bar: "Foo and Bar
on multiple lines"
note between Foo, Bar: Link
-text right: 'Comments\nOver here!'
+text right: "Comments\nOver here!"
state over Foo: Foo is ponderous
@@ -235,19 +235,19 @@ A <- ]: Profit!
Multiline Text
-title 'My Multiline
-Title'
+title "My Multiline
+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'
- Foo -> 'Multiline\nagent'
+if "Even multiline\ninside conditions like this"
+ Foo -> "Multiline\nagent"
end
-state over Foo: 'Newlines here,
-too!'
+state over Foo: "Newlines here,
+too!"
Themes
@@ -305,13 +305,13 @@ A <- B: than writing the whole name
Markdown
-define 'Name with
-**bold** and _italic_' as A
-define 'Also `code`
-and ~strikeout~' as B
+define "Name with
+**bold** and _italic_" as A
+define "Also `code`
+and ~strikeout~" as B
-A -> B: '_**basic markdown
-is supported!**_'
+A -> B: "_**basic markdown
+is supported!**_"
Alternative Agent Ordering
diff --git a/scripts/editor.js b/scripts/editor.js
index d214d66..0047a1b 100644
--- a/scripts/editor.js
+++ b/scripts/editor.js
@@ -21,7 +21,7 @@
'Bowie -> Goblin: The babe with the power\n' +
'Goblin -> Bowie: What power?\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' +
'Bowie -> Goblin: You do!\n' +
'Goblin -> Bowie: Do what?\n' +
diff --git a/scripts/sequence/CodeMirrorHints.js b/scripts/sequence/CodeMirrorHints.js
index c15e329..1060b4e 100644
--- a/scripts/sequence/CodeMirrorHints.js
+++ b/scripts/sequence/CodeMirrorHints.js
@@ -4,6 +4,17 @@ define(['core/ArrayUtilities'], (array) => {
const TRIMMER = /^([ \t]*)(.*)$/;
const SQUASH_START = /^[ \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) {
const ln = cm.getLine(line);
@@ -22,13 +33,27 @@ define(['core/ArrayUtilities'], (array) => {
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 {
- text: text,
- displayText: (text === '\n') ? '' : text.trim(),
- className: (text === '\n') ? 'pick-virtual' : null,
- from: SQUASH_START.test(text) ? ranges.squashFrom : ranges.wordFrom,
- to: SQUASH_END.test(text) ? ranges.squashTo : ranges.wordTo,
+ text: quoted,
+ displayText: (quoted === '\n') ? '' : quoted.trim(),
+ className: (quoted === '\n') ? 'pick-virtual' : null,
+ from: SQUASH_START.test(quoted) ?
+ ranges.squashFrom : ranges.wordFrom,
+ to: SQUASH_END.test(quoted) ?
+ ranges.squashTo : ranges.wordTo,
};
}
@@ -37,14 +62,14 @@ define(['core/ArrayUtilities'], (array) => {
if(!identified) {
return [];
}
- return identified.map((item) => (prefix + item + suffix));
+ return identified.map((item) => ({v: item, prefix, suffix, q: true}));
}
function populateGlobals(suggestions, globals = {}) {
for(let i = 0; i < suggestions.length;) {
- if(typeof suggestions[i] === 'object') {
+ if(suggestions[i].global) {
const identified = getGlobals(suggestions[i], globals);
- array.mergeSets(suggestions, identified);
+ array.mergeSets(suggestions, identified, suggestionsEqual);
suggestions.splice(i, 1);
} else {
++ i;
@@ -52,16 +77,29 @@ define(['core/ArrayUtilities'], (array) => {
}
}
- function getHints(cm, options) {
- const cur = cm.getCursor();
- const token = cm.getTokenAt(cur);
+ function getPartial(cur, token) {
let partial = token.string;
if(token.end > cur.ch) {
partial = partial.substr(0, cur.ch - token.start);
}
const parts = TRIMMER.exec(partial);
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);
let comp = (continuation ?
@@ -77,18 +115,22 @@ define(['core/ArrayUtilities'], (array) => {
const ranges = makeRanges(cm, cur.line, from, token.end);
let selfValid = false;
const list = (comp
- .filter((opt) => opt.startsWith(partial))
- .map((opt) => {
- if(opt === partial + ' ' && !options.completeSingle) {
+ .filter(({v, q}) => (q || !quote) && v.startsWith(partial))
+ .map((o) => {
+ if(o.v === partial + ' ' && !options.completeSingle) {
selfValid = true;
return null;
}
- return makeHintItem(opt, ranges);
+ return makeHintItem(o, ranges, quote);
})
.filter((opt) => (opt !== null))
);
if(selfValid && list.length > 0) {
- list.unshift(makeHintItem(partial + ' ', ranges));
+ list.unshift(makeHintItem(
+ {v: partial, suffix: ' ', q: false},
+ ranges,
+ quote
+ ));
}
return {
diff --git a/scripts/sequence/CodeMirrorMode.js b/scripts/sequence/CodeMirrorMode.js
index 292724e..b01fbac 100644
--- a/scripts/sequence/CodeMirrorMode.js
+++ b/scripts/sequence/CodeMirrorMode.js
@@ -3,6 +3,15 @@ define(['core/ArrayUtilities'], (array) => {
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 = ((() => {
// The order of commands inside "then" blocks directly influences the
// order they are displayed to the user in autocomplete menus.
@@ -279,9 +288,9 @@ define(['core/ArrayUtilities'], (array) => {
function cmCappedToken(token, current) {
if(Object.keys(current.then).length > 0) {
- return token + ' ';
+ return {v: token, suffix: ' ', q: false};
} else {
- return token + '\n';
+ return {v: token, suffix: '\n', q: false};
}
}
@@ -304,9 +313,9 @@ define(['core/ArrayUtilities'], (array) => {
} else if(current.suggest === true) {
return [cmCappedToken(token, current)];
} else if(Array.isArray(current.suggest)) {
- return current.suggest;
+ return current.suggest.map((v) => ({v, q: false}));
} else if(current.suggest) {
- return [current.suggest];
+ return [{v: current.suggest, q: false}];
} else {
return null;
}
@@ -322,7 +331,8 @@ define(['core/ArrayUtilities'], (array) => {
}
array.mergeSets(
comp,
- cmGetSuggestions(state, token, current, next)
+ cmGetSuggestions(state, token, current, next),
+ suggestionsEqual
);
});
return comp;
@@ -336,7 +346,8 @@ define(['core/ArrayUtilities'], (array) => {
}
array.mergeSets(
state['known' + locals.type],
- [locals.value + ' ']
+ [{v: locals.value, suffix: ' ', q: true}],
+ suggestionsEqual
);
locals.type = '';
locals.value = '';
diff --git a/scripts/sequence/Tokeniser.js b/scripts/sequence/Tokeniser.js
index 958dbeb..9c1c4fd 100644
--- a/scripts/sequence/Tokeniser.js
+++ b/scripts/sequence/Tokeniser.js
@@ -23,14 +23,6 @@ define(['./CodeMirrorMode'], (CMMode) => {
escapeWith: unescape,
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: /(?=[\-~<])/y,
diff --git a/scripts/sequence/Tokeniser_spec.js b/scripts/sequence/Tokeniser_spec.js
index c9408b9..1956604 100644
--- a/scripts/sequence/Tokeniser_spec.js
+++ b/scripts/sequence/Tokeniser_spec.js
@@ -102,7 +102,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
});
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);
expect(tokens).toEqual([
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', () => {
const input = '"foo bar"';
const tokens = tokeniser.tokenise(input);
@@ -130,7 +140,7 @@ defineDescribe('Sequence Tokeniser', ['./Tokeniser'], (Tokeniser) => {
});
it('ignores quotes within comments', () => {
- const input = 'foo # bar "\'baz\nzig';
+ const input = 'foo # bar "baz\nzig';
const tokens = tokeniser.tokenise(input);
expect(tokens).toEqual([
token({s: '', v: 'foo'}),