mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-14 06:59:01 -04:00
Line wrap comments in generated code.
This commit is contained in:
parent
4ba2b1bea3
commit
3012d52808
7 changed files with 180 additions and 155 deletions
|
@ -70,6 +70,13 @@ Blockly.Generator.prototype.STATEMENT_PREFIX = null;
|
|||
*/
|
||||
Blockly.Generator.prototype.INDENT = ' ';
|
||||
|
||||
/**
|
||||
* Maximum length for a comment before wrapping. Does not account for
|
||||
* indenting level.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.Generator.prototype.COMMENT_WRAP = 60;
|
||||
|
||||
/**
|
||||
* Generate code for all blocks in the workspace to the specified language.
|
||||
* @param {Blockly.Workspace} workspace Workspace to generate code from.
|
||||
|
|
156
core/tooltip.js
156
core/tooltip.js
|
@ -239,7 +239,7 @@ Blockly.Tooltip.show_ = function() {
|
|||
while (goog.isFunction(tip)) {
|
||||
tip = tip();
|
||||
}
|
||||
tip = Blockly.Tooltip.wrap_(tip, Blockly.Tooltip.LIMIT);
|
||||
tip = Blockly.utils.wrap(tip, Blockly.Tooltip.LIMIT);
|
||||
// Create new text, line by line.
|
||||
var lines = tip.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
|
@ -282,157 +282,3 @@ Blockly.Tooltip.show_ = function() {
|
|||
Blockly.Tooltip.DIV.style.top = anchorY + 'px';
|
||||
Blockly.Tooltip.DIV.style.left = anchorX + 'px';
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap text to the specified width.
|
||||
* @param {string} text Text to wrap.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {string} Wrapped text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.wrap_ = function(text, limit) {
|
||||
if (text.length <= limit) {
|
||||
// Short text, no need to wrap.
|
||||
return text;
|
||||
}
|
||||
// Split the text into words.
|
||||
var words = text.trim().split(/\s+/);
|
||||
// Set limit to be the length of the largest word.
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
if (words[i].length > limit) {
|
||||
limit = words[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
var lastScore;
|
||||
var score = -Infinity;
|
||||
var lastText;
|
||||
var lineCount = 1;
|
||||
do {
|
||||
lastScore = score;
|
||||
lastText = text;
|
||||
// Create a list of booleans representing if a space (false) or
|
||||
// a break (true) appears after each word.
|
||||
var wordBreaks = [];
|
||||
// Seed the list with evenly spaced linebreaks.
|
||||
var steps = words.length / lineCount;
|
||||
var insertedBreaks = 1;
|
||||
for (var i = 0; i < words.length - 1; i++) {
|
||||
if (insertedBreaks < (i + 1.5) / steps) {
|
||||
insertedBreaks++;
|
||||
wordBreaks[i] = true;
|
||||
} else {
|
||||
wordBreaks[i] = false;
|
||||
}
|
||||
}
|
||||
wordBreaks = Blockly.Tooltip.wrapMutate_(words, wordBreaks, limit);
|
||||
score = Blockly.Tooltip.wrapScore_(words, wordBreaks, limit);
|
||||
text = Blockly.Tooltip.wrapToText_(words, wordBreaks);
|
||||
lineCount++;
|
||||
} while (score > lastScore);
|
||||
return lastText;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute a score for how good the wrapping is.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {number} Larger the better.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.wrapScore_ = function(words, wordBreaks, limit) {
|
||||
// If this function becomes a performance liability, add caching.
|
||||
// Compute the length of each line.
|
||||
var lineLengths = [0];
|
||||
var linePunctuation = [];
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
lineLengths[lineLengths.length - 1] += words[i].length;
|
||||
if (wordBreaks[i] === true) {
|
||||
lineLengths.push(0);
|
||||
linePunctuation.push(words[i].charAt(words[i].length - 1));
|
||||
} else if (wordBreaks[i] === false) {
|
||||
lineLengths[lineLengths.length - 1]++;
|
||||
}
|
||||
}
|
||||
var maxLength = Math.max.apply(Math, lineLengths);
|
||||
|
||||
var score = 0;
|
||||
for (var i = 0; i < lineLengths.length; i++) {
|
||||
// Optimize for width.
|
||||
// -2 points per char over limit (scaled to the power of 1.5).
|
||||
score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2;
|
||||
// Optimize for even lines.
|
||||
// -1 point per char smaller than max (scaled to the power of 1.5).
|
||||
score -= Math.pow(maxLength - lineLengths[i], 1.5);
|
||||
// Optimize for structure.
|
||||
// Add score to line endings after punctuation.
|
||||
if ('.?!'.indexOf(linePunctuation[i]) != -1) {
|
||||
score += limit / 3;
|
||||
} else if (',;)]}'.indexOf(linePunctuation[i]) != -1) {
|
||||
score += limit / 4;
|
||||
}
|
||||
}
|
||||
// All else being equal, the last line should not be longer than the
|
||||
// previous line. For example, this looks wrong:
|
||||
// aaa bbb
|
||||
// ccc ddd eee
|
||||
if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <=
|
||||
lineLengths[lineLengths.length - 2]) {
|
||||
score += 0.5;
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutate the array of line break locations until an optimal solution is found.
|
||||
* No line breaks are added or deleted, they are simply moved around.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {!Array.<boolean>} New array of optimal line breaks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.wrapMutate_ = function(words, wordBreaks, limit) {
|
||||
var bestScore = Blockly.Tooltip.wrapScore_(words, wordBreaks, limit);
|
||||
var bestBreaks;
|
||||
// Try shifting every line break forward or backward.
|
||||
for (var i = 0; i < wordBreaks.length - 1; i++) {
|
||||
if (wordBreaks[i] == wordBreaks[i + 1]) {
|
||||
continue;
|
||||
}
|
||||
var mutatedWordBreaks = [].concat(wordBreaks);
|
||||
mutatedWordBreaks[i] = !mutatedWordBreaks[i];
|
||||
mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1];
|
||||
var mutatedScore =
|
||||
Blockly.Tooltip.wrapScore_(words, mutatedWordBreaks, limit);
|
||||
if (mutatedScore > bestScore) {
|
||||
bestScore = mutatedScore;
|
||||
bestBreaks = mutatedWordBreaks;
|
||||
}
|
||||
}
|
||||
if (bestBreaks) {
|
||||
// Found an improvement. See if it may be improved further.
|
||||
return Blockly.Tooltip.wrapMutate_(words, bestBreaks, limit);
|
||||
}
|
||||
// No improvements found. Done.
|
||||
return wordBreaks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reassemble the array of words into text, with the specified line breaks.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @return {string} Plain text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Tooltip.wrapToText_ = function(words, wordBreaks) {
|
||||
var text = [];
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
text.push(words[i]);
|
||||
if (wordBreaks[i] !== undefined) {
|
||||
text.push(wordBreaks[i] ? '\n' : ' ');
|
||||
}
|
||||
}
|
||||
return text.join('');
|
||||
};
|
||||
|
|
168
core/utils.js
168
core/utils.js
|
@ -495,3 +495,171 @@ Blockly.genUid = function() {
|
|||
*/
|
||||
Blockly.genUid.soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
/**
|
||||
* Wrap text to the specified width.
|
||||
* @param {string} text Text to wrap.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {string} Wrapped text.
|
||||
*/
|
||||
Blockly.utils.wrap = function(text, limit) {
|
||||
var lines = text.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = Blockly.utils.wrap_line_(lines[i], limit);
|
||||
}
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap single line of text to the specified width.
|
||||
* @param {string} text Text to wrap.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {string} Wrapped text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.wrap_line_ = function(text, limit) {
|
||||
if (text.length <= limit) {
|
||||
// Short text, no need to wrap.
|
||||
return text;
|
||||
}
|
||||
// Split the text into words.
|
||||
var words = text.trim().split(/\s+/);
|
||||
// Set limit to be the length of the largest word.
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
if (words[i].length > limit) {
|
||||
limit = words[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
var lastScore;
|
||||
var score = -Infinity;
|
||||
var lastText;
|
||||
var lineCount = 1;
|
||||
do {
|
||||
lastScore = score;
|
||||
lastText = text;
|
||||
// Create a list of booleans representing if a space (false) or
|
||||
// a break (true) appears after each word.
|
||||
var wordBreaks = [];
|
||||
// Seed the list with evenly spaced linebreaks.
|
||||
var steps = words.length / lineCount;
|
||||
var insertedBreaks = 1;
|
||||
for (var i = 0; i < words.length - 1; i++) {
|
||||
if (insertedBreaks < (i + 1.5) / steps) {
|
||||
insertedBreaks++;
|
||||
wordBreaks[i] = true;
|
||||
} else {
|
||||
wordBreaks[i] = false;
|
||||
}
|
||||
}
|
||||
wordBreaks = Blockly.utils.wrapMutate_(words, wordBreaks, limit);
|
||||
score = Blockly.utils.wrapScore_(words, wordBreaks, limit);
|
||||
text = Blockly.utils.wrapToText_(words, wordBreaks);
|
||||
lineCount++;
|
||||
} while (score > lastScore);
|
||||
return lastText;
|
||||
};
|
||||
|
||||
/**
|
||||
* Compute a score for how good the wrapping is.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {number} Larger the better.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.wrapScore_ = function(words, wordBreaks, limit) {
|
||||
// If this function becomes a performance liability, add caching.
|
||||
// Compute the length of each line.
|
||||
var lineLengths = [0];
|
||||
var linePunctuation = [];
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
lineLengths[lineLengths.length - 1] += words[i].length;
|
||||
if (wordBreaks[i] === true) {
|
||||
lineLengths.push(0);
|
||||
linePunctuation.push(words[i].charAt(words[i].length - 1));
|
||||
} else if (wordBreaks[i] === false) {
|
||||
lineLengths[lineLengths.length - 1]++;
|
||||
}
|
||||
}
|
||||
var maxLength = Math.max.apply(Math, lineLengths);
|
||||
|
||||
var score = 0;
|
||||
for (var i = 0; i < lineLengths.length; i++) {
|
||||
// Optimize for width.
|
||||
// -2 points per char over limit (scaled to the power of 1.5).
|
||||
score -= Math.pow(Math.abs(limit - lineLengths[i]), 1.5) * 2;
|
||||
// Optimize for even lines.
|
||||
// -1 point per char smaller than max (scaled to the power of 1.5).
|
||||
score -= Math.pow(maxLength - lineLengths[i], 1.5);
|
||||
// Optimize for structure.
|
||||
// Add score to line endings after punctuation.
|
||||
if ('.?!'.indexOf(linePunctuation[i]) != -1) {
|
||||
score += limit / 3;
|
||||
} else if (',;)]}'.indexOf(linePunctuation[i]) != -1) {
|
||||
score += limit / 4;
|
||||
}
|
||||
}
|
||||
// All else being equal, the last line should not be longer than the
|
||||
// previous line. For example, this looks wrong:
|
||||
// aaa bbb
|
||||
// ccc ddd eee
|
||||
if (lineLengths.length > 1 && lineLengths[lineLengths.length - 1] <=
|
||||
lineLengths[lineLengths.length - 2]) {
|
||||
score += 0.5;
|
||||
}
|
||||
return score;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutate the array of line break locations until an optimal solution is found.
|
||||
* No line breaks are added or deleted, they are simply moved around.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @param {number} limit Width to wrap each line.
|
||||
* @return {!Array.<boolean>} New array of optimal line breaks.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.wrapMutate_ = function(words, wordBreaks, limit) {
|
||||
var bestScore = Blockly.utils.wrapScore_(words, wordBreaks, limit);
|
||||
var bestBreaks;
|
||||
// Try shifting every line break forward or backward.
|
||||
for (var i = 0; i < wordBreaks.length - 1; i++) {
|
||||
if (wordBreaks[i] == wordBreaks[i + 1]) {
|
||||
continue;
|
||||
}
|
||||
var mutatedWordBreaks = [].concat(wordBreaks);
|
||||
mutatedWordBreaks[i] = !mutatedWordBreaks[i];
|
||||
mutatedWordBreaks[i + 1] = !mutatedWordBreaks[i + 1];
|
||||
var mutatedScore =
|
||||
Blockly.utils.wrapScore_(words, mutatedWordBreaks, limit);
|
||||
if (mutatedScore > bestScore) {
|
||||
bestScore = mutatedScore;
|
||||
bestBreaks = mutatedWordBreaks;
|
||||
}
|
||||
}
|
||||
if (bestBreaks) {
|
||||
// Found an improvement. See if it may be improved further.
|
||||
return Blockly.utils.wrapMutate_(words, bestBreaks, limit);
|
||||
}
|
||||
// No improvements found. Done.
|
||||
return wordBreaks;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reassemble the array of words into text, with the specified line breaks.
|
||||
* @param {!Array.<string>} words Array of each word.
|
||||
* @param {!Array.<boolean>} wordBreaks Array of line breaks.
|
||||
* @return {string} Plain text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.wrapToText_ = function(words, wordBreaks) {
|
||||
var text = [];
|
||||
for (var i = 0; i < words.length; i++) {
|
||||
text.push(words[i]);
|
||||
if (wordBreaks[i] !== undefined) {
|
||||
text.push(wordBreaks[i] ? '\n' : ' ');
|
||||
}
|
||||
}
|
||||
return text.join('');
|
||||
};
|
||||
|
|
|
@ -192,6 +192,7 @@ Blockly.JavaScript.scrub_ = function(block, code) {
|
|||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.wrap(comment, this.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
if (block.getProcedureDef) {
|
||||
// Use a comment block for function comments.
|
||||
|
|
|
@ -164,6 +164,7 @@ Blockly.Lua.scrub_ = function(block, code) {
|
|||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.wrap(comment, this.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
commentCode += Blockly.Lua.prefixLines(comment, '-- ') + '\n';
|
||||
}
|
||||
|
|
|
@ -167,6 +167,7 @@ Blockly.PHP.scrub_ = function(block, code) {
|
|||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.wrap(comment, this.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
commentCode += Blockly.PHP.prefixLines(comment, '// ') + '\n';
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ Blockly.Python.scrub_ = function(block, code) {
|
|||
if (!block.outputConnection || !block.outputConnection.targetConnection) {
|
||||
// Collect comment for this block.
|
||||
var comment = block.getCommentText();
|
||||
comment = Blockly.utils.wrap(comment, this.COMMENT_WRAP - 3);
|
||||
if (comment) {
|
||||
if (block.getProcedureDef) {
|
||||
// Use a comment block for function comments.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue