На основе примера, показанного в анимированном gif, кажется, что ваша процедура должна
- обрабатывать выделение
- задавать свойства, если выбранная область имеет некоторый формат (например, подчеркнутый )
- установить свойства, если выбранная область НЕ имеет какой-либо формат (например, не подчеркнута)
- fini sh как можно быстрее
и ваш пример кода достигает всех этих целей, кроме последней.
Проблема в том, что вы вызываете функции text.set...()
в каждой позиции индекса. Каждый вызов является синхронным и блокирует код до тех пор, пока документ не будет обновлен, поэтому время выполнения увеличивается линейно с каждым символом в выделенном фрагменте.
Я предлагаю создать коллекцию поддиапазонов из диапазона выбора, а затем для в каждом поддиапазоне используется text.set...(subrange.start, subrange.end)
для применения форматирования. Теперь время выполнения будет зависеть от фрагментов символов, а не от отдельных символов. то есть, вы обновляете только тогда, когда форматирование переключается с подчеркнутого на неподчеркнутый в вашем примере.
Вот пример кода, который реализует эту идею поддиапазона. Я разделил определенную c функцию предиката (text.isUnderline
) и определенные c эффекты форматирования на их собственные функции, чтобы отделить общую идею от конкретной реализации c.
// run this function with selection
function transformUnderlinedToBoldAndYellow() {
transformSelection("isUnderline", boldYellowOrSmall);
}
function transformSelection(stylePredicateKey, stylingFunction) {
const selectedText = DocumentApp.getActiveDocument().getSelection();
if (!selectedText) return;
const getStyledSubRanges = makeStyledSubRangeReducer(stylePredicateKey);
selectedText.getRangeElements()
.reduce(getStyledSubRanges, [])
.forEach(stylingFunction);
}
function makeStyledSubRangeReducer(stylePredicateKey) {
return function(ranges, rangeElement) {
const {text, start, end} = unwrapRangeElement(rangeElement);
if (start >= end) return ranges; // filter out empty selections
const range = {
text, start, end,
styled: [], notStyled: [] // we will extend our range with subranges
};
const getKey = (isStyled) => isStyled ? "styled" : "notStyled";
let currentKey = getKey(text[stylePredicateKey](start));
range[currentKey].unshift({start: start});
for (let index = start + 1; index <= end; ++index) {
const isStyled = text[stylePredicateKey](index);
if (getKey(isStyled) !== currentKey) { // we are switching styles
range[currentKey][0].end = index - 1; // note end of this style
currentKey = getKey(isStyled);
range[currentKey].unshift({start: index}); // start new style range
}
}
ranges.push(range);
return ranges;
}
}
// a helper function to unwrap a range selection, deals with isPartial,
// maps RangeElement => {text, start, end}
function unwrapRangeElement(rangeElement) {
const isPartial = rangeElement.isPartial();
const text = rangeElement.getElement().asText();
return {
text: text,
start: isPartial
? rangeElement.getStartOffset()
: 0,
end: isPartial
? rangeElement.getEndOffsetInclusive()
: text.getText().length - 1
};
}
// apply specific formatting to satisfy the example
function boldYellowOrSmall(range) {
const {text, start, end, styled, notStyled} = range;
styled.forEach(function setTextBoldAndYellow(range) {
text.setBold(range.start, range.end || end, true);
text.setBackgroundColor(range.start, range.end || end, '#ffff00');
});
notStyled.forEach(function setTextSmall(range) {
text.setFontSize(range.start, range.end || end, 8);
});
}