Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/containers/blocks.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,29 @@ class Blocks extends React.Component {
if (fromRuby) {
this.workspace.cleanUp();

// Re-calculate the position of the comments.
this.workspace.getTopComments(false).forEach(comment => {
if (comment.blockId) {
const block = this.workspace.getBlockById(comment.blockId);
if (block) {
const blockXY = block.getRelativeToSurfaceXY();
const blockHW = block.getHeightWidth();
const rtl = this.workspace.RTL;
const x = rtl ?
blockXY.x - blockHW.width - 20 - comment.getWidth() :
blockXY.x + blockHW.width + 20;
const y = blockXY.y;
comment.moveTo(x, y);

const targetComments = this.props.vm.editingTarget.comments;
if (targetComments && targetComments[comment.id]) {
targetComments[comment.id].x = x;
targetComments[comment.id].y = y;
}
}
}
});

this.workspace.getTopBlocks(false).forEach(wsTopBlock => {
const topBlock = blocks.getBlock(wsTopBlock.id);
if (topBlock) {
Expand Down
9 changes: 7 additions & 2 deletions src/lib/ruby-generator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ RubyGenerator.scrub_ = function (block, code) {
let commentCode = '';
if (!this.isConnectedValue(block)) {
let comment = this.getCommentText(block);
if (comment) {
if (comment && !comment.startsWith('@ruby:')) {
commentCode += `${this.prefixLines(comment, '# ')}\n`;
}
const inputs = this.getInputs(block);
Expand All @@ -366,7 +366,12 @@ RubyGenerator.scrub_ = function (block, code) {
if (childBlock) {
comment = this.allNestedComments(childBlock);
if (comment) {
commentCode += this.prefixLines(comment, '# ');
const filteredComment = comment.split('\n')
.filter(line => !line.startsWith('@ruby:'))
.join('\n');
if (filteredComment.trim().length > 0) {
commentCode += this.prefixLines(filteredComment, '# ');
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions src/lib/ruby-generator/looks.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ export default function (Generator) {

Generator.looks_say = function (block) {
const message = Generator.valueToCode(block, 'MESSAGE', Generator.ORDER_NONE) || Generator.quote_('');
const comment = Generator.getCommentText(block);
if (comment) {
if (comment.startsWith('@ruby:method:')) {
const methodName = comment.substring(13);
if (['print', 'puts', 'p'].includes(methodName)) {
return `${methodName}(${message})\n`;
}
}
}
return `say(${message})\n`;
};

Expand Down
33 changes: 33 additions & 0 deletions src/lib/ruby-to-blocks-converter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class RubyToBlocksConverter {
extensionIDs: new Set(),

blocks: {},
comments: {},
blockTypes: {},
localVariables: {},
variables: {},
Expand Down Expand Up @@ -283,11 +284,20 @@ class RubyToBlocksConverter {
Object.keys(target.blocks._blocks).forEach(blockId => {
target.blocks.deleteBlock(blockId);
});
target.comments = {};

Object.keys(this._context.blocks).forEach(blockId => {
target.blocks.createBlock(this._context.blocks[blockId]);
});

Object.keys(this._context.comments).forEach(commentId => {
const comment = this._context.comments[commentId];
target.createComment(
comment.id, comment.blockId, comment.text,
comment.x, comment.y, comment.width, comment.height, comment.minimized
);
});

this.vm.emitWorkspaceUpdate();
});
}
Expand Down Expand Up @@ -878,6 +888,25 @@ class RubyToBlocksConverter {
return null;
}

createComment (text, blockId, x = 0, y = 0, minimized = true) {
return this._createComment(text, blockId, x, y, minimized);
}

_createComment (text, blockId, x = 0, y = 0, minimized = true) {
const id = Blockly.utils.genUid();
this._context.comments[id] = {
id: id,
text: text,
blockId: blockId,
x: x,
y: y,
width: 200,
height: 200,
minimized: minimized
};
return id;
}

createBlock (opcode, type, attributes = {}) {
return this._createBlock(opcode, type, attributes);
}
Expand All @@ -895,6 +924,9 @@ class RubyToBlocksConverter {
x: void 0,
y: void 0
}, attributes);
if (attributes.comment) {
block.comment = this._createComment(attributes.comment, block.id);
}
this._context.blocks[block.id] = block;
this._context.blockTypes[block.id] = type;
return block;
Expand Down Expand Up @@ -1425,6 +1457,7 @@ class RubyToBlocksConverter {
} else {
result.push(block);
}

if (block.next) {
const b = this._lastBlock(block);
if (this._getBlockType(b) === 'statement') {
Expand Down
11 changes: 11 additions & 0 deletions src/lib/ruby-to-blocks-converter/looks.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ const validateBackdrop = function (backdropName, args) {
*/
const LooksConverter = {
register: function (converter) {
['print', 'puts', 'p'].forEach(methodName => {
converter.registerOnSend('sprite', methodName, 1, params => {
const {args} = params;
if (!converter._isNumberOrStringOrBlock(args[0])) return null;

const block = createBlockWithMessage.call(converter, 'looks_say', args[0], 'Hello!');
block.comment = converter.createComment(`@ruby:method:${methodName}`, block.id, 200, 0);
return block;
});
});

['say', 'think'].forEach(methodName => {
converter.registerOnSend('sprite', methodName, 1, params => {
const {args} = params;
Expand Down
8 changes: 6 additions & 2 deletions test/helpers/expect-to-equal-blocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,12 @@ const expectToEqualBlock = function (context, parent, actualBlock, expectedBlock
expect(blocks.getOpcode(block)).toEqual(expected.opcode);
expect(block.parent).toEqual(parent);
expect(block.shadow).toEqual(expected.shadow === true);
expect(block.x).toEqual(void 0);
expect(block.y).toEqual(void 0);
if (expected.x !== void 0) {
expect(block.x).toEqual(expected.x);
}
if (expected.y !== void 0) {
expect(block.y).toEqual(expected.y);
}

expectToEqualFields(context, blocks.getFields(block), expected.fields);

Expand Down
127 changes: 127 additions & 0 deletions test/unit/lib/ruby-generator/looks.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import RubyGenerator from '../../../../src/lib/ruby-generator';
import LooksBlocks from '../../../../src/lib/ruby-generator/looks';

describe('RubyGenerator/Looks', () => {
beforeEach(() => {
RubyGenerator.cache_ = {
comments: {},
targetCommentTexts: []
};
RubyGenerator.definitions_ = {};
RubyGenerator.functionNames_ = {};
RubyGenerator.currentTarget = null;
LooksBlocks(RubyGenerator);
});

describe('looks_say', () => {
test('normal say', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {
MESSAGE: {}
}
};
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
const expected = 'say("Hello!")\n';
expect(RubyGenerator.looks_say(block)).toEqual(expected);
});

test('with @ruby:method:print', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {
MESSAGE: {}
}
};
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:print' };
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
const expected = 'print("Hello!")\n';
expect(RubyGenerator.looks_say(block)).toEqual(expected);
});

test('with @ruby:method:puts', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {
MESSAGE: {}
}
};
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:puts' };
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
const expected = 'puts("Hello!")\n';
expect(RubyGenerator.looks_say(block)).toEqual(expected);
});

test('with @ruby:method:p', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {
MESSAGE: {}
}
};
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:p' };
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
const expected = 'p("Hello!")\n';
expect(RubyGenerator.looks_say(block)).toEqual(expected);
});

test('with unknown @ruby: tag defaults to say', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {
MESSAGE: {}
}
};
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:unknown' };
RubyGenerator.valueToCode = jest.fn().mockReturnValue('"Hello!"');
const expected = 'say("Hello!")\n';
expect(RubyGenerator.looks_say(block)).toEqual(expected);
});
});

describe('scrub_ (meta-comment filtering)', () => {
test('should filter out @ruby: comments', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {},
next: null
};
RubyGenerator.cache_.comments['block-id'] = { text: '@ruby:method:print' };
RubyGenerator.getInputs = jest.fn().mockReturnValue({});
RubyGenerator.isConnectedValue = jest.fn().mockReturnValue(false);
RubyGenerator.getBlock = jest.fn().mockReturnValue(null);
RubyGenerator.blockToCode = jest.fn().mockReturnValue('');

const code = 'print("Hello!")\n';
const result = RubyGenerator.scrub_(block, code);

// Should NOT contain the comment since it starts with @ruby:
expect(result).toEqual('print("Hello!")\n');
});

test('should keep normal comments', () => {
const block = {
id: 'block-id',
opcode: 'looks_say',
inputs: {},
next: null
};
RubyGenerator.cache_.comments['block-id'] = { text: 'normal comment' };
RubyGenerator.getInputs = jest.fn().mockReturnValue({});
RubyGenerator.isConnectedValue = jest.fn().mockReturnValue(false);
RubyGenerator.getBlock = jest.fn().mockReturnValue(null);
RubyGenerator.blockToCode = jest.fn().mockReturnValue('');

const code = 'say("Hello!")\n';
const result = RubyGenerator.scrub_(block, code);

expect(result).toEqual('# normal comment\nsay("Hello!")\n');
});
});
});
35 changes: 35 additions & 0 deletions test/unit/lib/ruby-to-blocks-converter/looks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1182,4 +1182,39 @@ describe('RubyToBlocksConverter/Looks', () => {
});
});
});

describe('print, puts, p', () => {
['print', 'puts', 'p'].forEach(method => {
test(`${method}("Hello") should become looks_say with comment`, () => {
code = `${method}("Hello")`;
expected = [
{
opcode: 'looks_say',
inputs: [
{
name: 'MESSAGE',
block: expectedInfo.makeText('Hello')
}
]
}
];

// First verify blocks structure
convertAndExpectToEqualBlocks(converter, target, code, expected);

// Then verify comment
// We need to find the block that is 'looks_say' (it should be the first/only top level block)
const blockId = Object.keys(converter.blocks).find(id => converter.blocks[id].opcode === 'looks_say');
const block = converter.blocks[blockId];
expect(block.comment).toBeDefined();

const commentId = block.comment;
expect(converter._context.comments[commentId]).toBeDefined();
expect(converter._context.comments[commentId].text).toEqual(`@ruby:method:${method}`);
expect(converter._context.comments[commentId].x).toEqual(200);
expect(converter._context.comments[commentId].y).toEqual(0);
expect(converter._context.comments[commentId].minimized).toBe(true);
});
});
});
});
Loading