Highlight code generated by blocks

191 views
Skip to first unread message

Antoine Escriva

unread,
Aug 7, 2023, 10:04:04 AM8/7/23
to Blockly
Hello,

I don't know if this subject has already been discussed, but I'd like the related code in the editor to be highlighted when a user clicks on a block. (I use Ace as my editor)
Do you know if this is possible?

Have a nice day,
Antoine E.

Beka Westberg

unread,
Aug 7, 2023, 12:18:03 PM8/7/23
to blo...@googlegroups.com
Hello Antoine!

It depends a lot on what language you're generating and what highlighting APIs your editor has.

Using the code generators directly (e..g javascriptGenerator.forBlock) to try to do string matches probably wouldn't work because each block generates the code for each of its inner blocks.

But you could design a system that defines regexes for each block which match the block's generated code. This could be a mixin you apply to each block, or a separate map from block type to regex. Hopefully your editor has a way you can use a regex to highlight the relevant text =)

I hope that helps! If you have any further questions please reply =)
--Beka

--
You received this message because you are subscribed to the Google Groups "Blockly" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blockly+u...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/blockly/43a7d738-b337-4b38-8f51-909ab3652608n%40googlegroups.com.

Mark Friedman

unread,
Aug 7, 2023, 3:14:24 PM8/7/23
to blo...@googlegroups.com
I've thought a little bit about this problem for a system that I'm developing and, unless I am misunderstanding Beka, I think it would be difficult to define regexes which will match a specific block's generated code.  Afterall, you could have multiple identical blocks, which would therefore generate identical code.  You could, I suppose, try to include more of the context of the code in order to make it more specific to a given block, but I think it then gets more complex to locate the correct position in the generated code.  And you still have the possibility (albeit small) of completely identical blocks with completely identical contexts.  Still, this approach might be workable.

Another approach would be to generate unique comments in the code (e.g. the comment could contain the block's ID) and then just search for that comment to find the appropriate generated code for the block.  Of course, this approach pollutes the generated code that your user sees, which you may not want.  A (more complex) variant of this approach would be to use the search mechanism to build a map of block IDs to positions in the generated code and then strip out the generated comments while adjusting the positions in the ID->position map.

I know that the above is a bit hand-wavy but I think it could work.

Hope this helps.

-Mark


Antoine Escriva

unread,
Aug 8, 2023, 4:51:07 AM8/8/23
to Blockly
Thank you for your comments.
@Beka I use a MicroPython generator for my app and as @Mark said, regexes don't seem usable in my case, since several blocks can generate the same code, or more simply, the same block can be used several times. So it's impossible to make the link between a block and the editor code simply with a regex.
I've also thought about the option of adding comments in the code, but I don't think that's practical. It would make the code less readable.

Unfortunately I don't know the position of the code generated in Ace by any particular block. That's where my problem comes from 😕

ewpa...@gmail.com

unread,
Aug 8, 2023, 8:37:23 AM8/8/23
to Blockly
How is the code ultimately being rendered? If you are rendering it into HTML somewhere, they you could call the generator twice and one time override scrub to wrap the generated code in <span data-blockid="foo">...</span> and then as you highlight each block you can select the corresponding span with document.querySelector("span[data-blockid='foo']") and toggle CSS classes, etc. This is just a rough sketch of how it might work though and I expect it will need to be tweaked in practice.

Cheers,
Evan

Mark Friedman

unread,
Aug 8, 2023, 6:06:38 PM8/8/23
to blo...@googlegroups.com
On Tue, Aug 8, 2023 at 1:52 AM Antoine Escriva <antoi...@gmail.com> wrote:
...
I've also thought about the option of adding comments in the code, but I don't think that's practical. It would make the code less readable.

I agree, and that's why I proposed the more complex mechanism that would strip out the comments.


Unfortunately I don't know the position of the code generated in Ace by any particular block. That's where my problem comes from 😕

Assuming that you built the block->position map that I mentioned, you might be able to use the Ace API to highlight the code.  For example, you could use the Ace indexToPosition() method to get the position of the start of a block's code in the editor.  Note that in this context, a position from the block->position map is what Ace calls an index.  Ace's notion of a position is a pair of row and column. Then you could add the length of the block's generated code to get the index of the end of the block's code and again use Ace's indexToPosition() method to get the position of the end of the block's code in the editor.  You could then use Ace's selection class methods to highlight the code.  There is also an addMarker() method in Ace that, I believe, you could use to highlight the code without selecting it, if that's what you would prefer.

Note that the above is simply a high-level sketch of one approach that you could use.  There are likely other approaches for using the Ace API (as well as others that don't use the Ace API).  Also note that I have no experience with the Ace editor, other than some reading that I just did today, so I could be off base here.  Asking for help in the Ace discussion forum would probably be wise.

Hope this helps.

-Mark

Koen Van Wijk

unread,
Apr 4, 2024, 4:08:30 AM4/4/24
to Blockly

I tried to make an implementation of the proposal, however I got stuck... see below...

I have changed the default scrub_ implementation to have some begin and end tags:

const nextBlock =
    block.nextConnection && block.nextConnection.targetBlock();
  const nextCode = thisOnly ? '' : this.blockToCode(nextBlock);
  return '#beg_block_id# ' + block.id + commentCode + code + '#end_block_id# ' + block.id + nextCode;

For example the code generation of:
blocklt for loop.png
for count in range(10):
    print(i == 2)
Becomes: #beg_block_id# iG~(qK?kuel-!uz.rgY5for count in range(10): #beg_block_id# pvDHl2~Npb|SofB_XS(Hprint(#beg_block_id# h$W)rqtKBHb6]IUA;GiV#beg_block_id# )PfnCu_II?@Nb*HWewVpi#end_block_id# )PfnCu_II?@Nb*HWewVp == #beg_block_id# 2fmz:*M]~z4h{V:@xU4_2#end_block_id# 2fmz:*M]~z4h{V:@xU4_#end_block_id# h$W)rqtKBHb6]IUA;GiV) #end_block_id# pvDHl2~Npb|SofB_XS(H#end_block_id# iG~(qK?kuel-!uz.rgY5"
With some code I was able to get the block_id to the range:
function getBlockIdsAndRanges(codeString, cleanCode) {
  let blockIdDict = {};
  let blockIdStack = [];
  let tagLen = 0;
  // match #beg_block_id# and exactly 20 Chars
  let matchArray = Array.from(codeString.matchAll(/(#beg_block_id#|#end_block_id#) (\S{20})/g));
 
 
  for (let match of matchArray) {
      let cmd = match[1];
      let blockId = match[2];
      console.log(cmd + " " + blockId);
      let tagActualLen = match[0].length;  
      if (cmd === '#beg_block_id#') {
          blockIdStack.push([blockId, match.index - tagLen]);
          tagLen += tagActualLen;
      } else if (cmd === '#end_block_id#') {
          tagLen += tagActualLen;

          if (blockIdStack.length === 0) {
              console.error(`Error: Unmatched 'end' for block_id '${blockId}' at index ${match.index}`);
              continue;
          }
          let lastBlock = blockIdStack.pop();
          let lastBlockId = lastBlock[0];
          let startIndex = lastBlock[1];
          if (lastBlockId !== blockId) {
              console.error(`Error: Mismatched block_id at index ${match.index}`);
          } else {
              blockIdDict[blockId] = {
                  'startIndex': startIndex,
                  'endIndex': match.index + match[0].length - tagLen,
                  'code' : cleanCode.substring(startIndex, match.index + match[0].length - tagLen)
              };
          }
      }
  }

  if (blockIdStack.length !== 0) {
      for (let block of blockIdStack) {
          console.error(`Error: Not ended block_id '${block[0]}' at index ${block[1]}`);
      }
  }

  return blockIdDict;
}


However I get stuck on the last part of the workspaceToCode implementation:
// Final scrubbing of whitespace.
    codeString = codeString.replace(/^\s+\n/, '');
    codeString = codeString.replace(/\n\s+$/, '\n');
    codeString = codeString.replace(/[ \t]+\n/g, '\n');
    return codeString;

This replaces white space and that behave differently when the tags are included and consequently the startIndex and endIndex are wrong. What would be your approach?


Regards,
Koen


Mark Friedman

unread,
Apr 4, 2024, 4:59:01 PM4/4/24
to blo...@googlegroups.com
I'm not sure I completely understand the problem that you're having.  However, I'll try to read between the lines with some educated guesses.  I'm guessing that the cleanCode parameter in your getBlockIdsAndRanges function is the result of generating your code without the tags (and the codeString param is the code with the tags).  Perhaps you're doing that by having two different code generators , one with the usual scrub_ method and another with the scrub_ that adds the tags.  The problem that you might be seeing, then, is that the cleanCode will get affected by the final scrubbing, but the codeString with the tags won't (or at least won't in all the same cases as the cleanCode).  If I've guessed right, then I can see a couple of different ways to deal with the problem:
  1. Have two versions of workspaceToCode.  The usual one for cleanCode and another one where you change the regexps and replacements in the final scrubbing to account for your tags.  That way the codeString and the cleanCode will be more in sync.
  2. Have a single version of workspaceToCode that doesn't do the final scrubbing at all.
If my guesses are wrong, let me know and maybe provide some more details on your code and the problem you're seeing.

Hope this helps.

-Mark


Koen Van Wijk

unread,
Apr 7, 2024, 1:25:29 PM4/7/24
to Blockly
Hi Mark,

Thks indeed I ended up replacing the workspaceToCode to remove the final scrubbing. Now it works!

Regards, Koen

Reply all
Reply to author
Forward
0 new messages