Skip to content

Commit

Permalink
Newline parsing now uses a new custom input.
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnesky committed Aug 4, 2023
1 parent b28321d commit c52b7e1
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 78 deletions.
61 changes: 40 additions & 21 deletions core/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {Size} from './utils/size.js';
import type {VariableModel} from './variable_model.js';
import type {Workspace} from './workspace.js';
import {DummyInput} from './inputs/dummy_input.js';
import {EndRowInput} from './inputs/end_row_input.js';
import {ValueInput} from './inputs/value_input.js';
import {StatementInput} from './inputs/statement_input.js';
import {IconType} from './icons/icon_types.js';
Expand Down Expand Up @@ -1339,6 +1340,12 @@ export class Block implements IASTNodeLocation, IDeletable {
return true;
}
}
for (let i = 0; i < this.inputList.length; i++) {
if (this.inputList[i] instanceof EndRowInput) {
// A row-end input is present. Inline value inputs.
return true;
}
}
return false;
}

Expand Down Expand Up @@ -1560,6 +1567,17 @@ export class Block implements IASTNodeLocation, IDeletable {
return this.appendInput(new DummyInput(name, this));
}

/**
* Appends an input that ends the row.
*
* @param name Optional language-neutral identifier which may used to find
* this input again. Should be unique to this block.
* @returns The input object created.
*/
appendEndRowInput(name = ''): Input {
return this.appendInput(new EndRowInput(name, this));
}

/**
* Appends the given input row.
*
Expand Down Expand Up @@ -1628,8 +1646,8 @@ export class Block implements IASTNodeLocation, IDeletable {
this.interpolate_(
json['message' + i],
json['args' + i] || [],
// Backwards compatibility: lastDummyAlign aliases implicitDummyAlign.
json['lastDummyAlign' + i] || json['implicitDummyAlign' + i],
// Backwards compatibility: lastDummyAlign aliases implicitAlign.
json['lastDummyAlign' + i] || json['implicitAlign' + i],
warningPrefix,
);
i++;
Expand Down Expand Up @@ -1766,20 +1784,19 @@ export class Block implements IASTNodeLocation, IDeletable {
* @param message Text contains interpolation tokens (%1, %2, ...) that match
* with fields or inputs defined in the args array.
* @param args Array of arguments to be interpolated.
* @param implicitDummyAlign If an implicit dummy input is added at the end or
* in place of newline tokens, how should it be aligned?
* @param implicitAlign If an implicit input is added at the end or in place
* of newline tokens, how should it be aligned?
* @param warningPrefix Warning prefix string identifying block.
*/
private interpolate_(
message: string,
args: AnyDuringMigration[],
implicitDummyAlign: string | undefined,
implicitAlign: string | undefined,
warningPrefix: string,
) {
const tokens = parsing.tokenizeInterpolation(message);
this.validateTokens_(tokens, args.length);
const elements = this.interpolateArguments_(
tokens, args, implicitDummyAlign);
const elements = this.interpolateArguments_(tokens, args, implicitAlign);

// An array of [field, fieldName] tuples.
const fieldStack = [];
Expand Down Expand Up @@ -1857,19 +1874,20 @@ export class Block implements IASTNodeLocation, IDeletable {

/**
* Inserts args in place of numerical tokens. String args are converted to
* JSON that defines a label field. If necessary an extra dummy input is added
* to the end of the elements.
* JSON that defines a label field. Newline characters are converted to
* end-row inputs, and if necessary an extra dummy input is added to the end
* of the elements.
*
* @param tokens The tokens to interpolate
* @param args The arguments to insert.
* @param implicitDummyAlign The alignment any added implicit dummies input
* should have, if we are required to add one.
* @param implicitAlign The alignment to use for any implicitly added end-row
* or dummy inputs, if necessary.
* @returns The JSON definitions of field and inputs to add to the block.
*/
private interpolateArguments_(
tokens: Array<string | number>,
args: Array<AnyDuringMigration | string>,
implicitDummyAlign: string | undefined,
implicitAlign: string | undefined,
): AnyDuringMigration[] {
const elements = [];
for (let i = 0; i < tokens.length; i++) {
Expand All @@ -1880,10 +1898,10 @@ export class Block implements IASTNodeLocation, IDeletable {
// Args can be strings, which is why this isn't elseif.
if (typeof element === 'string') {
if (element === '\n') {
// Convert newline tokens to dummies with endOfRow enabled.
const newlineInput = {'type': 'input_dummy', 'endOfRow': true};
if (implicitDummyAlign) {
(newlineInput as AnyDuringMigration)['align'] = implicitDummyAlign;
// Convert newline tokens to end-row inputs.
const newlineInput = {'type': 'input_end_row'};
if (implicitAlign) {
(newlineInput as AnyDuringMigration)['align'] = implicitAlign;
}
element = newlineInput as AnyDuringMigration;
} else {
Expand All @@ -1906,8 +1924,8 @@ export class Block implements IASTNodeLocation, IDeletable {
)
) {
const dummyInput = {'type': 'input_dummy'};
if (implicitDummyAlign) {
(dummyInput as AnyDuringMigration)['align'] = implicitDummyAlign;
if (implicitAlign) {
(dummyInput as AnyDuringMigration)['align'] = implicitAlign;
}
elements.push(dummyInput);
}
Expand Down Expand Up @@ -1971,6 +1989,9 @@ export class Block implements IASTNodeLocation, IDeletable {
case 'input_dummy':
input = this.appendDummyInput(element['name']);
break;
case 'input_end_row':
input = this.appendEndRowInput(element['name']);
break;
default: {
input = this.appendInputFromRegistry(element['type'], element['name']);
break;
Expand All @@ -1994,9 +2015,6 @@ export class Block implements IASTNodeLocation, IDeletable {
input.setAlign(alignment);
}
}
if (element['endOfRow'] != undefined) {
input.setEndOfRow(!!element['endOfRow']);
}
return input;
}

Expand All @@ -2012,6 +2030,7 @@ export class Block implements IASTNodeLocation, IDeletable {
str === 'input_value' ||
str === 'input_statement' ||
str === 'input_dummy' ||
str === 'input_end_row' ||
registry.hasItem(registry.Type.INPUT, str)
);
}
Expand Down
3 changes: 2 additions & 1 deletion core/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
import {Align} from './inputs/align.js';
import {Input} from './inputs/input.js';
import {DummyInput} from './inputs/dummy_input.js';
import {EndRowInput} from './inputs/end_row_input.js';
import {StatementInput} from './inputs/statement_input.js';
import {ValueInput} from './inputs/value_input.js';
import {inputTypes} from './inputs/input_types.js';

export {Align, Input, DummyInput, StatementInput, ValueInput, inputTypes};
export {Align, Input, DummyInput, EndRowInput, StatementInput, ValueInput, inputTypes};
31 changes: 31 additions & 0 deletions core/inputs/end_row_input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type {Block} from '../block.js';
import {Input} from './input.js';
import {inputTypes} from './input_types.js';

/**
* Represents an input on a block that is always the last input in the row. Any
* following input will be rendered on the next row even if the block has inline
* inputs. Any newline character in a JSON block definition's message will
* automatically be parsed as an end-row input.
*/
export class EndRowInput extends Input {
readonly type = inputTypes.END_ROW;

/**
* @param name Language-neutral identifier which may used to find this input
* again.
* @param block The block containing this input.
*/
constructor(
public name: string,
block: Block,
) {
super(name, block);
}
}
31 changes: 0 additions & 31 deletions core/inputs/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ export class Input {
fieldRow: Field[] = [];
/** Alignment of input's fields (left, right or centre). */
align = Align.LEFT;

/**
* If true, any following input should always be rendered on a new row even if
* this input is rendered inline.
*/
private endOfRow = false;

/** Is the input visible? */
private visible = true;
Expand Down Expand Up @@ -252,31 +246,6 @@ export class Input {
return this;
}

/**
* If true, any following input should always be rendered on a new row even if
* this input is rendered inline.
*
* @returns True if this input should be the last input on its row.
*/
isEndOfRow(): boolean {
return this.endOfRow;
}

/**
* Set whether the input should be the last input on its row.
*
* @param endOfRow Whether the input should be the last input on its row.
* @returns The input being modified (to allow chaining).
*/
setEndOfRow(endOfRow: boolean): Input {
this.endOfRow = endOfRow;
if (this.sourceBlock.rendered) {
const sourceBlock = this.sourceBlock as BlockSvg;
sourceBlock.queueRender();
}
return this;
}

/**
* Changes the connection's shadow block.
*
Expand Down
4 changes: 4 additions & 0 deletions core/inputs/input_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ export enum inputTypes {
DUMMY = 5,
// An unknown type of input defined by an external developer.
CUSTOM = 6,
// An input with no connections that is always the last input of a row. Any
// subsequent input will be rendered on the next row. Any newline character in
// a JSON block definition's message will be parsed as an end-row input.
END_ROW = 7,
}
9 changes: 5 additions & 4 deletions core/renderers/common/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type {RenderedConnection} from '../../rendered_connection.js';
import type {Measurable} from '../measurables/base.js';
import {BottomRow} from '../measurables/bottom_row.js';
import {DummyInput} from '../../inputs/dummy_input.js';
import {EndRowInput} from '../../inputs/end_row_input.js';
import {ExternalValueInput} from '../measurables/external_value_input.js';
import {Field} from '../measurables/field.js';
import {Hat} from '../measurables/hat.js';
Expand Down Expand Up @@ -326,7 +327,7 @@ export class RenderInfo {
} else if (input instanceof ValueInput) {
activeRow.elements.push(new ExternalValueInput(this.constants_, input));
activeRow.hasExternalInput = true;
} else if (input instanceof DummyInput) {
} else if (input instanceof DummyInput || input instanceof EndRowInput) {
// Dummy inputs have no visual representation, but the information is
// still important.
activeRow.minHeight = Math.max(
Expand Down Expand Up @@ -355,9 +356,9 @@ export class RenderInfo {
if (!lastInput) {
return false;
}
// If the last input was marked as the end of a row, then the next input
// should always be on the next row.
if (lastInput.isEndOfRow()) {
// If the previous input was an end-row input, then any following input
// should always be rendered on the next row.
if (lastInput instanceof EndRowInput) {
return true;
}
// A statement input or an input following one always gets a new row.
Expand Down
3 changes: 2 additions & 1 deletion core/renderers/geras/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {RenderInfo as BaseRenderInfo} from '../common/info.js';
import type {Measurable} from '../measurables/base.js';
import type {BottomRow} from '../measurables/bottom_row.js';
import {DummyInput} from '../../inputs/dummy_input.js';
import {EndRowInput} from '../../inputs/end_row_input.js';
import {ExternalValueInput} from '../measurables/external_value_input.js';
import type {Field} from '../measurables/field.js';
import {InRowSpacer} from '../measurables/in_row_spacer.js';
Expand Down Expand Up @@ -90,7 +91,7 @@ export class RenderInfo extends BaseRenderInfo {
} else if (input instanceof ValueInput) {
activeRow.elements.push(new ExternalValueInput(this.constants_, input));
activeRow.hasExternalInput = true;
} else if (input instanceof DummyInput) {
} else if (input instanceof DummyInput || input instanceof EndRowInput) {
// Dummy inputs have no visual representation, but the information is
// still important.
activeRow.minHeight = Math.max(
Expand Down
2 changes: 1 addition & 1 deletion core/renderers/measurables/row.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class Row {
hasInlineInput = false;

/**
* Whether the row has any dummy inputs.
* Whether the row has any dummy inputs or end-row inputs.
*/
hasDummyInput = false;

Expand Down
12 changes: 9 additions & 3 deletions core/renderers/zelos/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ goog.declareModuleId('Blockly.zelos.RenderInfo');

import type {BlockSvg} from '../../block_svg.js';
import {DummyInput} from '../../inputs/dummy_input.js';
import {EndRowInput} from '../../inputs/end_row_input.js';
import {FieldImage} from '../../field_image.js';
import {FieldLabel} from '../../field_label.js';
import {FieldTextInput} from '../../field_textinput.js';
Expand Down Expand Up @@ -124,6 +125,11 @@ export class RenderInfo extends BaseRenderInfo {
if (!lastInput) {
return false;
}
// If the previous input was an end-row input, then any following input
// should always be rendered on the next row.
if (lastInput instanceof EndRowInput) {
return true;
}
// A statement input or an input following one always gets a new row.
if (
input instanceof StatementInput ||
Expand Down Expand Up @@ -267,9 +273,9 @@ export class RenderInfo extends BaseRenderInfo {
override addInput_(input: Input, activeRow: Row) {
// If we have two dummy inputs on the same row, one aligned left and the
// other right, keep track of the right aligned dummy input so we can add
// padding later.
// padding later. An end-row input after a dummy input also counts.
if (
input instanceof DummyInput &&
(input instanceof DummyInput || input instanceof EndRowInput) &&
activeRow.hasDummyInput &&
activeRow.align === Align.LEFT &&
input.align === Align.RIGHT
Expand Down Expand Up @@ -502,7 +508,7 @@ export class RenderInfo extends BaseRenderInfo {
const connectionWidth = this.outputConnection.width;
const outerShape = this.outputConnection.shape.type;
const constants = this.constants_;
if (this.isMultiRow && this.inputRows.length > 1) {
if (this.inputRows.length > 1) {
switch (outerShape) {
case constants.SHAPES.ROUND: {
// Special case for multi-row round reporter blocks.
Expand Down
2 changes: 1 addition & 1 deletion core/xml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export function blockToDom(
const input = block.inputList[i];
let container: Element;
let empty = true;
if (input.type === inputTypes.DUMMY) {
if (input.type === inputTypes.DUMMY || input.type === inputTypes.END_ROW) {
continue;
} else {
const childBlock = input.connection!.targetBlock();
Expand Down
Loading

0 comments on commit c52b7e1

Please sign in to comment.