Skip to main content
dublove
Legend
Oct 20, 2026 10:51 AM UTC
Answered

How to determine the continuous execution of code? I only want a piece of code to run once.

I have chosen some text.
First run: I want to restore the line spacing of the selected text to the "original paragraph style" line spacing of the first paragraph.
Second run: Take the line spacing of the first paragraph of text, add a dot, and then apply it to the selected text.
After the third time, repeat the second operation.


It's a bit contradictory, these are two separate actions. We need to ensure that the line spacing is restored to its original state while also increasing it on top of it.

var d = app.activeDocument;
var item = app.selection[0];
//-----------------------A-------------------
//Apply the first leading to the selected text
var OriginalStyleLeading = item.paragraph[0].appliedParagraphStyle.leading;
app.selection[0].text[0].leading = OriginalStyleLeading;

//-----------------------B-------------------
//Adjust line spacing  When operating continuously, only repeat this step
var firstParLeading = item.paragraph[0].appliedParagraphStyle.leading;
app.selection[0].text[0].leading = firstParLeading + 1;

After the second time, only part B can be repeated.

Is it possible use a clock to determine whether to execute for the first time? For example, within 3 seconds, it is considered as the second time.

    m1b
    m1bCorrect answer
    Community Expert
    Oct 21, 2026 1:35 AM UTC

    Hi ​@dublove, here’s my idea: we use an `afterSelectionChanged` event listener to reset the mode. Try this script:

    /**
    * @file Reset Or Increment Leading.js
    *
    * First run on a text selection: resets the leading of the selected text to
    * the leading of each paragraph's applied paragraph style. Other overrides
    * are left untouched.
    *
    * Each subsequent run on the *same* selection adds LEADING_INCREMENT to the
    * leading. Any change of selection (even re-selecting the same text) re-arms
    * the reset.
    *
    * Notes:
    * - runs in a persistent engine, so that its state survives between runs.
    * - an `afterSelectionChanged` listener disarms the increment mode as soon
    * as the selection changes. It stays installed for the session, but
    * does nothing while disarmed.
    * - an insertion point selection targets its whole paragraph.
    * - best assigned to a keyboard shortcut.
    *
    * @author m1b
    * @version 2026-10-21
    * @discussion https://community.adobe.com/questions-671/how-to-determine-the-continuous-execution-of-code-i-only-want-a-piece-of-code-to-run-once-1652200?tid=1652200
    */
    //@targetengine 'resetOrIncrementLeading'

    function main() {

    const LEADING_INCREMENT = 1;
    const LISTENER_NAME = 'resetOrIncrementLeading';

    if (0 === app.documents.length)
    return;

    var target = _getTargetText(app.activeDocument.selection);

    if (!target)
    return alert('Please select some text and try again.');

    var state = _getState();
    var identity = _getSelectionIdentity();
    var chunks = _getParagraphChunks(target);

    if (
    null !== state.identity
    && identity === state.identity
    ) {
    incrementLeading(chunks, LEADING_INCREMENT);
    }
    else {
    resetLeading(chunks);
    }

    // arm: the next run on this same selection will increment
    state.identity = identity;
    _addListener(LISTENER_NAME);

    };

    var savedMeasurementUnit = app.scriptPreferences.measurementUnit;

    try {

    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
    app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Reset Or Increment Leading');

    } catch (error) {
    alert('Reset Or Increment Leading: ' + error.message + ' (line ' + error.line + ')');
    }

    finally {
    app.scriptPreferences.measurementUnit = savedMeasurementUnit;
    }

    /**
    * Sets each chunk's leading to its paragraph style's leading.
    * @author m1b
    * @version 2026-10-21
    * @param {Array<Object>} chunks - the chunks, see _getParagraphChunks.
    */
    function resetLeading(chunks) {

    for (var i = 0; i < chunks.length; i++)
    chunks[i].text.leading = chunks[i].paragraphStyle.leading;

    };

    /**
    * Adds `increment` to each chunk's leading, converting
    * auto leading to its absolute value first.
    * @author m1b
    * @version 2026-10-21
    * @param {Array<Object>} chunks - the chunks, see _getParagraphChunks.
    * @param {Number} increment - the amount to add, in points.
    */
    function incrementLeading(chunks, increment) {

    for (var i = 0; i < chunks.length; i++) {

    var firstCharacter = chunks[i].text.characters[0];
    var leading = firstCharacter.leading;

    if (Leading.AUTO === leading)
    leading = firstCharacter.pointSize * firstCharacter.autoLeading / 100;

    chunks[i].text.leading = leading + increment;

    }

    };

    /**
    * Returns the text to work on, or nothing if the selection isn't text.
    * @author m1b
    * @version 2026-10-21
    * @param {Array} items - the document selection.
    * @returns {Text?}
    */
    function _getTargetText(items) {

    if (1 !== items.length)
    return;

    var item = items[0];

    if ('InsertionPoint' === item.constructor.name)
    return item.paragraphs[0];

    if (/^(Text|Character|Word|Line|Paragraph|TextColumn|TextStyleRange)$/.test(item.constructor.name))
    return item;

    };

    /**
    * Splits `text` at paragraph boundaries, so each part
    * can be paired with its own paragraph style.
    * @author m1b
    * @version 2026-10-21
    * @param {Text} text - the text to split.
    * @returns {Array<Object>} - [{text: Text, paragraphStyle: ParagraphStyle}].
    */
    function _getParagraphChunks(text) {

    var chunks = [];
    var characters = text.characters;
    var start = text.index;
    var end = start + characters.length;
    var paragraphs = text.paragraphs.everyItem().getElements();

    for (var i = 0; i < paragraphs.length; i++) {

    var paragraphStart = paragraphs[i].index;
    var paragraphEnd = paragraphStart + paragraphs[i].characters.length;

    var from = Math.max(start, paragraphStart) - start;
    var to = Math.min(end, paragraphEnd) - start - 1;

    if (to < from)
    continue;

    chunks.push({
    text: characters.itemByRange(from, to).texts[0].getElements()[0],
    paragraphStyle: paragraphs[i].appliedParagraphStyle
    });

    }

    return chunks;

    };

    /**
    * Returns a string identifying the current selection, or
    * an empty string if there isn't exactly one selected item.
    * @author m1b
    * @version 2026-10-21
    * @returns {String}
    */
    function _getSelectionIdentity() {

    try {

    if (
    0 === app.documents.length
    || 1 !== app.selection.length
    )
    return '';

    return app.selection[0].toSpecifier();

    } catch (error) {
    return '';
    }

    };

    /**
    * Returns the persistent state object, creating it if necessary.
    * @author m1b
    * @version 2026-10-21
    * @returns {Object} - {identity: String|null}.
    */
    function _getState() {

    if (undefined === $.global.resetOrIncrementLeadingState)
    $.global.resetOrIncrementLeadingState = { identity: null };

    return $.global.resetOrIncrementLeadingState;

    };

    /**
    * Installs the selection listener, first removing any listeners
    * left by previous runs, so only one runs the current handler code.
    * @author m1b
    * @version 2026-10-21
    * @param {String} name - the name that identifies this script's listeners.
    */
    function _addListener(name) {

    var listeners = app.eventListeners.everyItem().getElements();

    for (var i = listeners.length - 1; i >= 0; i--) {

    if (name === listeners[i].name)
    listeners[i].remove();

    }

    var listener = app.addEventListener('afterSelectionChanged', _onSelectionChanged);
    listener.name = name;

    };

    /**
    * Disarms the increment mode when the selection has really changed.
    * Note: the listener must not remove itself here; InDesign
    * throws "the object no longer exists" if it does.
    * @author m1b
    * @version 2026-10-21
    */
    function _onSelectionChanged() {

    var state = _getState();

    if (null === state.identity)
    return;

    // ignore events that don't actually change the selection
    if (state.identity === _getSelectionIdentity())
    return;

    state.identity = null;

    };

     

    11 replies

    m1b
    Community Expert
    m1bCommunity ExpertCorrect answer
    Community Expert
    Oct 21, 2026 1:35 AM UTC

    Hi ​@dublove, here’s my idea: we use an `afterSelectionChanged` event listener to reset the mode. Try this script:

    /**
    * @file Reset Or Increment Leading.js
    *
    * First run on a text selection: resets the leading of the selected text to
    * the leading of each paragraph's applied paragraph style. Other overrides
    * are left untouched.
    *
    * Each subsequent run on the *same* selection adds LEADING_INCREMENT to the
    * leading. Any change of selection (even re-selecting the same text) re-arms
    * the reset.
    *
    * Notes:
    * - runs in a persistent engine, so that its state survives between runs.
    * - an `afterSelectionChanged` listener disarms the increment mode as soon
    * as the selection changes. It stays installed for the session, but
    * does nothing while disarmed.
    * - an insertion point selection targets its whole paragraph.
    * - best assigned to a keyboard shortcut.
    *
    * @author m1b
    * @version 2026-10-21
    * @discussion https://community.adobe.com/questions-671/how-to-determine-the-continuous-execution-of-code-i-only-want-a-piece-of-code-to-run-once-1652200?tid=1652200
    */
    //@targetengine 'resetOrIncrementLeading'

    function main() {

    const LEADING_INCREMENT = 1;
    const LISTENER_NAME = 'resetOrIncrementLeading';

    if (0 === app.documents.length)
    return;

    var target = _getTargetText(app.activeDocument.selection);

    if (!target)
    return alert('Please select some text and try again.');

    var state = _getState();
    var identity = _getSelectionIdentity();
    var chunks = _getParagraphChunks(target);

    if (
    null !== state.identity
    && identity === state.identity
    ) {
    incrementLeading(chunks, LEADING_INCREMENT);
    }
    else {
    resetLeading(chunks);
    }

    // arm: the next run on this same selection will increment
    state.identity = identity;
    _addListener(LISTENER_NAME);

    };

    var savedMeasurementUnit = app.scriptPreferences.measurementUnit;

    try {

    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
    app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Reset Or Increment Leading');

    } catch (error) {
    alert('Reset Or Increment Leading: ' + error.message + ' (line ' + error.line + ')');
    }

    finally {
    app.scriptPreferences.measurementUnit = savedMeasurementUnit;
    }

    /**
    * Sets each chunk's leading to its paragraph style's leading.
    * @author m1b
    * @version 2026-10-21
    * @param {Array<Object>} chunks - the chunks, see _getParagraphChunks.
    */
    function resetLeading(chunks) {

    for (var i = 0; i < chunks.length; i++)
    chunks[i].text.leading = chunks[i].paragraphStyle.leading;

    };

    /**
    * Adds `increment` to each chunk's leading, converting
    * auto leading to its absolute value first.
    * @author m1b
    * @version 2026-10-21
    * @param {Array<Object>} chunks - the chunks, see _getParagraphChunks.
    * @param {Number} increment - the amount to add, in points.
    */
    function incrementLeading(chunks, increment) {

    for (var i = 0; i < chunks.length; i++) {

    var firstCharacter = chunks[i].text.characters[0];
    var leading = firstCharacter.leading;

    if (Leading.AUTO === leading)
    leading = firstCharacter.pointSize * firstCharacter.autoLeading / 100;

    chunks[i].text.leading = leading + increment;

    }

    };

    /**
    * Returns the text to work on, or nothing if the selection isn't text.
    * @author m1b
    * @version 2026-10-21
    * @param {Array} items - the document selection.
    * @returns {Text?}
    */
    function _getTargetText(items) {

    if (1 !== items.length)
    return;

    var item = items[0];

    if ('InsertionPoint' === item.constructor.name)
    return item.paragraphs[0];

    if (/^(Text|Character|Word|Line|Paragraph|TextColumn|TextStyleRange)$/.test(item.constructor.name))
    return item;

    };

    /**
    * Splits `text` at paragraph boundaries, so each part
    * can be paired with its own paragraph style.
    * @author m1b
    * @version 2026-10-21
    * @param {Text} text - the text to split.
    * @returns {Array<Object>} - [{text: Text, paragraphStyle: ParagraphStyle}].
    */
    function _getParagraphChunks(text) {

    var chunks = [];
    var characters = text.characters;
    var start = text.index;
    var end = start + characters.length;
    var paragraphs = text.paragraphs.everyItem().getElements();

    for (var i = 0; i < paragraphs.length; i++) {

    var paragraphStart = paragraphs[i].index;
    var paragraphEnd = paragraphStart + paragraphs[i].characters.length;

    var from = Math.max(start, paragraphStart) - start;
    var to = Math.min(end, paragraphEnd) - start - 1;

    if (to < from)
    continue;

    chunks.push({
    text: characters.itemByRange(from, to).texts[0].getElements()[0],
    paragraphStyle: paragraphs[i].appliedParagraphStyle
    });

    }

    return chunks;

    };

    /**
    * Returns a string identifying the current selection, or
    * an empty string if there isn't exactly one selected item.
    * @author m1b
    * @version 2026-10-21
    * @returns {String}
    */
    function _getSelectionIdentity() {

    try {

    if (
    0 === app.documents.length
    || 1 !== app.selection.length
    )
    return '';

    return app.selection[0].toSpecifier();

    } catch (error) {
    return '';
    }

    };

    /**
    * Returns the persistent state object, creating it if necessary.
    * @author m1b
    * @version 2026-10-21
    * @returns {Object} - {identity: String|null}.
    */
    function _getState() {

    if (undefined === $.global.resetOrIncrementLeadingState)
    $.global.resetOrIncrementLeadingState = { identity: null };

    return $.global.resetOrIncrementLeadingState;

    };

    /**
    * Installs the selection listener, first removing any listeners
    * left by previous runs, so only one runs the current handler code.
    * @author m1b
    * @version 2026-10-21
    * @param {String} name - the name that identifies this script's listeners.
    */
    function _addListener(name) {

    var listeners = app.eventListeners.everyItem().getElements();

    for (var i = listeners.length - 1; i >= 0; i--) {

    if (name === listeners[i].name)
    listeners[i].remove();

    }

    var listener = app.addEventListener('afterSelectionChanged', _onSelectionChanged);
    listener.name = name;

    };

    /**
    * Disarms the increment mode when the selection has really changed.
    * Note: the listener must not remove itself here; InDesign
    * throws "the object no longer exists" if it does.
    * @author m1b
    * @version 2026-10-21
    */
    function _onSelectionChanged() {

    var state = _getState();

    if (null === state.identity)
    return;

    // ignore events that don't actually change the selection
    if (state.identity === _getSelectionIdentity())
    return;

    state.identity = null;

    };

     

    Translate
    dublove
    dubloveAuthor
    Legend
    Oct 21, 2026 2:16 AM UTC

    Hi ​@m1b 

    This seems to be possible.
    Thank you.
    I slept for a while and woke up in the middle of the night,I  thinking of a simpler method.

    As long as I checked whether the current leading met the leading obtained by the "calculation mode", I would not restore the leading of the first paragraph, but directly read it and continue calculating.

    var item = app.selection[0];
    //-10 to 10 is sufficient.
    var n = [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
    for (i = 0; i < n.length; i++) {
    if (firstParLeading == originalStyLeading + n[i] * 1) {
    newLeading = originalStyLeading + 1
    } else {
    item.paragraph[0].text[0].leading = itemparagraphs[0].appliedParagraphStyle.leading;
    }
    }

     

    Translate
    rob day
    Community Expert
    Community Expert
    Oct 20, 2026 9:43 PM UTC

    I want to restore the line spacing of the selected text to the "original paragraph style"

     

    You might look at the Text or Paragraph object’s clearOverrides() method, which would clear any overrides and restore the text to the assigned style:

     

    https://www.indesignjs.de/indesignapi/indesign/Text.html

     

    //some selected text with style overrides
    var s = app.selection[0]
    s.clearOverrides()

     

    Translate
    Community Expert
    Oct 20, 2026 10:54 AM UTC

    The first operation would not lead to any change after the 1st run so even if it is run it has no effect. If you still want to optimise it you can check if the leading is already not equal to what you are trying to set. Did I miss something?

    Translate
    -Manan
    dublove
    dubloveAuthor
    Legend
    Oct 20, 2026 11:02 AM UTC

    Hi ​@Manan Joshi 

    To restore the leading of the original style, it is because the current leading may have been manually adjusted.

    I need to operate on the original leading.

    Translate
    Community Expert
    Oct 20, 2026 11:07 AM UTC

    That is what I am saying on the first run it will restore it on the subsequent runs if will not do anything because the leading is already set to the value you desire.

    Translate
    -Manan