Linter fixes

This commit is contained in:
David Evans 2018-04-22 14:15:51 +01:00
parent 2e708ebb4d
commit c58b8f7a22
20 changed files with 1641 additions and 1561 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,18 @@
/* eslint-disable max-lines */
/* eslint-disable sort-keys */ // Maybe later
/* eslint-disable complexity */ // Temporary ignore while switching linter
/*
* The order of commands inside "then" blocks directly influences the
* order they are displayed to the user in autocomplete menus.
* This relies on the fact that common JS engines maintain insertion
* order in objects, though this is not guaranteed. It could be switched
* to use Map objects instead for strict compliance, at the cost of
* extra syntax.
*/
/* eslint-disable sort-keys */
import {flatMap, last, mergeSets} from '../../core/ArrayUtilities.mjs';
const CM_ERROR = {type: 'error line-error', suggest: false, then: {'': 0}};
const CM_ERROR = {type: 'error line-error', suggest: [], then: {'': 0}};
function textTo(exit, suggest = false) {
function textTo(exit, suggest = []) {
return {
type: 'string',
suggest,
@ -29,19 +35,10 @@ const AGENT_INFO_TYPES = [
];
const makeCommands = ((() => {
/*
* The order of commands inside "then" blocks directly influences the
* order they are displayed to the user in autocomplete menus.
* This relies on the fact that common JS engines maintain insertion
* order in objects, though this is not guaranteed. It could be switched
* to use Map objects instead for strict compliance, at the cost of
* extra syntax.
*/
function agentListTo(exit, next = 1) {
return {
type: 'variable',
suggest: {known: 'Agent'},
suggest: [{known: 'Agent'}],
then: Object.assign({}, exit, {
'': 0,
',': {type: 'operator', then: {'': next}},
@ -49,8 +46,8 @@ const makeCommands = ((() => {
};
}
const end = {type: '', suggest: '\n', then: {}};
const hiddenEnd = {type: '', suggest: false, then: {}};
const end = {type: '', suggest: ['\n'], then: {}};
const hiddenEnd = {type: '', suggest: [], then: {}};
const textToEnd = textTo({'\n': end});
const colonTextToEnd = {
type: 'operator',
@ -59,7 +56,7 @@ const makeCommands = ((() => {
const aliasListToEnd = agentListTo({
'\n': end,
'as': {type: 'keyword', then: {
'': {type: 'variable', suggest: {known: 'Agent'}, then: {
'': {type: 'variable', suggest: [{known: 'Agent'}], then: {
'': 0,
',': {type: 'operator', then: {'': 3}},
'\n': end,
@ -70,7 +67,7 @@ const makeCommands = ((() => {
const agentToOptText = {
type: 'variable',
suggest: {known: 'Agent'},
suggest: [{known: 'Agent'}],
then: {
'': 0,
':': {type: 'operator', then: {
@ -86,7 +83,7 @@ const makeCommands = ((() => {
'as': {type: 'keyword', then: {
'': {
type: 'variable',
suggest: {known: 'Agent'},
suggest: [{known: 'Agent'}],
then: {
'': 0,
'\n': end,
@ -217,7 +214,7 @@ const makeCommands = ((() => {
'...': {type: 'operator', then: {
'': {
type: 'variable',
suggest: {known: 'DelayedAgent'},
suggest: [{known: 'DelayedAgent'}],
then: {
'': 0,
':': CM_ERROR,
@ -239,14 +236,14 @@ const makeCommands = ((() => {
const hiddenLabelIndicator = {
type: 'operator',
suggest: false,
suggest: [],
override: 'Label',
then: {},
};
const firstAgent = {
type: 'variable',
suggest: {known: 'Agent'},
suggest: [{known: 'Agent'}],
then: Object.assign({
'': 0,
}, connectors, {
@ -256,7 +253,7 @@ const makeCommands = ((() => {
const firstAgentDelayed = {
type: 'variable',
suggest: {known: 'DelayedAgent'},
suggest: [{known: 'DelayedAgent'}],
then: Object.assign({
'': 0,
':': hiddenLabelIndicator,
@ -298,10 +295,7 @@ const makeCommands = ((() => {
'theme': {type: 'keyword', then: {
'': {
type: 'string',
suggest: {
global: 'themes',
suffix: '\n',
},
suggest: [{global: 'themes', suffix: '\n'}],
then: {
'': 0,
'\n': end,
@ -344,7 +338,7 @@ const makeCommands = ((() => {
}},
'if': commonGroup,
'else': {type: 'keyword', suggest: ['else\n', 'else if: '], then: {
'if': {type: 'keyword', suggest: 'if: ', then: {
'if': {type: 'keyword', suggest: ['if: '], then: {
'': textToEnd,
':': {type: 'operator', then: {
'': textToEnd,
@ -364,11 +358,11 @@ const makeCommands = ((() => {
'': agentListTo({':': CM_ERROR}, agentListToText),
}},
}},
'state': {type: 'keyword', suggest: 'state over ', then: {
'state': {type: 'keyword', suggest: ['state over '], then: {
'over': {type: 'keyword', then: {
'': {
type: 'variable',
suggest: {known: 'Agent'},
suggest: [{known: 'Agent'}],
then: {
'': 0,
',': CM_ERROR,
@ -392,7 +386,7 @@ const makeCommands = ((() => {
'simultaneously': {type: 'keyword', then: {
':': {type: 'operator', then: {}},
'with': {type: 'keyword', then: {
'': {type: 'variable', suggest: {known: 'Label'}, then: {
'': {type: 'variable', suggest: [{known: 'Label'}], then: {
'': 0,
':': {type: 'operator', then: {}},
}},
@ -406,33 +400,32 @@ const makeCommands = ((() => {
});
})());
/* eslint-enable sort-keys */
function cmCappedToken(token, current) {
if(Object.keys(current.then).length > 0) {
return {v: token, suffix: ' ', q: false};
return {q: false, suffix: ' ', v: token};
} else {
return {v: token, suffix: '\n', q: false};
return {q: false, suffix: '\n', v: token};
}
}
function cmGetSuggestions(state, token, current) {
let suggestions = current.suggest;
if(!Array.isArray(suggestions)) {
suggestions = [suggestions];
}
const suggestions = current.suggest || [''];
return flatMap(suggestions, (suggest) => {
if(suggest === false) {
return [];
} else if(typeof suggest === 'object') {
if(typeof suggest === 'object') {
if(suggest.known) {
return state['known' + suggest.known] || [];
} else {
return [suggest];
}
} else if(typeof suggest === 'string' && suggest) {
return [{v: suggest, q: (token === '')}];
} else {
} else if(suggest === '') {
return [cmCappedToken(token, current)];
} else if(typeof suggest === 'string') {
return [{q: (token === ''), v: suggest}];
} else {
throw new Error('Invalid suggestion type ' + suggest);
}
});
}
@ -454,30 +447,40 @@ function cmMakeCompletions(state, path) {
return comp;
}
function updateSuggestion(state, locals, token, {suggest, override}) {
let known = null;
if(typeof suggest === 'object' && suggest.known) {
known = suggest.known;
}
if(locals.type && known !== locals.type) {
if(override) {
locals.type = override;
function getSuggestionCategory(suggestions) {
for(const suggestion of suggestions) {
if(typeof suggestion === 'object' && suggestion.known) {
return suggestion.known;
}
mergeSets(
state['known' + locals.type],
[{v: locals.value, suffix: ' ', q: true}],
suggestionsEqual
);
locals.type = '';
}
return null;
}
function appendToken(base, token) {
return base + (base ? token.s : '') + token.v;
}
function storeKnownEntity(state, type, value) {
mergeSets(
state['known' + type],
[{q: true, suffix: ' ', v: value}],
suggestionsEqual
);
}
function updateKnownEntities(state, locals, token, current) {
const known = getSuggestionCategory(current.suggest || ['']);
if(locals.type && known !== locals.type) {
storeKnownEntity(state, current.override || locals.type, locals.value);
locals.value = '';
}
if(known) {
locals.type = known;
if(locals.value) {
locals.value += token.s;
}
locals.value += token.v;
locals.value = appendToken(locals.value, token);
}
locals.type = known;
}
function cmCheckToken(state, eol, commands) {
@ -506,10 +509,10 @@ function cmCheckToken(state, eol, commands) {
path.push(found || CM_ERROR);
}
current = last(path);
updateSuggestion(state, suggestions, token, current);
updateKnownEntities(state, suggestions, token, current);
});
if(eol) {
updateSuggestion(state, suggestions, null, {});
updateKnownEntities(state, suggestions, null, {});
}
state.nextCompletions = cmMakeCompletions(state, path);
state.valid = (
@ -522,11 +525,13 @@ function cmCheckToken(state, eol, commands) {
function getInitialToken(block) {
const baseToken = (block.baseToken || {});
return {
value: baseToken.v || '',
quoted: baseToken.q || false,
value: baseToken.v || '',
};
}
const NO_TOKEN = -1;
export default class Mode {
constructor(tokenDefinitions, arrows) {
this.tokenDefinitions = tokenDefinitions;
@ -536,20 +541,20 @@ export default class Mode {
startState() {
return {
currentType: -1,
beginCompletions: cmMakeCompletions({}, [this.commands]),
completions: [],
current: '',
currentSpace: '',
currentQuoted: false,
currentSpace: '',
currentType: NO_TOKEN,
indent: 0,
isVar: true,
knownAgent: [],
knownDelayedAgent: [],
knownLabel: [],
beginCompletions: cmMakeCompletions({}, [this.commands]),
completions: [],
line: [],
nextCompletions: [],
valid: true,
isVar: true,
line: [],
indent: 0,
};
}
@ -588,14 +593,14 @@ export default class Mode {
_addToken(state) {
state.line.push({
v: state.current,
s: state.currentSpace,
q: state.currentQuoted,
s: state.currentSpace,
v: state.current,
});
}
_tokenEndFound(stream, state, block, match) {
state.currentType = -1;
state.currentType = NO_TOKEN;
if(block.includeEnd) {
state.current += match[0];
}
@ -636,21 +641,33 @@ export default class Mode {
}
}
_tokenContinueOrBegin(stream, state) {
if(state.currentType === NO_TOKEN) {
if(stream.sol()) {
state.line.length = 0;
}
if(!this._tokenBegin(stream, state)) {
return '';
}
}
return this._tokenEnd(stream, state);
}
_isLineTerminated(state) {
return state.currentType !== NO_TOKEN || state.valid;
}
token(stream, state) {
state.completions = state.nextCompletions;
state.isVar = true;
if(stream.sol() && state.currentType === -1) {
state.line.length = 0;
}
let type = '';
if(state.currentType !== -1 || this._tokenBegin(stream, state)) {
type = this._tokenEnd(stream, state);
}
if(state.currentType === -1 && stream.eol() && !state.valid) {
const type = this._tokenContinueOrBegin(stream, state);
if(stream.eol() && !this._isLineTerminated(state)) {
return 'line-error ' + type;
} else {
return type;
}
return type;
}
indent(state) {

View File

@ -1,6 +1,4 @@
/* eslint-disable max-lines */
/* eslint-disable max-statements */
/* eslint-disable sort-keys */ // Maybe later
import SequenceDiagram from '../SequenceDiagram.mjs';
@ -10,17 +8,17 @@ describe('Code Mirror Mode', () => {
SequenceDiagram.registerCodeMirrorMode(CM);
const cm = new CM(null, {
value: '',
mode: 'sequence',
globals: {
themes: ['Theme', 'Other Theme'],
},
mode: 'sequence',
value: '',
});
function getTokens(line) {
return cm.getLineTokens(line).map((token) => ({
v: token.string,
type: token.type,
v: token.string,
}));
}
@ -29,7 +27,7 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('# foo');
expect(getTokens(0)).toEqual([
{v: '# foo', type: 'comment'},
{type: 'comment', v: '# foo'},
]);
});
@ -37,8 +35,8 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('terminators cross');
expect(getTokens(0)).toEqual([
{v: 'terminators', type: 'keyword'},
{v: ' cross', type: 'keyword'},
{type: 'keyword', v: 'terminators'},
{type: 'keyword', v: ' cross'},
]);
});
@ -46,9 +44,9 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('terminators cross # foo');
expect(getTokens(0)).toEqual([
{v: 'terminators', type: 'keyword'},
{v: ' cross', type: 'keyword'},
{v: ' # foo', type: 'comment'},
{type: 'keyword', v: 'terminators'},
{type: 'keyword', v: ' cross'},
{type: 'comment', v: ' # foo'},
]);
});
@ -68,10 +66,10 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'keyword', v: 'title'},
{type: 'string', v: ' my'},
{type: 'string', v: ' free'},
{type: 'string', v: ' text'},
]);
});
@ -79,8 +77,8 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('title "my free text"');
expect(getTokens(0)).toEqual([
{v: 'title', type: 'keyword'},
{v: ' "my free text"', type: 'string'},
{type: 'keyword', v: 'title'},
{type: 'string', v: ' "my free text"'},
]);
});
@ -88,9 +86,9 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('A -> B');
expect(getTokens(0)).toEqual([
{v: 'A', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' B', type: 'variable'},
{type: 'variable', v: 'A'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' B'},
]);
});
@ -98,10 +96,10 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('A "->" -> B');
expect(getTokens(0)).toEqual([
{v: 'A', type: 'variable'},
{v: ' "->"', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' B', type: 'variable'},
{type: 'variable', v: 'A'},
{type: 'variable', v: ' "->"'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' B'},
]);
});
@ -109,14 +107,14 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'keyword', v: 'define'},
{type: 'variable', v: ' A'},
{type: 'keyword', v: ' as'},
{type: 'variable', v: ' B'},
{type: 'operator', v: ','},
{type: 'variable', v: ' C'},
{type: 'keyword', v: ' as'},
{type: 'variable', v: ' D'},
]);
});
@ -124,11 +122,11 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'variable', v: 'Foo'},
{type: 'variable', v: ' Bar'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' Zig'},
{type: 'variable', v: ' Zag'},
]);
});
@ -136,9 +134,9 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('abc->xyz');
expect(getTokens(0)).toEqual([
{v: 'abc', type: 'variable'},
{v: '->', type: 'keyword'},
{v: 'xyz', type: 'variable'},
{type: 'variable', v: 'abc'},
{type: 'keyword', v: '->'},
{type: 'variable', v: 'xyz'},
]);
});
@ -146,9 +144,9 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('abc-xxyz');
expect(getTokens(0)).toEqual([
{v: 'abc', type: 'variable'},
{v: '-x', type: 'keyword'},
{v: 'xyz', type: 'variable'},
{type: 'variable', v: 'abc'},
{type: 'keyword', v: '-x'},
{type: 'variable', v: 'xyz'},
]);
});
@ -156,10 +154,10 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('Foo -> *Bar');
expect(getTokens(0)).toEqual([
{v: 'Foo', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' *', type: 'operator'},
{v: 'Bar', type: 'variable'},
{type: 'variable', v: 'Foo'},
{type: 'keyword', v: ' ->'},
{type: 'operator', v: ' *'},
{type: 'variable', v: 'Bar'},
]);
});
@ -167,10 +165,10 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('*Foo -> Bar');
expect(getTokens(0)).toEqual([
{v: '*', type: 'operator'},
{v: 'Foo', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' Bar', type: 'variable'},
{type: 'operator', v: '*'},
{type: 'variable', v: 'Foo'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' Bar'},
]);
});
@ -198,11 +196,11 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'variable', v: 'Foo'},
{type: 'keyword', v: ' ->'},
{type: 'operator', v: ' +'},
{type: 'operator', v: '*'},
{type: 'variable', v: 'Bar'},
]);
});
@ -210,11 +208,11 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'variable', v: 'Foo'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' Bar'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hello'},
]);
});
@ -236,19 +234,19 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('A -> ...x\n...x -> B: hello');
expect(getTokens(0)).toEqual([
{v: 'A', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' ...', type: 'operator'},
{v: 'x', type: 'variable'},
{type: 'variable', v: 'A'},
{type: 'keyword', v: ' ->'},
{type: 'operator', v: ' ...'},
{type: 'variable', v: 'x'},
]);
expect(getTokens(1)).toEqual([
{v: '...', type: 'operator'},
{v: 'x', type: 'variable'},
{v: ' ->', type: 'keyword'},
{v: ' B', type: 'variable'},
{v: ':', type: 'operator'},
{v: ' hello', type: 'string'},
{type: 'operator', v: '...'},
{type: 'variable', v: 'x'},
{type: 'keyword', v: ' ->'},
{type: 'variable', v: ' B'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hello'},
]);
});
@ -270,39 +268,39 @@ describe('Code Mirror Mode', () => {
);
expect(getTokens(0)).toEqual([
{v: 'if', type: 'keyword'},
{type: 'keyword', v: 'if'},
]);
expect(getTokens(1)).toEqual([
{v: 'if', type: 'keyword'},
{v: ' something', type: 'string'},
{type: 'keyword', v: 'if'},
{type: 'string', v: ' something'},
]);
expect(getTokens(2)).toEqual([
{v: 'else', type: 'keyword'},
{v: ' if', type: 'keyword'},
{v: ' another', type: 'string'},
{v: ' thing', type: 'string'},
{type: 'keyword', v: 'else'},
{type: 'keyword', v: ' if'},
{type: 'string', v: ' another'},
{type: 'string', v: ' thing'},
]);
expect(getTokens(3)).toEqual([
{v: 'else', type: 'keyword'},
{type: 'keyword', v: 'else'},
]);
expect(getTokens(4)).toEqual([
{v: 'end', type: 'keyword'},
{type: 'keyword', v: 'end'},
]);
expect(getTokens(5)).toEqual([
{v: 'repeat', type: 'keyword'},
{v: ' a', type: 'string'},
{v: ' few', type: 'string'},
{v: ' times', type: 'string'},
{type: 'keyword', v: 'repeat'},
{type: 'string', v: ' a'},
{type: 'string', v: ' few'},
{type: 'string', v: ' times'},
]);
expect(getTokens(6)).toEqual([
{v: 'group', type: 'keyword'},
{v: ' foo', type: 'string'},
{type: 'keyword', v: 'group'},
{type: 'string', v: ' foo'},
]);
});
@ -314,25 +312,25 @@ describe('Code Mirror Mode', () => {
);
expect(getTokens(0)).toEqual([
{v: 'if', type: 'keyword'},
{v: ':', type: 'operator'},
{v: ' something', type: 'string'},
{type: 'keyword', v: 'if'},
{type: 'operator', v: ':'},
{type: 'string', v: ' something'},
]);
expect(getTokens(1)).toEqual([
{v: 'else', type: 'keyword'},
{v: ' if', type: 'keyword'},
{v: ':', type: 'operator'},
{v: ' another', type: 'string'},
{v: ' thing', type: 'string'},
{type: 'keyword', v: 'else'},
{type: 'keyword', v: ' if'},
{type: 'operator', v: ':'},
{type: 'string', v: ' another'},
{type: 'string', v: ' thing'},
]);
expect(getTokens(2)).toEqual([
{v: 'repeat', type: 'keyword'},
{v: ':', type: 'operator'},
{v: ' a', type: 'string'},
{v: ' few', type: 'string'},
{v: ' times', type: 'string'},
{type: 'keyword', v: 'repeat'},
{type: 'operator', v: ':'},
{type: 'string', v: ' a'},
{type: 'string', v: ' few'},
{type: 'string', v: ' times'},
]);
});
@ -346,53 +344,53 @@ describe('Code Mirror Mode', () => {
);
expect(getTokens(0)).toEqual([
{v: 'note', type: 'keyword'},
{v: ' over', type: 'keyword'},
{v: ' A', type: 'variable'},
{v: ':', type: 'operator'},
{v: ' hi', type: 'string'},
{type: 'keyword', v: 'note'},
{type: 'keyword', v: ' over'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
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'},
{type: 'keyword', v: 'note'},
{type: 'keyword', v: ' over'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ','},
{type: 'variable', v: ' B'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
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'},
{type: 'keyword', v: 'note'},
{type: 'keyword', v: ' left'},
{type: 'keyword', v: ' of'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ','},
{type: 'variable', v: ' B'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
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'},
{type: 'keyword', v: 'note'},
{type: 'keyword', v: ' right'},
{type: 'keyword', v: ' of'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ','},
{type: 'variable', v: ' B'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
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'},
{type: 'keyword', v: 'note'},
{type: 'keyword', v: ' between'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ','},
{type: 'variable', v: ' B'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
});
@ -406,11 +404,11 @@ describe('Code Mirror Mode', () => {
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'},
{type: 'keyword', v: 'state'},
{type: 'keyword', v: ' over'},
{type: 'variable', v: ' A'},
{type: 'operator', v: ':'},
{type: 'string', v: ' hi'},
]);
});
@ -424,13 +422,13 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('divider tear with height 60: stuff');
expect(getTokens(0)).toEqual([
{v: 'divider', type: 'keyword'},
{v: ' tear', type: 'keyword'},
{v: ' with', type: 'keyword'},
{v: ' height', type: 'keyword'},
{v: ' 60', type: 'number'},
{v: ':', type: 'operator'},
{v: ' stuff', type: 'string'},
{type: 'keyword', v: 'divider'},
{type: 'keyword', v: ' tear'},
{type: 'keyword', v: ' with'},
{type: 'keyword', v: ' height'},
{type: 'number', v: ' 60'},
{type: 'operator', v: ':'},
{type: 'string', v: ' stuff'},
]);
});
@ -438,11 +436,11 @@ describe('Code Mirror Mode', () => {
cm.getDoc().setValue('A is a red database');
expect(getTokens(0)).toEqual([
{v: 'A', type: 'variable'},
{v: ' is', type: 'keyword'},
{v: ' a', type: 'keyword'},
{v: ' red', type: 'keyword'},
{v: ' database', type: 'keyword'},
{type: 'variable', v: 'A'},
{type: 'keyword', v: ' is'},
{type: 'keyword', v: ' a'},
{type: 'keyword', v: ' red'},
{type: 'keyword', v: ' database'},
]);
});
@ -467,7 +465,7 @@ describe('Code Mirror Mode', () => {
it('suggests commands when used at the start of a line', () => {
cm.getDoc().setValue('');
const hints = getHintTexts({line: 0, ch: 0});
const hints = getHintTexts({ch: 0, line: 0});
expect(hints).toContain('theme ');
expect(hints).toContain('title ');
@ -491,7 +489,7 @@ describe('Code Mirror Mode', () => {
it('ignores indentation', () => {
cm.getDoc().setValue(' ');
const hints = getHintTexts({line: 0, ch: 2});
const hints = getHintTexts({ch: 2, line: 0});
expect(hints).toContain('theme ');
expect(hints).toContain('title ');
@ -499,7 +497,7 @@ describe('Code Mirror Mode', () => {
it('suggests known header types', () => {
cm.getDoc().setValue('headers ');
const hints = getHintTexts({line: 0, ch: 8});
const hints = getHintTexts({ch: 8, line: 0});
expect(hints).toEqual([
'none\n',
@ -512,7 +510,7 @@ describe('Code Mirror Mode', () => {
it('suggests known terminator types', () => {
cm.getDoc().setValue('terminators ');
const hints = getHintTexts({line: 0, ch: 12});
const hints = getHintTexts({ch: 12, line: 0});
expect(hints).toEqual([
'none\n',
@ -525,7 +523,7 @@ describe('Code Mirror Mode', () => {
it('suggests divider types', () => {
cm.getDoc().setValue('divider ');
const hints = getHintTexts({line: 0, ch: 8});
const hints = getHintTexts({ch: 8, line: 0});
expect(hints).toEqual([
'line ',
@ -540,7 +538,7 @@ describe('Code Mirror Mode', () => {
it('suggests divider sizes', () => {
cm.getDoc().setValue('divider space with height ');
const hints = getHintTexts({line: 0, ch: 26});
const hints = getHintTexts({ch: 26, line: 0});
expect(hints).toEqual([
'6 ',
@ -550,7 +548,7 @@ describe('Code Mirror Mode', () => {
it('suggests useful autolabel values', () => {
cm.getDoc().setValue('autolabel ');
const hints = getHintTexts({line: 0, ch: 10});
const hints = getHintTexts({ch: 10, line: 0});
expect(hints).toContain('off\n');
expect(hints).toContain('"<label>"\n');
@ -559,7 +557,7 @@ describe('Code Mirror Mode', () => {
it('suggests note positioning', () => {
cm.getDoc().setValue('note ');
const hints = getHintTexts({line: 0, ch: 5});
const hints = getHintTexts({ch: 5, line: 0});
expect(hints).toEqual([
'over ',
@ -573,14 +571,14 @@ describe('Code Mirror Mode', () => {
it('filters suggestions', () => {
cm.getDoc().setValue('term');
const hints = getHintTexts({line: 0, ch: 4});
const hints = getHintTexts({ch: 4, line: 0});
expect(hints).toEqual(['terminators ']);
});
it('suggests known agent names and flags', () => {
cm.getDoc().setValue('Foo -> ');
const hints = getHintTexts({line: 0, ch: 7});
const hints = getHintTexts({ch: 7, line: 0});
expect(hints).toEqual([
'+ ',
@ -594,7 +592,7 @@ describe('Code Mirror Mode', () => {
it('only suggests valid flag combinations', () => {
cm.getDoc().setValue('Foo -> + ');
const hints = getHintTexts({line: 0, ch: 10});
const hints = getHintTexts({ch: 10, line: 0});
expect(hints).toContain('* ');
expect(hints).not.toContain('! ');
@ -605,7 +603,7 @@ describe('Code Mirror Mode', () => {
it('suggests known agent names at the start of lines', () => {
cm.getDoc().setValue('Foo -> Bar\n');
const hints = getHintTexts({line: 1, ch: 0});
const hints = getHintTexts({ch: 0, line: 1});
expect(hints).toContain('Foo ');
expect(hints).toContain('Bar ');
@ -613,21 +611,21 @@ describe('Code Mirror Mode', () => {
it('suggests known labels', () => {
cm.getDoc().setValue('Abc:\nsimultaneously with ');
const hints = getHintTexts({line: 1, ch: 20});
const hints = getHintTexts({ch: 20, line: 1});
expect(hints).toEqual(['Abc ']);
});
it('suggests known themes', () => {
cm.getDoc().setValue('theme ');
const hints = getHintTexts({line: 0, ch: 6});
const hints = getHintTexts({ch: 6, line: 0});
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});
const hints = getHintTexts({ch: 12, line: 0});
expect(hints).toContain('Other Theme\n');
expect(hints).not.toContain('Theme\n');
@ -635,7 +633,7 @@ describe('Code Mirror Mode', () => {
it('suggests multi-word agents', () => {
cm.getDoc().setValue('Zig Zag -> Meh\nFoo Bar -> ');
const hints = getHintTexts({line: 1, ch: 11});
const hints = getHintTexts({ch: 11, line: 1});
expect(hints).toContain('Zig Zag ');
expect(hints).toContain('Meh ');
@ -644,7 +642,7 @@ describe('Code Mirror Mode', () => {
it('suggests quoted agent names if a quote is typed', () => {
cm.getDoc().setValue('Zig Zag -> Meh\nFoo Bar -> "');
const hints = getHintTexts({line: 1, ch: 12});
const hints = getHintTexts({ch: 12, line: 1});
expect(hints).toEqual([
'"Zig Zag" ',
@ -655,7 +653,7 @@ describe('Code Mirror Mode', () => {
it('suggests filtered multi-word agents', () => {
cm.getDoc().setValue('Zig Zag -> Meh\nFoo Bar -> Foo ');
const hints = getHintTexts({line: 1, ch: 15});
const hints = getHintTexts({ch: 15, line: 1});
expect(hints).toContain('Foo Bar ');
expect(hints).not.toContain('Zig Zag ');
@ -664,33 +662,33 @@ describe('Code Mirror Mode', () => {
it('suggests quoted names where required', () => {
cm.getDoc().setValue('"Zig -> Zag" -> ');
const hints = getHintTexts({line: 0, ch: 16});
const hints = getHintTexts({ch: 16, line: 0});
expect(hints).toContain('"Zig -> Zag" ');
});
it('filters quoted names ignoring quotes', () => {
cm.getDoc().setValue('"Zig -> Zag" -> Zig');
let hints = getHintTexts({line: 0, ch: 19});
let hints = getHintTexts({ch: 19, line: 0});
expect(hints).toContain('"Zig -> Zag" ');
cm.getDoc().setValue('"Zig -> Zag" -> Zag');
hints = getHintTexts({line: 0, ch: 19});
hints = getHintTexts({ch: 19, line: 0});
expect(hints).not.toContain('"Zig -> Zag" ');
});
it('suggests known delayed agents', () => {
cm.getDoc().setValue('A -> ...woo\n... ');
const hints = getHintTexts({line: 1, ch: 4});
const hints = getHintTexts({ch: 4, line: 1});
expect(hints).toEqual(['woo ']);
});
it('suggests agent properties', () => {
cm.getDoc().setValue('A is a ');
const hints = getHintTexts({line: 0, ch: 7});
const hints = getHintTexts({ch: 7, line: 0});
expect(hints).toContain('database ');
expect(hints).toContain('red ');
@ -699,7 +697,7 @@ describe('Code Mirror Mode', () => {
it('suggests indefinite articles for agent properties', () => {
cm.getDoc().setValue('A is ');
const hints = getHintTexts({line: 0, ch: 5});
const hints = getHintTexts({ch: 5, line: 0});
expect(hints).toContain('database ');
expect(hints).toContain('a ');
@ -709,7 +707,7 @@ describe('Code Mirror Mode', () => {
it('suggests more agent properties after the first', () => {
cm.getDoc().setValue('A is a red ');
const hints = getHintTexts({line: 0, ch: 11});
const hints = getHintTexts({ch: 11, line: 0});
expect(hints).toContain('database ');
expect(hints).toContain('\n');

View File

@ -1,5 +1,3 @@
/* eslint-disable complexity */ // Temporary ignore while switching linter
import {last, mergeSets} from '../../core/ArrayUtilities.mjs';
const TRIMMER = /^([ \t]*)(.*)$/;
@ -12,7 +10,7 @@ const ONGOING_QUOTE = /^"(\\.|[^"])*$/;
const REQUIRED_QUOTED = /[\r\n:,"<>\-~]/;
const QUOTE_ESCAPE = /["\\]/g;
function suggestionsEqual(a, b) {
function completionsEqual(a, b) {
return (
(a.v === b.v) &&
(a.prefix === b.prefix) &&
@ -89,12 +87,12 @@ function getGlobals({global, prefix = '', suffix = ''}, globals) {
return identified.map((item) => ({prefix, q: true, suffix, v: item}));
}
function populateGlobals(suggestions, globals = {}) {
for(let i = 0; i < suggestions.length;) {
if(suggestions[i].global) {
const identified = getGlobals(suggestions[i], globals);
mergeSets(suggestions, identified, suggestionsEqual);
suggestions.splice(i, 1);
function populateGlobals(completions, globals = {}) {
for(let i = 0; i < completions.length;) {
if(completions[i].global) {
const identified = getGlobals(completions[i], globals);
mergeSets(completions, identified, completionsEqual);
completions.splice(i, 1);
} else {
++ i;
}
@ -176,6 +174,18 @@ function partialMatch(v, p) {
return p.valid && v.startsWith(p.partial);
}
function getCompletions(cur, token, globals) {
let completions = null;
if(cur.ch > 0 && token.state.line.length > 0) {
completions = token.state.completions.slice();
} else {
completions = token.state.beginCompletions
.concat(token.state.knownAgent);
}
populateGlobals(completions, globals);
return completions;
}
export function getHints(cm, options) {
const cur = cm.getCursor();
const tokens = getTokensUpTo(cm, cur);
@ -183,16 +193,7 @@ export function getHints(cm, options) {
const pVar = getVariablePartial(tokens, cur);
const pKey = getKeywordPartial(token, cur);
const continuation = (cur.ch > 0 && token.state.line.length > 0);
let comp = (continuation ?
token.state.completions :
token.state.beginCompletions
);
if(!continuation) {
comp = comp.concat(token.state.knownAgent);
}
populateGlobals(comp, cm.options.globals);
const completions = getCompletions(cur, token, cm.options.globals);
const ranges = {
fromKey: makeRangeFrom(cm, cur.line, pKey.from),
@ -200,7 +201,7 @@ export function getHints(cm, options) {
to: makeRangeTo(cm, cur.line, token.end),
};
let selfValid = null;
const list = (comp
const list = (completions
.filter((o) => (
(o.q || !pVar.quote) &&
partialMatch(o.v, o.q ? pVar : pKey)

View File

@ -1,6 +1,5 @@
/* eslint-disable max-lines */
/* eslint-disable sort-keys */ // Maybe later
/* eslint-disable complexity */ // Temporary ignore while switching linter
import {
flatMap,
@ -41,15 +40,6 @@ const PAgent = {
// Agent from Generator: {id, formattedLabel, anchorRight}
const GAgent = {
equals: (a, b) => (a.id === b.id),
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => ({
anchorRight,
id,
isVirtualSource,
options: [],
}),
indexOf: (list, gAgent) => indexOf(list, gAgent, GAgent.equals),
hasIntersection: (a, b) => hasIntersection(a, b, GAgent.equals),
addNearby: (target, reference, item, offset) => {
const p = indexOf(target, reference, GAgent.equals);
if(p === -1) {
@ -58,11 +48,28 @@ const GAgent = {
target.splice(p + offset, 0, item);
}
},
equals: (a, b) => (a.id === b.id),
hasIntersection: (a, b) => hasIntersection(a, b, GAgent.equals),
indexOf: (list, gAgent) => indexOf(list, gAgent, GAgent.equals),
make: (id, {anchorRight = false, isVirtualSource = false} = {}) => ({
anchorRight,
id,
isVirtualSource,
options: [],
}),
};
function isExpiredGroupAlias(state) {
return state.blocked && state.group === null;
}
function isReservedAgentName(name) {
return name.startsWith('__');
}
const NOTE_DEFAULT_G_AGENTS = {
'note over': [GAgent.make('['), GAgent.make(']')],
'note left': [GAgent.make('[')],
'note over': [GAgent.make('['), GAgent.make(']')],
'note right': [GAgent.make(']')],
};
@ -247,25 +254,25 @@ export default class Generator {
this.currentNest = null;
this.stageHandlers = {
'block begin': this.handleBlockBegin.bind(this),
'block split': this.handleBlockSplit.bind(this),
'block end': this.handleBlockEnd.bind(this),
'group begin': this.handleGroupBegin.bind(this),
'mark': this.handleMark.bind(this),
'async': this.handleAsync.bind(this),
'agent define': this.handleAgentDefine.bind(this),
'agent options': this.handleAgentOptions.bind(this),
'agent begin': this.handleAgentBegin.bind(this),
'agent define': this.handleAgentDefine.bind(this),
'agent end': this.handleAgentEnd.bind(this),
'divider': this.handleDivider.bind(this),
'label pattern': this.handleLabelPattern.bind(this),
'agent options': this.handleAgentOptions.bind(this),
'async': this.handleAsync.bind(this),
'block begin': this.handleBlockBegin.bind(this),
'block end': this.handleBlockEnd.bind(this),
'block split': this.handleBlockSplit.bind(this),
'connect': this.handleConnect.bind(this),
'connect-delay-begin': this.handleConnectDelayBegin.bind(this),
'connect-delay-end': this.handleConnectDelayEnd.bind(this),
'note over': this.handleNote.bind(this),
'note left': this.handleNote.bind(this),
'note right': this.handleNote.bind(this),
'divider': this.handleDivider.bind(this),
'group begin': this.handleGroupBegin.bind(this),
'label pattern': this.handleLabelPattern.bind(this),
'mark': this.handleMark.bind(this),
'note between': this.handleNote.bind(this),
'note left': this.handleNote.bind(this),
'note over': this.handleNote.bind(this),
'note right': this.handleNote.bind(this),
};
this.expandGroupedGAgent = this.expandGroupedGAgent.bind(this);
this.handleStage = this.handleStage.bind(this);
@ -273,6 +280,14 @@ export default class Generator {
this.endGroup = this.endGroup.bind(this);
}
_aliasInUse(alias) {
const old = this.agentAliases.get(alias);
if(old && old !== alias) {
return true;
}
return this.gAgents.some((gAgent) => (gAgent.id === alias));
}
toGAgent({name, alias, flags}) {
if(alias) {
if(this.agentAliases.has(name)) {
@ -280,11 +295,7 @@ export default class Generator {
'Cannot alias ' + name + '; it is already an alias'
);
}
const old = this.agentAliases.get(alias);
if(
(old && old !== alias) ||
this.gAgents.some((gAgent) => (gAgent.id === alias))
) {
if(this._aliasInUse(alias)) {
throw new Error(
'Cannot use ' + alias +
' as an alias; it is already in use'
@ -325,8 +336,8 @@ export default class Generator {
}
});
this.addStage({
type: 'parallel',
stages: viableStages,
type: 'parallel',
});
}
@ -361,25 +372,27 @@ export default class Generator {
allowCovered = false,
allowVirtual = false,
} = {}) {
/* eslint-disable complexity */ // The checks are quite simple
gAgents.forEach((gAgent) => {
/* eslint-enable complexity */
const state = this.getGAgentState(gAgent);
if(state.blocked && state.group === null) {
const name = gAgent.id;
if(isExpiredGroupAlias(state)) {
// Used to be a group alias; can never be reused
throw new Error('Duplicate agent name: ' + gAgent.id);
throw new Error('Duplicate agent name: ' + name);
}
if(!allowCovered && state.covered) {
throw new Error(
'Agent ' + gAgent.id + ' is hidden behind group'
);
throw new Error('Agent ' + name + ' is hidden behind group');
}
if(!allowGrouped && state.group !== null) {
throw new Error('Agent ' + gAgent.id + ' is in a group');
throw new Error('Agent ' + name + ' is in a group');
}
if(!allowVirtual && gAgent.isVirtualSource) {
throw new Error('cannot use message source here');
throw new Error('Cannot use message source here');
}
if(gAgent.id.startsWith('__')) {
throw new Error(gAgent.id + ' is a reserved name');
if(isReservedAgentName(name)) {
throw new Error(name + ' is a reserved name');
}
});
}
@ -410,9 +423,9 @@ export default class Generator {
this.defineGAgents(filteredGAgents);
return {
type: (visible ? 'agent begin' : 'agent end'),
agentIDs: filteredGAgents.map((gAgent) => gAgent.id),
mode,
type: (visible ? 'agent begin' : 'agent end'),
};
}
@ -436,16 +449,16 @@ export default class Generator {
});
return {
type: 'agent highlight',
agentIDs: filteredGAgents.map((gAgent) => gAgent.id),
highlighted,
type: 'agent highlight',
};
}
_makeSection(header, stages) {
return {
header,
delayedConnections: new Map(),
header,
stages,
};
}
@ -467,21 +480,21 @@ export default class Generator {
const gAgents = [leftGAgent, rightGAgent];
const stages = [];
this.currentSection = this._makeSection({
type: 'block begin',
blockType,
tag: this.textFormatter(tag),
label: this.textFormatter(label),
canHide: true,
label: this.textFormatter(label),
left: leftGAgent.id,
right: rightGAgent.id,
ln,
right: rightGAgent.id,
tag: this.textFormatter(tag),
type: 'block begin',
}, stages);
this.currentNest = {
blockType,
gAgents,
hasContent: false,
leftGAgent,
rightGAgent,
hasContent: false,
sections: [this.currentSection],
};
this.replaceGAgentState(leftGAgent, AgentState.LOCKED);
@ -505,10 +518,10 @@ export default class Generator {
handleBlockBegin({ln, blockType, tag, label}) {
this.beginNested(blockType, {
tag,
label,
name: this.nextBlockName(),
ln,
name: this.nextBlockName(),
tag,
});
}
@ -521,13 +534,13 @@ export default class Generator {
}
this._checkSectionEnd();
this.currentSection = this._makeSection({
type: 'block split',
blockType,
tag: this.textFormatter(tag),
label: this.textFormatter(label),
left: this.currentNest.leftGAgent.id,
right: this.currentNest.rightGAgent.id,
ln,
right: this.currentNest.rightGAgent.id,
tag: this.textFormatter(tag),
type: 'block split',
}, []);
this.currentNest.sections.push(this.currentSection);
}
@ -554,9 +567,9 @@ export default class Generator {
this.currentSection.stages.push(...section.stages);
});
this.addStage({
type: 'block end',
left: nested.leftGAgent.id,
right: nested.rightGAgent.id,
type: 'block end',
});
} else {
throw new Error('Empty block');
@ -595,10 +608,10 @@ export default class Generator {
return {
gAgents,
leftGAgent,
rightGAgent,
gAgentsContained,
gAgentsCovered,
leftGAgent,
rightGAgent,
};
}
@ -614,13 +627,13 @@ export default class Generator {
this.activeGroups.set(alias, details);
this.addStage(this.setGAgentVis(details.gAgents, true, 'box'));
this.addStage({
type: 'block begin',
blockType,
tag: this.textFormatter(tag),
canHide: false,
label: this.textFormatter(label),
left: details.leftGAgent.id,
right: details.rightGAgent.id,
tag: this.textFormatter(tag),
type: 'block begin',
});
}
@ -640,23 +653,23 @@ export default class Generator {
this.updateGAgentState(GAgent.make(name), {group: null});
return {
type: 'block end',
left: details.leftGAgent.id,
right: details.rightGAgent.id,
type: 'block end',
};
}
handleMark({name}) {
this.markers.add(name);
this.addStage({type: 'mark', name}, false);
this.addStage({name, type: 'mark'}, false);
}
handleDivider({mode, height, label}) {
this.addStage({
type: 'divider',
mode,
height,
formattedLabel: this.textFormatter(label),
height,
mode,
type: 'divider',
}, false);
}
@ -664,7 +677,7 @@ export default class Generator {
if(target !== '' && !this.markers.has(target)) {
throw new Error('Unknown marker: ' + target);
}
this.addStage({type: 'async', target}, false);
this.addStage({target, type: 'async'}, false);
}
handleLabelPattern({pattern}) {
@ -867,15 +880,15 @@ export default class Generator {
const tag = {};
this.handleConnectDelayBegin({
agent: agents[0],
tag,
options,
ln: 0,
options,
tag,
});
this.handleConnectDelayEnd({
agent: agents[1],
tag,
label,
options,
tag,
});
return;
}
@ -886,10 +899,10 @@ export default class Generator {
gAgents = this.expandVirtualSourceAgents(gAgents);
const connectStage = {
type: 'connect',
agentIDs: gAgents.map((gAgent) => gAgent.id),
label: this.textFormatter(this.applyLabelPattern(label)),
options,
type: 'connect',
};
this.addParallelStages(this._makeConnectParallelStages(
@ -908,14 +921,14 @@ export default class Generator {
const uniqueTag = this.nextVirtualAgentName();
const connectStage = {
type: 'connect-delay-begin',
tag: uniqueTag,
agentIDs: null,
label: null,
options,
tag: uniqueTag,
type: 'connect-delay-begin',
};
dcs.set(tag, {tag, uniqueTag, ln, gAgents, connectStage});
dcs.set(tag, {connectStage, gAgents, ln, tag, uniqueTag});
this.addParallelStages(this._makeConnectParallelStages(
flags,
@ -955,8 +968,8 @@ export default class Generator {
});
const connectEndStage = {
type: 'connect-delay-end',
tag: dcInfo.uniqueTag,
type: 'connect-delay-end',
};
this.addParallelStages(this._makeConnectParallelStages(
@ -987,18 +1000,18 @@ export default class Generator {
this.defineGAgents(gAgents);
this.addStage({
type,
agentIDs,
mode,
label: this.textFormatter(label),
mode,
type,
});
}
handleAgentDefine({agents}) {
const gAgents = agents.map(this.toGAgent);
this.validateGAgents(gAgents, {
allowGrouped: true,
allowCovered: true,
allowGrouped: true,
});
mergeSets(this.gAgents, gAgents, GAgent.equals);
}
@ -1007,8 +1020,8 @@ export default class Generator {
const gAgent = this.toGAgent(agent);
const gAgents = [gAgent];
this.validateGAgents(gAgents, {
allowGrouped: true,
allowCovered: true,
allowGrouped: true,
});
mergeSets(this.gAgents, gAgents, GAgent.equals);
@ -1086,10 +1099,10 @@ export default class Generator {
this.textFormatter = meta.textFormatter;
const globals = this.beginNested('global', {
tag: '',
label: '',
name: '',
ln: 0,
name: '',
tag: '',
});
stages.forEach(this.handleStage);
@ -1117,12 +1130,12 @@ export default class Generator {
swapFirstBegin(globals.stages, meta.headers || 'box');
return {
meta: {
title: this.textFormatter(meta.title),
theme: meta.theme,
code: meta.code,
},
agents: this.gAgents.slice(),
meta: {
code: meta.code,
theme: meta.theme,
title: this.textFormatter(meta.title),
},
stages: globals.stages,
};
}

View File

@ -1,5 +1,3 @@
/* eslint-disable sort-keys */ // Maybe later
import {dom, textSizerFactory} from '../../../spec/stubs/TestDOM.mjs';
import SVG from '../../svg/SVG.mjs';
import parser from './MarkdownParser.mjs';
@ -8,7 +6,7 @@ describe('Markdown Parser', () => {
it('converts simple text', () => {
const formatted = parser('hello everybody');
expect(formatted).toEqual([[{text: 'hello everybody', attrs: null}]]);
expect(formatted).toEqual([[{attrs: null, text: 'hello everybody'}]]);
});
it('produces an empty array given an empty input', () => {
@ -21,8 +19,8 @@ describe('Markdown Parser', () => {
const formatted = parser('hello\neverybody');
expect(formatted).toEqual([
[{text: 'hello', attrs: null}],
[{text: 'everybody', attrs: null}],
[{attrs: null, text: 'hello'}],
[{attrs: null, text: 'everybody'}],
]);
});
@ -30,11 +28,11 @@ describe('Markdown Parser', () => {
const formatted = parser('a **b** c __d__ e');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {'font-weight': 'bolder'}},
{text: ' c ', attrs: null},
{text: 'd', attrs: {'font-weight': 'bolder'}},
{text: ' e', attrs: null},
{attrs: null, text: 'a '},
{attrs: {'font-weight': 'bolder'}, text: 'b'},
{attrs: null, text: ' c '},
{attrs: {'font-weight': 'bolder'}, text: 'd'},
{attrs: null, text: ' e'},
]]);
});
@ -42,7 +40,7 @@ describe('Markdown Parser', () => {
const formatted = parser('a**b**c__d__e');
expect(formatted).toEqual([[
{text: 'a**b**c__d__e', attrs: null},
{attrs: null, text: 'a**b**c__d__e'},
]]);
});
@ -50,11 +48,11 @@ describe('Markdown Parser', () => {
const formatted = parser('a **b\nc** d');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {'font-weight': 'bolder'}},
{attrs: null, text: 'a '},
{attrs: {'font-weight': 'bolder'}, text: 'b'},
], [
{text: 'c', attrs: {'font-weight': 'bolder'}},
{text: ' d', attrs: null},
{attrs: {'font-weight': 'bolder'}, text: 'c'},
{attrs: null, text: ' d'},
]]);
});
@ -62,11 +60,11 @@ describe('Markdown Parser', () => {
const formatted = parser('a *b* c _d_ e');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {'font-style': 'italic'}},
{text: ' c ', attrs: null},
{text: 'd', attrs: {'font-style': 'italic'}},
{text: ' e', attrs: null},
{attrs: null, text: 'a '},
{attrs: {'font-style': 'italic'}, text: 'b'},
{attrs: null, text: ' c '},
{attrs: {'font-style': 'italic'}, text: 'd'},
{attrs: null, text: ' e'},
]]);
});
@ -74,9 +72,9 @@ describe('Markdown Parser', () => {
const formatted = parser('a ~b~ c');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {'text-decoration': 'line-through'}},
{text: ' c', attrs: null},
{attrs: null, text: 'a '},
{attrs: {'text-decoration': 'line-through'}, text: 'b'},
{attrs: null, text: ' c'},
]]);
});
@ -84,9 +82,9 @@ describe('Markdown Parser', () => {
const formatted = parser('a `b` c');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {'font-family': 'monospace'}},
{text: ' c', attrs: null},
{attrs: null, text: 'a '},
{attrs: {'font-family': 'monospace'}, text: 'b'},
{attrs: null, text: ' c'},
]]);
});
@ -94,9 +92,9 @@ describe('Markdown Parser', () => {
const formatted = parser('a.`b`.c');
expect(formatted).toEqual([[
{text: 'a.', attrs: null},
{text: 'b', attrs: {'font-family': 'monospace'}},
{text: '.c', attrs: null},
{attrs: null, text: 'a.'},
{attrs: {'font-family': 'monospace'}, text: 'b'},
{attrs: null, text: '.c'},
]]);
});
@ -104,12 +102,12 @@ describe('Markdown Parser', () => {
const formatted = parser('a **_b_ c**');
expect(formatted).toEqual([[
{text: 'a ', attrs: null},
{text: 'b', attrs: {
'font-weight': 'bolder',
{attrs: null, text: 'a '},
{attrs: {
'font-style': 'italic',
}},
{text: ' c', attrs: {'font-weight': 'bolder'}},
'font-weight': 'bolder',
}, text: 'b'},
{attrs: {'font-weight': 'bolder'}, text: ' c'},
]]);
});
@ -117,16 +115,16 @@ describe('Markdown Parser', () => {
const formatted = parser('_a **b_ ~c~**');
expect(formatted).toEqual([[
{text: 'a ', attrs: {'font-style': 'italic'}},
{text: 'b', attrs: {
'font-weight': 'bolder',
{attrs: {'font-style': 'italic'}, text: 'a '},
{attrs: {
'font-style': 'italic',
}},
{text: ' ', attrs: {'font-weight': 'bolder'}},
{text: 'c', attrs: {
'font-weight': 'bolder',
}, text: 'b'},
{attrs: {'font-weight': 'bolder'}, text: ' '},
{attrs: {
'font-weight': 'bolder',
'text-decoration': 'line-through',
}},
}, text: 'c'},
]]);
});

View File

@ -1,7 +1,4 @@
/* eslint-disable max-lines */
/* eslint-disable sort-keys */ // Maybe later
/* eslint-disable complexity */ // Temporary ignore while switching linter
/* eslint-disable no-param-reassign */ // Also temporary
import {combine, last} from '../../core/ArrayUtilities.mjs';
import Tokeniser from './Tokeniser.mjs';
@ -90,17 +87,47 @@ const NOTE_TYPES = {
'text': {
mode: 'text',
types: {
'left': {type: 'note left', skip: ['of'], min: 0, max: null},
'right': {type: 'note right', skip: ['of'], min: 0, max: null},
'left': {
type: 'note left',
skip: ['of'],
min: 0,
max: Number.POSITIVE_INFINITY,
},
'right': {
type: 'note right',
skip: ['of'],
min: 0,
max: Number.POSITIVE_INFINITY,
},
},
},
'note': {
mode: 'note',
types: {
'over': {type: 'note over', skip: [], min: 0, max: null},
'left': {type: 'note left', skip: ['of'], min: 0, max: null},
'right': {type: 'note right', skip: ['of'], min: 0, max: null},
'between': {type: 'note between', skip: [], min: 2, max: null},
'over': {
type: 'note over',
skip: [],
min: 0,
max: Number.POSITIVE_INFINITY,
},
'left': {
type: 'note left',
skip: ['of'],
min: 0,
max: Number.POSITIVE_INFINITY,
},
'right': {
type: 'note right',
skip: ['of'],
min: 0,
max: Number.POSITIVE_INFINITY,
},
'between': {
type: 'note between',
skip: [],
min: 2,
max: Number.POSITIVE_INFINITY,
},
},
},
'state': {
@ -135,15 +162,20 @@ function makeError(message, token = null) {
return new Error(message + suffix);
}
function joinLabel(line, begin = 0, end = null) {
function endIndexInLine(line, end = null) {
if(end === null) {
end = line.length;
return line.length;
}
if(end <= begin) {
return end;
}
function joinLabel(line, begin = 0, end = null) {
const e = endIndexInLine(line, end);
if(e <= begin) {
return '';
}
let result = line[begin].v;
for(let i = begin + 1; i < end; ++ i) {
for(let i = begin + 1; i < e; ++ i) {
result += line[i].s + line[i].v;
}
return result;
@ -179,30 +211,23 @@ function skipOver(line, start, skip, error = null) {
return start + skip.length;
}
function findToken(line, tokens, {
function findTokens(line, tokens, {
start = 0,
limit = null,
orEnd = false,
} = {}) {
if(limit === null) {
limit = line.length;
}
if(!Array.isArray(tokens)) {
tokens = [tokens];
}
for(let i = start; i <= limit - tokens.length; ++ i) {
const e = endIndexInLine(line, limit);
for(let i = start; i <= e - tokens.length; ++ i) {
if(skipOver(line, i, tokens) !== i) {
return i;
}
}
return orEnd ? limit : -1;
return orEnd ? e : -1;
}
function findFirstToken(line, tokenMap, {start = 0, limit = null} = {}) {
if(limit === null) {
limit = line.length;
}
for(let pos = start; pos < limit; ++ pos) {
const e = endIndexInLine(line, limit);
for(let pos = start; pos < e; ++ pos) {
const value = tokenMap.get(tokenKeyword(line[pos]));
if(value) {
return {pos, value};
@ -212,12 +237,9 @@ function findFirstToken(line, tokenMap, {start = 0, limit = null} = {}) {
}
function readAgentAlias(line, start, end, {enableAlias, allowBlankName}) {
let aliasSep = -1;
let aliasSep = end;
if(enableAlias) {
aliasSep = findToken(line, 'as', {start});
}
if(aliasSep === -1 || aliasSep >= end) {
aliasSep = end;
aliasSep = findTokens(line, ['as'], {limit: end, orEnd: true, start});
}
if(start >= aliasSep && !allowBlankName) {
let errPosToken = line[start];
@ -286,29 +308,17 @@ function readAgentList(line, start, end, readAgentOpts) {
}
const PARSERS = [
(line, meta) => { // Title
if(tokenKeyword(line[0]) !== 'title') {
return null;
}
{begin: ['title'], fn: (line, meta) => { // Title
meta.title = joinLabel(line, 1);
return true;
},
(line, meta) => { // Theme
if(tokenKeyword(line[0]) !== 'theme') {
return null;
}
}},
{begin: ['theme'], fn: (line, meta) => { // Theme
meta.theme = joinLabel(line, 1);
return true;
},
(line, meta) => { // Terminators
if(tokenKeyword(line[0]) !== 'terminators') {
return null;
}
}},
{begin: ['terminators'], fn: (line, meta) => { // Terminators
const type = tokenKeyword(line[1]);
if(!type) {
throw makeError('Unspecified termination', line[0]);
@ -318,13 +328,9 @@ const PARSERS = [
}
meta.terminators = type;
return true;
},
(line, meta) => { // Headers
if(tokenKeyword(line[0]) !== 'headers') {
return null;
}
}},
{begin: ['headers'], fn: (line, meta) => { // Headers
const type = tokenKeyword(line[1]);
if(!type) {
throw makeError('Unspecified header', line[0]);
@ -334,15 +340,11 @@ const PARSERS = [
}
meta.headers = type;
return true;
},
}},
(line) => { // Divider
if(tokenKeyword(line[0]) !== 'divider') {
return null;
}
const labelSep = findToken(line, ':', {orEnd: true});
const heightSep = findToken(line, ['with', 'height'], {
{begin: ['divider'], fn: (line) => { // Divider
const labelSep = findTokens(line, [':'], {orEnd: true});
const heightSep = findTokens(line, ['with', 'height'], {
limit: labelSep,
orEnd: true,
});
@ -368,13 +370,9 @@ const PARSERS = [
height,
label: joinLabel(line, labelSep + 1),
};
},
(line) => { // Autolabel
if(tokenKeyword(line[0]) !== 'autolabel') {
return null;
}
}},
{begin: ['autolabel'], fn: (line) => { // Autolabel
let raw = null;
if(tokenKeyword(line[1]) === 'off') {
raw = '<label>';
@ -385,13 +383,16 @@ const PARSERS = [
type: 'label pattern',
pattern: labelPatternParser(raw),
};
},
}},
(line) => { // Block
if(tokenKeyword(line[0]) === 'end' && line.length === 1) {
return {type: 'block end'};
{begin: ['end'], fn: (line) => { // Block End
if(line.length !== 1) {
return null;
}
return {type: 'block end'};
}},
{begin: [], fn: (line) => { // Block
const type = BLOCK_TYPES[tokenKeyword(line[0])];
if(!type) {
return null;
@ -407,17 +408,11 @@ const PARSERS = [
tag: type.tag,
label: joinLabel(line, skip),
};
},
}},
(line) => { // Begin reference
if(
tokenKeyword(line[0]) !== 'begin' ||
tokenKeyword(line[1]) !== 'reference'
) {
return null;
}
{begin: ['begin', 'reference'], fn: (line) => { // Begin reference
let agents = [];
const labelSep = findToken(line, ':');
const labelSep = findTokens(line, [':']);
if(tokenKeyword(line[2]) === 'over' && labelSep > 3) {
agents = readAgentList(line, 3, labelSep);
} else if(labelSep !== 2) {
@ -440,9 +435,9 @@ const PARSERS = [
label: def.name,
alias: def.alias,
};
},
}},
(line) => { // Agent
{begin: [], fn: (line) => { // Agent
const type = AGENT_MANIPULATION_TYPES[tokenKeyword(line[0])];
if(!type || line.length <= 1) {
return null;
@ -450,12 +445,9 @@ const PARSERS = [
return Object.assign({
agents: readAgentList(line, 1, line.length, {aliases: true}),
}, type);
},
}},
(line) => { // Async
if(tokenKeyword(line[0]) !== 'simultaneously') {
return null;
}
{begin: ['simultaneously'], fn: (line) => { // Async
if(tokenKeyword(last(line)) !== ':') {
return null;
}
@ -470,11 +462,11 @@ const PARSERS = [
type: 'async',
target,
};
},
}},
(line) => { // Note
{begin: [], fn: (line) => { // Note
const mode = NOTE_TYPES[tokenKeyword(line[0])];
const labelSep = findToken(line, ':');
const labelSep = findTokens(line, [':']);
if(!mode || labelSep === -1) {
return null;
}
@ -488,7 +480,7 @@ const PARSERS = [
if(agents.length < type.min) {
throw makeError('Too few agents for ' + mode.mode, line[0]);
}
if(type.max !== null && agents.length > type.max) {
if(agents.length > type.max) {
throw makeError('Too many agents for ' + mode.mode, line[0]);
}
return {
@ -497,10 +489,10 @@ const PARSERS = [
mode: mode.mode,
label: joinLabel(line, labelSep + 1),
};
},
}},
(line) => { // Connect
const labelSep = findToken(line, ':', {orEnd: true});
{begin: [], fn: (line) => { // Connect
const labelSep = findTokens(line, [':'], {orEnd: true});
const connectionToken = findFirstToken(
line,
CONNECT.types,
@ -548,9 +540,9 @@ const PARSERS = [
options: connectionToken.value,
};
}
},
}},
(line) => { // Marker
{begin: [], fn: (line) => { // Marker
if(line.length < 2 || tokenKeyword(last(line)) !== ':') {
return null;
}
@ -558,10 +550,10 @@ const PARSERS = [
type: 'mark',
name: joinLabel(line, 0, line.length - 1),
};
},
}},
(line) => { // Options
const sepPos = findToken(line, 'is');
{begin: [], fn: (line) => { // Options
const sepPos = findTokens(line, ['is']);
if(sepPos < 1) {
return null;
}
@ -583,13 +575,16 @@ const PARSERS = [
agent,
options,
};
},
}},
];
function parseLine(line, {meta, stages}) {
let stage = null;
for(let i = 0; i < PARSERS.length; ++ i) {
stage = PARSERS[i](line, meta);
for(const {begin, fn} of PARSERS) {
if(skipOver(line, 0, begin) !== begin.length) {
continue;
}
stage = fn(line, meta);
if(stage) {
break;
}

View File

@ -140,6 +140,16 @@ describe('Sequence Parser', () => {
});
it('propagates aliases', () => {
const parsed = parser.parse('define Foo Bar as A');
expect(parsed.stages).toEqual([
{type: 'agent define', ln: jasmine.anything(), agents: [
{name: 'Foo Bar', alias: 'A', flags: []},
]},
]);
});
it('propagates long aliases', () => {
const parsed = parser.parse('define Foo Bar as A B');
expect(parsed.stages).toEqual([
@ -149,6 +159,16 @@ describe('Sequence Parser', () => {
]);
});
it('ignores missing aliases', () => {
const parsed = parser.parse('define Foo Bar as');
expect(parsed.stages).toEqual([
{type: 'agent define', ln: jasmine.anything(), agents: [
{name: 'Foo Bar', alias: '', flags: []},
]},
]);
});
it('propagates agent options', () => {
const parsed = parser.parse('Foo bar is zig zag');
@ -256,6 +276,12 @@ describe('Sequence Parser', () => {
));
});
it('rejects missing agent names with aliases', () => {
expect(() => parser.parse('define as A')).toThrow(new Error(
'Missing agent name at line 1, character 7'
));
});
it('parses source agents', () => {
const parsed = parser.parse('A -> *');

View File

@ -1,5 +1,3 @@
/* eslint-disable max-lines */
import './components/AgentCap.mjs';
import './components/AgentHighlight.mjs';
import './components/Block.mjs';

View File

@ -1,5 +1,3 @@
/* eslint-disable sort-keys */ // Maybe later
import {
VirtualDocument,
textSizerFactory,
@ -13,8 +11,8 @@ describe('Sequence Renderer', () => {
beforeEach(() => {
renderer = new Renderer({
document: new VirtualDocument(),
themes: [new BasicThemeFactory()],
textSizerFactory,
themes: [new BasicThemeFactory()],
});
});
@ -28,14 +26,14 @@ describe('Sequence Renderer', () => {
const GENERATED = {
connect: (agentIDs, label = []) => ({
type: 'connect',
agentIDs,
label,
options: {
line: 'solid',
left: 0,
line: 'solid',
right: 1,
},
type: 'connect',
}),
};
@ -49,30 +47,30 @@ describe('Sequence Renderer', () => {
describe('.render', () => {
it('populates the SVG with content', () => {
renderer.render({
meta: {title: format('Title')},
agents: [
{
id: '[',
formattedLabel: null,
anchorRight: true,
options: [],
}, {
id: 'Col 1',
formattedLabel: format('Col 1!'),
anchorRight: false,
options: [],
}, {
id: 'Col 2',
formattedLabel: format('Col 2!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
id: '[',
options: [],
}, {
anchorRight: false,
formattedLabel: format('Col 1!'),
id: 'Col 1',
options: [],
}, {
anchorRight: false,
formattedLabel: format('Col 2!'),
id: 'Col 2',
options: [],
}, {
anchorRight: false,
formattedLabel: null,
id: ']',
options: [],
},
],
meta: {title: format('Title')},
stages: [],
});
const element = renderer.dom();
@ -83,20 +81,20 @@ describe('Sequence Renderer', () => {
it('adds the code as metadata', () => {
renderer.render({
meta: {title: [], code: 'hello'},
agents: [
{
id: '[',
formattedLabel: null,
anchorRight: true,
formattedLabel: null,
id: '[',
options: [],
}, {
id: ']',
formattedLabel: null,
anchorRight: false,
formattedLabel: null,
id: ']',
options: [],
},
],
meta: {code: 'hello', title: []},
stages: [],
});
const element = renderer.dom();
@ -111,34 +109,34 @@ describe('Sequence Renderer', () => {
*/
renderer.render({
meta: {title: []},
agents: [
{
id: '[',
formattedLabel: null,
anchorRight: true,
options: [],
}, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
id: '[',
options: [],
}, {
anchorRight: false,
formattedLabel: format('A!'),
id: 'A',
options: [],
}, {
anchorRight: false,
formattedLabel: format('B!'),
id: 'B',
options: [],
}, {
anchorRight: false,
formattedLabel: null,
id: ']',
options: [],
},
],
meta: {title: []},
stages: [
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},
{agentIDs: ['A', 'B'], mode: 'box', type: 'agent begin'},
GENERATED.connect(['A', 'B']),
{type: 'agent end', agentIDs: ['A', 'B'], mode: 'none'},
{agentIDs: ['A', 'B'], mode: 'none', type: 'agent end'},
],
});
@ -158,49 +156,49 @@ describe('Sequence Renderer', () => {
*/
renderer.render({
meta: {title: []},
agents: [
{
id: '[',
formattedLabel: null,
anchorRight: true,
options: [],
}, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: 'C',
formattedLabel: format('C!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
id: '[',
options: [],
}, {
anchorRight: false,
formattedLabel: format('A!'),
id: 'A',
options: [],
}, {
anchorRight: false,
formattedLabel: format('B!'),
id: 'B',
options: [],
}, {
anchorRight: false,
formattedLabel: format('C!'),
id: 'C',
options: [],
}, {
anchorRight: false,
formattedLabel: null,
id: ']',
options: [],
},
],
meta: {title: []},
stages: [
{
type: 'agent begin',
agentIDs: ['A', 'B', 'C'],
mode: 'box',
type: 'agent begin',
},
GENERATED.connect(['[', 'A']),
GENERATED.connect(['A', 'B']),
GENERATED.connect(['B', 'C']),
GENERATED.connect(['C', ']']),
{
type: 'agent end',
agentIDs: ['A', 'B', 'C'],
mode: 'none',
type: 'agent end',
},
],
});
@ -228,50 +226,50 @@ describe('Sequence Renderer', () => {
*/
renderer.render({
meta: {title: []},
agents: [
{
id: '[',
formattedLabel: null,
anchorRight: true,
options: [],
}, {
id: 'A',
formattedLabel: format('A!'),
anchorRight: false,
options: [],
}, {
id: 'B',
formattedLabel: format('B!'),
anchorRight: false,
options: [],
}, {
id: 'C',
formattedLabel: format('C!'),
anchorRight: false,
options: [],
}, {
id: 'D',
formattedLabel: format('D!'),
anchorRight: false,
options: [],
}, {
id: ']',
formattedLabel: null,
id: '[',
options: [],
}, {
anchorRight: false,
formattedLabel: format('A!'),
id: 'A',
options: [],
}, {
anchorRight: false,
formattedLabel: format('B!'),
id: 'B',
options: [],
}, {
anchorRight: false,
formattedLabel: format('C!'),
id: 'C',
options: [],
}, {
anchorRight: false,
formattedLabel: format('D!'),
id: 'D',
options: [],
}, {
anchorRight: false,
formattedLabel: null,
id: ']',
options: [],
},
],
meta: {title: []},
stages: [
{type: 'agent begin', agentIDs: ['A', 'B'], mode: 'box'},
{agentIDs: ['A', 'B'], mode: 'box', type: 'agent begin'},
GENERATED.connect(['A', 'B'], format('short')),
{type: 'agent end', agentIDs: ['B'], mode: 'cross'},
{type: 'agent begin', agentIDs: ['C'], mode: 'box'},
{agentIDs: ['B'], mode: 'cross', type: 'agent end'},
{agentIDs: ['C'], mode: 'box', type: 'agent begin'},
GENERATED.connect(['A', 'C'], format('long description')),
{type: 'agent end', agentIDs: ['C'], mode: 'cross'},
{type: 'agent begin', agentIDs: ['D'], mode: 'box'},
{agentIDs: ['C'], mode: 'cross', type: 'agent end'},
{agentIDs: ['D'], mode: 'box', type: 'agent begin'},
GENERATED.connect(['A', 'D'], format('short again')),
{type: 'agent end', agentIDs: ['A', 'D'], mode: 'cross'},
{agentIDs: ['A', 'D'], mode: 'cross', type: 'agent end'},
],
});

View File

@ -1,5 +1,3 @@
/* eslint-disable sort-keys */ // Maybe later
import BaseComponent, {register} from './BaseComponent.mjs';
import {mergeSets} from '../../../core/ArrayUtilities.mjs';
@ -36,11 +34,11 @@ class Arrowhead {
const config = this.getConfig(theme);
const short = this.short(theme);
layer.add(config.render(config.attrs, {
dir,
height: config.height,
width: config.width,
x: pt.x + short * dir.dx,
y: pt.y + short * dir.dy,
width: config.width,
height: config.height,
dir,
}));
}
@ -74,9 +72,9 @@ class Arrowcross {
render(layer, theme, pt, dir) {
const config = this.getConfig(theme);
layer.add(config.render({
radius: config.radius,
x: pt.x + config.short * dir.dx,
y: pt.y + config.short * dir.dy,
radius: config.radius,
}));
}
@ -96,10 +94,10 @@ class Arrowcross {
const ARROWHEADS = [
{
render: () => null,
width: () => 0,
height: () => 0,
lineGap: () => 0,
render: () => null,
width: () => 0,
},
new Arrowhead('single'),
new Arrowhead('double'),
@ -184,12 +182,12 @@ export class Connect extends BaseComponent {
const dx1 = lArrow.lineGap(env.theme, line.attrs);
const dx2 = rArrow.lineGap(env.theme, line.attrs);
const rendered = line.renderRev(line.attrs, {
x1: x1 + dx1,
y1,
x2: x2 + dx2,
y2,
xR,
rad: config.loopbackRadius,
x1: x1 + dx1,
x2: x2 + dx2,
xR,
y1,
y2,
});
clickable.add(rendered.shape);
@ -225,9 +223,9 @@ export class Connect extends BaseComponent {
);
const renderedText = env.svg.boxedText({
padding: config.mask.padding,
boxAttrs: {'fill': '#000000'},
labelAttrs: config.label.loopbackAttrs,
padding: config.mask.padding,
}, label, {
x: xL - config.mask.padding.left,
y: yBegin - height + config.label.margin.top,
@ -251,20 +249,20 @@ export class Connect extends BaseComponent {
env.lineMaskLayer.add(renderedText.box);
const clickable = env.makeRegion().add(
env.svg.box(OUTLINE_ATTRS, {
'height': raise + env.primaryY - yBegin + arrowDip,
'width': xR + config.loopbackRadius - from.x,
'x': from.x,
'y': yBegin - raise,
'width': xR + config.loopbackRadius - from.x,
'height': raise + env.primaryY - yBegin + arrowDip,
}),
renderedText.label
);
this.renderRevArrowLine({
x1: from.x + from.currentMaxRad,
y1: yBegin,
x2: to.x + to.currentMaxRad,
y2: env.primaryY,
xR,
y1: yBegin,
y2: env.primaryY,
}, options, env, clickable);
return (
@ -291,8 +289,8 @@ export class Connect extends BaseComponent {
const rendered = line.renderFlat(line.attrs, {
x1: x1 + d1 * dx,
y1: y1 + d1 * dy,
x2: x2 - d2 * dx,
y1: y1 + d1 * dy,
y2: y2 - d2 * dy,
});
clickable.add(rendered.shape);
@ -304,9 +302,9 @@ export class Connect extends BaseComponent {
rArrow.render(clickable, env.theme, p2, {dx: -dx, dy: -dy});
return {
lArrow,
p1,
p2,
lArrow,
rArrow,
};
}
@ -316,16 +314,16 @@ export class Connect extends BaseComponent {
if(from.isVirtualSource) {
clickable.add(config.render({
radius: config.radius,
x: rendered.p1.x - config.radius,
y: rendered.p1.y,
radius: config.radius,
}));
}
if(to.isVirtualSource) {
clickable.add(config.render({
radius: config.radius,
x: rendered.p2.x + config.radius,
y: rendered.p2.y,
radius: config.radius,
}));
}
}
@ -352,9 +350,9 @@ export class Connect extends BaseComponent {
}
const text = env.svg.boxedText({
padding: config.mask.padding,
boxAttrs,
labelAttrs: config.label.attrs,
padding: config.mask.padding,
}, label, {
x: midX,
y: midY + config.label.margin.top - height,
@ -382,8 +380,8 @@ export class Connect extends BaseComponent {
const rendered = this.renderArrowLine({
x1,
y1: yBegin,
x2,
y1: yBegin,
y2: env.primaryY,
}, options, env, clickable);
@ -394,7 +392,7 @@ export class Connect extends BaseComponent {
const lift = Math.max(height, arrowSpread);
this.renderVirtualSources({from, to, rendered}, env, clickable);
this.renderVirtualSources({from, rendered, to}, env, clickable);
clickable.add(env.svg.el('path')
.attrs(OUTLINE_ATTRS)
@ -407,12 +405,12 @@ export class Connect extends BaseComponent {
)));
this.renderSimpleLabel(label, {
height,
layer: clickable,
x1,
y1: yBegin,
x2,
y1: yBegin,
y2: env.primaryY,
height,
}, env);
return env.primaryY + Math.max(
@ -482,8 +480,8 @@ export class ConnectDelayBegin extends Connect {
render(stage, env) {
const dc = env.state.delayedConnections;
dc.set(stage.tag, {
stage,
from: Object.assign({}, env.agentInfos.get(stage.agentIDs[0])),
stage,
y: env.primaryY,
});
return env.primaryY + env.theme.actionMargin;

View File

@ -1,6 +1,3 @@
/* eslint-disable complexity */ // Temporary ignore while switching linter
/* eslint-disable no-param-reassign */ // Also temporary
import BaseComponent, {register} from './BaseComponent.mjs';
const OUTLINE_ATTRS = {
@ -26,6 +23,35 @@ function findExtremes(agentInfos, agentIDs) {
};
}
function findEdges(fullW, {
x0 = null,
x1 = null,
xMid = null,
}) {
let xL = x0;
let xR = x1;
if(xL === null && xMid !== null) {
xL = xMid - fullW / 2;
}
if(xR === null && xL !== null) {
xR = xL + fullW;
} else if(xL === null) {
xL = xR - fullW;
}
return {xL, xR};
}
function textAnchorX(anchor, {xL, xR}, padding) {
switch(anchor) {
case 'middle':
return (xL + padding.left + xR - padding.right) / 2;
case 'end':
return xR - padding.right;
default:
return xL + padding.left;
}
}
class NoteComponent extends BaseComponent {
prepareMeasurements({mode, label}, env) {
const config = env.theme.getNote(mode);
@ -37,67 +63,36 @@ class NoteComponent extends BaseComponent {
}
renderNote({
xMid = null,
x0 = null,
x1 = null,
mode,
label,
mode,
position,
}, env) {
const config = env.theme.getNote(mode);
const {padding} = config;
const y = env.topY + config.margin.top + config.padding.top;
const y = env.topY + config.margin.top + padding.top;
const labelNode = env.svg.formattedText(config.labelAttrs, label);
const size = env.textSizer.measure(labelNode);
const fullW = (
size.width +
config.padding.left +
config.padding.right
);
const fullH = (
config.padding.top +
size.height +
config.padding.bottom
);
if(x0 === null && xMid !== null) {
x0 = xMid - fullW / 2;
}
if(x1 === null && x0 !== null) {
x1 = x0 + fullW;
} else if(x0 === null) {
x0 = x1 - fullW;
}
switch(config.labelAttrs['text-anchor']) {
case 'middle':
labelNode.set({
x: (
x0 + config.padding.left +
x1 - config.padding.right
) / 2,
y,
});
break;
case 'end':
labelNode.set({x: x1 - config.padding.right, y});
break;
default:
labelNode.set({x: x0 + config.padding.left, y});
break;
}
const fullW = (size.width + padding.left + padding.right);
const fullH = (size.height + padding.top + padding.bottom);
const edges = findEdges(fullW, position);
labelNode.set({
x: textAnchorX(config.labelAttrs['text-anchor'], edges, padding),
y,
});
const boundingBox = {
height: fullH,
width: edges.xR - edges.xL,
x: edges.xL,
y: env.topY + config.margin.top,
};
env.makeRegion().add(
config.boxRenderer({
height: fullH,
width: x1 - x0,
x: x0,
y: env.topY + config.margin.top,
}),
env.svg.box(OUTLINE_ATTRS, {
height: fullH,
width: x1 - x0,
x: x0,
y: env.topY + config.margin.top,
}),
config.boxRenderer(boundingBox),
env.svg.box(OUTLINE_ATTRS, boundingBox),
labelNode
);
@ -150,14 +145,16 @@ export class NoteOver extends NoteComponent {
return this.renderNote({
label,
mode,
xMid,
position: {xMid},
}, env);
} else {
return this.renderNote({
label,
mode,
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
position: {
x0: infoL.x - infoL.currentMaxRad - config.overlap.left,
x1: infoR.x + infoR.currentMaxRad + config.overlap.right,
},
}, env);
}
}
@ -204,7 +201,7 @@ export class NoteSide extends NoteComponent {
return this.renderNote({
label,
mode,
x0,
position: {x0},
}, env);
} else {
const info = env.agentInfos.get(left);
@ -212,7 +209,7 @@ export class NoteSide extends NoteComponent {
return this.renderNote({
label,
mode,
x1,
position: {x1},
}, env);
}
}
@ -251,7 +248,7 @@ export class NoteBetween extends NoteComponent {
return this.renderNote({
label,
mode,
xMid,
position: {xMid},
}, env);
}
}

View File

@ -1,5 +1,3 @@
/* eslint-disable sort-keys */ // Maybe later
function optionsAttributes(attributes, options) {
const attrs = Object.assign({}, attributes['']);
options.forEach((opt) => {
@ -63,14 +61,14 @@ export default class BaseTheme {
return optionsAttributes(attributes, options);
}
renderAgentLine({x, y0, y1, width, className, options}) {
renderAgentLine({className, options, width, x, y0, y1}) {
const attrs = this.optionsAttributes(this.agentLineAttrs, options);
if(width > 0) {
return this.svg.box(attrs, {
height: y1 - y0,
width,
x: x - width / 2,
y: y0,
width,
height: y1 - y0,
}).addClass(className);
} else {
return this.svg.line(attrs, {
@ -84,7 +82,7 @@ export default class BaseTheme {
// INTERNAL HELPERS
renderArrowHead(attrs, {x, y, width, height, dir}) {
renderArrowHead(attrs, {dir, height, width, x, y}) {
const wx = width * dir.dx;
const wy = width * dir.dy;
const hy = height * 0.5 * dir.dx;
@ -98,7 +96,7 @@ export default class BaseTheme {
.attrs(attrs);
}
renderTag(attrs, {x, y, width, height}) {
renderTag(attrs, {height, width, x, y}) {
const {rx, ry} = attrs;
const x2 = x + width;
const y2 = y + height;
@ -148,12 +146,12 @@ export default class BaseTheme {
renderRef(options, position) {
return {
shape: this.svg.box(options, position).attrs({'fill': 'none'}),
fill: this.svg.box(options, position).attrs({'stroke': 'none'}),
mask: this.svg.box(options, position).attrs({
'fill': '#000000',
'stroke': 'none',
}),
fill: this.svg.box(options, position).attrs({'stroke': 'none'}),
shape: this.svg.box(options, position).attrs({'fill': 'none'}),
};
}
@ -163,6 +161,8 @@ export default class BaseTheme {
{x1, y1, x2, y2}
) {
return {
p1: {x: x1, y: y1},
p2: {x: x2, y: y2},
shape: this.svg.el('path')
.attr('d', this.svg.patternedLine(pattern)
.move(x1, y1)
@ -170,15 +170,13 @@ export default class BaseTheme {
.cap()
.asPath())
.attrs(attrs),
p1: {x: x1, y: y1},
p2: {x: x2, y: y2},
};
}
renderRevConnect(
pattern,
attrs,
{x1, y1, x2, y2, xR, rad}
{rad, x1, x2, xR, y1, y2}
) {
const maxRad = (y2 - y1) / 2;
const line = this.svg.patternedLine(pattern)
@ -193,20 +191,20 @@ export default class BaseTheme {
line.arc(xR, (y1 + y2) / 2, Math.PI);
}
return {
p1: {x: x1, y: y1},
p2: {x: x2, y: y2},
shape: this.svg.el('path')
.attr('d', line
.line(x2, y2)
.cap()
.asPath())
.attrs(attrs),
p1: {x: x1, y: y1},
p2: {x: x2, y: y2},
};
}
renderLineDivider(
{lineAttrs},
{x, y, labelWidth, width, height}
{height, labelWidth, width, x, y}
) {
let shape = null;
const yPos = y + height / 2;
@ -238,25 +236,25 @@ export default class BaseTheme {
renderDelayDivider(
{dotSize, gapSize},
{x, y, width, height}
{height, width, x, y}
) {
const mask = this.svg.el('g');
for(let i = 0; i + gapSize <= height; i += dotSize + gapSize) {
mask.add(this.svg.box({
'fill': '#000000',
}, {
height: gapSize,
width,
x,
y: y + i,
width,
height: gapSize,
}));
}
return {mask};
}
renderTearDivider(
{fadeBegin, fadeSize, pattern, zigWidth, zigHeight, lineAttrs},
{x, y, labelWidth, labelHeight, width, height, env}
{fadeBegin, fadeSize, lineAttrs, pattern, zigHeight, zigWidth},
{env, height, labelHeight, labelWidth, width, x, y}
) {
const maskGradID = env.addDef('tear-grad', () => {
const px = 100 / width;
@ -285,24 +283,24 @@ export default class BaseTheme {
this.svg.box({
'fill': 'url(#' + maskGradID + ')',
}, {
height: height + 10,
width,
x,
y: y - 5,
width,
height: height + 10,
})
);
const shapeMaskID = env.addDef(shapeMask);
if(labelWidth > 0) {
shapeMask.add(this.svg.box({
'fill': '#000000',
'rx': 2,
'ry': 2,
'fill': '#000000',
}, {
'height': labelHeight + 2,
'width': labelWidth,
'x': x + (width - labelWidth) / 2,
'y': y + (height - labelHeight) / 2 - 1,
'width': labelWidth,
'height': labelHeight + 2,
}));
}
@ -345,6 +343,6 @@ export default class BaseTheme {
'fill': '#000000',
});
}
return {shape, mask};
return {mask, shape};
}
}

View File

@ -1,5 +1,3 @@
/* eslint-disable complexity */ // Temporary ignore while switching linter
function makeCanvas(width, height) {
window.devicePixelRatio = 1;
const canvas = document.createElement('canvas');
@ -36,6 +34,20 @@ function proportionalSize(
}
}
function resize_ranges(l, h) {
/* eslint-disable no-bitwise */ // Faster than Math.floor
const li = (l | 0);
const hi = (h | 0);
/* eslint-enable no-bitwise */
const lm = (hi === li) ? (h - l) : (li + 1 - l);
const hm = h - hi;
if(hm < 0.001) {
return {hi: hi - 1, hm: 1, li, lm};
} else {
return {hi, hm, li, lm};
}
}
export default class ImageRegion {
constructor(width, height, values, {
origin = 0,
@ -296,40 +308,20 @@ export default class ImageRegion {
return sum;
}
resize(size) {
const {width, height} = this.getProportionalSize(size);
if(width === this.width && height === this.height) {
return this;
}
_resize(width, height) {
const {dim} = this;
const values = new Float32Array(width * height * dim);
const mx = this.width / width;
const my = this.height / height;
const values = new Float32Array(width * height * dim);
const norm = 1 / (mx * my);
function ranges(l, h) {
/* eslint-disable no-bitwise */ // Faster than Math.floor
const li = (l | 0);
const hi = (h | 0);
/* eslint-enable no-bitwise */
const lm = (hi === li) ? (h - l) : (li + 1 - l);
const hm = h - hi;
if(hm < 0.001) {
return {hi: hi - 1, hm: 1, li, lm};
} else {
return {hi, hm, li, lm};
}
}
const xrs = [];
for(let x = 0; x < width; ++ x) {
xrs[x] = ranges(x * mx, (x + 1) * mx);
xrs[x] = resize_ranges(x * mx, (x + 1) * mx);
}
for(let y = 0; y < height; ++ y) {
const yr = ranges(y * my, (y + 1) * my);
const yr = resize_ranges(y * my, (y + 1) * my);
for(let x = 0; x < width; ++ x) {
const xr = xrs[x];
const p = (y * width + x) * dim;
@ -342,6 +334,15 @@ export default class ImageRegion {
return new ImageRegion(width, height, values, {dim});
}
resize(size) {
const {width, height} = this.getProportionalSize(size);
if(width === this.width && height === this.height) {
return this;
}
return this._resize(width, height);
}
getSuggestedChannels({blue = null, green = null, red = null} = {}) {
return {
blue: (blue === null) ? (this.dim - 1) : blue,

View File

@ -83,7 +83,7 @@ module.exports = {
'lines-between-class-members': ['error'],
'max-depth': ['error', 4],
'max-len': ['error', {'ignoreUrls': true}],
'max-lines': ['error', 600],
'max-lines': ['error', 800],
'max-nested-callbacks': ['error', 4], // Includes jasmine blocks
'max-params': ['error', 4],
'max-statements': ['error', 20],

View File

@ -1,5 +1,3 @@
/* eslint-disable max-lines */
import DOMWrapper from '../../scripts/core/DOMWrapper.mjs';
const DELAY_AGENTCHANGE = 500;

View File

@ -504,8 +504,6 @@
}
}
/* eslint-disable max-lines */
const DELAY_AGENTCHANGE = 500;
const DELAY_STAGECHANGE = 250;
const PNG_RESOLUTION = 4;