From 704938a16638b0fc71ff32b843a47a241846a789 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 12:03:30 +0530 Subject: [PATCH 01/12] feat: auto tab spacing detection --- src/editor/Editor.js | 117 +++++++++++- src/editor/EditorHelper/EditorPreferences.js | 5 + src/editor/EditorHelper/IndentHelper.js | 170 ++++++++++++++++++ src/editor/EditorStatusBar.js | 3 +- .../default/Phoenix-prettier/main.js | 2 +- src/nls/root/strings.js | 1 + src/thirdparty/licences/credits.md | 1 + 7 files changed, 289 insertions(+), 10 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 40d36b5f1d..09f7e7e1cc 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -106,6 +106,8 @@ define(function (require, exports, module) { EditorPreferences.init(cmOptions); + const MAX_LINES_TO_SCAN_INDENT = 1000; + const CLOSE_BRACKETS = EditorPreferences.CLOSE_BRACKETS, CLOSE_TAGS = EditorPreferences.CLOSE_TAGS, DRAG_DROP = EditorPreferences.DRAG_DROP, @@ -118,6 +120,7 @@ define(function (require, exports, module) { SPACE_UNITS = EditorPreferences.SPACE_UNITS, STYLE_ACTIVE_LINE = EditorPreferences.STYLE_ACTIVE_LINE, TAB_SIZE = EditorPreferences.TAB_SIZE, + AUTO_TAB_SPACES = EditorPreferences.AUTO_TAB_SPACES, USE_TAB_CHAR = EditorPreferences.USE_TAB_CHAR, WORD_WRAP = EditorPreferences.WORD_WRAP, INDENT_LINE_COMMENT = EditorPreferences.INDENT_LINE_COMMENT, @@ -2275,6 +2278,13 @@ define(function (require, exports, module) { return PreferencesManager.get(prefName, PreferencesManager._buildContext(this.document.file.fullPath, this.document.getLanguage().getId())); }; + + const SPACING_OPTIONS = new Set([ + AUTO_TAB_SPACES, + USE_TAB_CHAR, + SPACE_UNITS, + TAB_SIZE + ]); /** * @private * @@ -2283,19 +2293,42 @@ define(function (require, exports, module) { * @param {string} prefName Name of the preference to visibly update */ Editor.prototype._updateOption = function (prefName) { - var oldValue = this._currentOptions[prefName], + let oldValue = this._currentOptions[prefName], newValue = this._getOption(prefName); + const fullPath = this.document.file.fullPath; + if(SPACING_OPTIONS.has(prefName)){ + if(prefName === SPACE_UNITS){ + newValue = Editor.getSpaceUnits(fullPath); + } else if(prefName === TAB_SIZE){ + newValue = Editor.getTabSize(fullPath); + } else { + const newTabSpaceCfg = Editor.getAutoTabSpaces(fullPath); + const newUseTabCharCfg = Editor.getUseTabChar(fullPath); + if(this._currentOptions[AUTO_TAB_SPACES] === newTabSpaceCfg && + this._currentOptions[USE_TAB_CHAR] === newUseTabCharCfg) { + // no change + return; + } + this._currentOptions[AUTO_TAB_SPACES] = newTabSpaceCfg; + this._currentOptions[USE_TAB_CHAR] = newUseTabCharCfg; + this._currentOptions[SPACE_UNITS] = Editor.getSpaceUnits(fullPath); + this._currentOptions[TAB_SIZE] = Editor.getTabSize(fullPath); + this._codeMirror.setOption(cmOptions[USE_TAB_CHAR], newUseTabCharCfg); + this._codeMirror.setOption("indentUnit", newUseTabCharCfg === true ? + this._currentOptions[TAB_SIZE] : + this._currentOptions[SPACE_UNITS] + ); + this.trigger("optionChange", AUTO_TAB_SPACES, newTabSpaceCfg); + this.trigger("optionChange", USE_TAB_CHAR, newUseTabCharCfg); + return; + } + } + if (oldValue !== newValue) { this._currentOptions[prefName] = newValue; - if (prefName === USE_TAB_CHAR) { - this._codeMirror.setOption(cmOptions[prefName], newValue); - this._codeMirror.setOption("indentUnit", newValue === true ? - this._currentOptions[TAB_SIZE] : - this._currentOptions[SPACE_UNITS] - ); - } else if (prefName === STYLE_ACTIVE_LINE) { + if (prefName === STYLE_ACTIVE_LINE) { this._updateStyleActiveLine(); } else if (prefName === SCROLL_PAST_END && this._visibleRange) { // Do not apply this option to inline editors @@ -2555,6 +2588,10 @@ define(function (require, exports, module) { * @return {boolean} */ Editor.getUseTabChar = function (fullPath) { + const computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues) { + return computedValues.useTabChar; + } return PreferencesManager.get(USE_TAB_CHAR, _buildPreferencesContext(fullPath)); }; @@ -2576,9 +2613,65 @@ define(function (require, exports, module) { * @return {number} */ Editor.getTabSize = function (fullPath) { + const computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues && computedValues.tabSize) { + return computedValues.tabSize; + } return PreferencesManager.get(TAB_SIZE, _buildPreferencesContext(fullPath)); }; + let computedTabSpaces = new Map(); + Editor._autoDetectTabSpaces = function (editor) { + if(!editor){ + return; + } + const fullPath = editor.document.file.fullPath; + if(!Editor.getAutoTabSpaces(fullPath)){ + return; // auto detect is disabled + } + if(computedTabSpaces.has(fullPath)) { + editor._updateOption(AUTO_TAB_SPACES); + return; + } + const text = editor.document.getText(); + const detectedVals = IndentHelper.detectIndent(text); + const useTabChar = (detectedVals.type === "tab"); + let amount = detectedVals.amount; + if(!detectedVals.type || !amount){ + // this happens if the util cant find out the tab/spacing config + amount = EditorPreferences.DEFAULT_SPACE_UNITS; + } + computedTabSpaces.set(fullPath, { + useTabChar, + tabSize: useTabChar ? Math.min(amount, EditorPreferences.MAX_TAB_SIZE) : 0, + spaceUnits: useTabChar ? 0 : Math.min(amount, EditorPreferences.MAX_SPACE_UNITS) + }); + editor._updateOption(AUTO_TAB_SPACES); + }; + + /** + * When set, the tabs and spaces to be used will be auto detected from the current file or fall back to defaults. + * Affects any editors that share the same preference location. + * @param {boolean} value + * @param {string=} fullPath Path to file to get preference for + * @return {boolean} true if value was valid + */ + Editor.setAutoTabSpaces = function (value, fullPath) { + computedTabSpaces = new Map(); // reset all computed values as user may want to recompute tabSpaces of + // current file by double clicking the statusbar icon + const options = fullPath && {context: fullPath}; + return PreferencesManager.set(AUTO_TAB_SPACES, value, options); + }; + + /** + * Get auto tabbing/spacing option + * @param {string=} fullPath Path to file to get preference for + * @return {number} + */ + Editor.getAutoTabSpaces = function (fullPath) { + return PreferencesManager.get(AUTO_TAB_SPACES, _buildPreferencesContext(fullPath)); + }; + /** * Sets indentation width. * Affects any editors that share the same preference location. @@ -2597,6 +2690,10 @@ define(function (require, exports, module) { * @return {number} */ Editor.getSpaceUnits = function (fullPath) { + const computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues && computedValues.spaceUnits) { + return computedValues.spaceUnits; + } return PreferencesManager.get(SPACE_UNITS, _buildPreferencesContext(fullPath)); }; @@ -2790,6 +2887,10 @@ define(function (require, exports, module) { }); }); }); + + function f() { + + } // Define public API exports.Editor = Editor; diff --git a/src/editor/EditorHelper/EditorPreferences.js b/src/editor/EditorHelper/EditorPreferences.js index 4eaa43cdd6..50b73b1a63 100644 --- a/src/editor/EditorHelper/EditorPreferences.js +++ b/src/editor/EditorHelper/EditorPreferences.js @@ -43,6 +43,7 @@ define(function (require, exports, module) { SPACE_UNITS = "spaceUnits", STYLE_ACTIVE_LINE = "styleActiveLine", TAB_SIZE = "tabSize", + AUTO_TAB_SPACES = "autoTabSpaces", UPPERCASE_COLORS = "uppercaseColors", USE_TAB_CHAR = "useTabChar", WORD_WRAP = "wordWrap", @@ -141,6 +142,9 @@ define(function (require, exports, module) { validator: _.partialRight(ValidationUtils.isIntegerInRange, MIN_TAB_SIZE, MAX_TAB_SIZE), description: Strings.DESCRIPTION_TAB_SIZE }); + PreferencesManager.definePreference(AUTO_TAB_SPACES, "boolean", true, { + description: Strings.DESCRIPTION_AUTO_TAB_SPACE + }); PreferencesManager.definePreference(UPPERCASE_COLORS, "boolean", false, { description: Strings.DESCRIPTION_UPPERCASE_COLORS }); @@ -194,6 +198,7 @@ define(function (require, exports, module) { exports.SPACE_UNITS = SPACE_UNITS; exports.STYLE_ACTIVE_LINE = STYLE_ACTIVE_LINE; exports.TAB_SIZE = TAB_SIZE; + exports.AUTO_TAB_SPACES = AUTO_TAB_SPACES; exports.UPPERCASE_COLORS = UPPERCASE_COLORS; exports.USE_TAB_CHAR = USE_TAB_CHAR; exports.WORD_WRAP = WORD_WRAP; diff --git a/src/editor/EditorHelper/IndentHelper.js b/src/editor/EditorHelper/IndentHelper.js index 951b4d0a1e..1c7c2a8eda 100644 --- a/src/editor/EditorHelper/IndentHelper.js +++ b/src/editor/EditorHelper/IndentHelper.js @@ -32,6 +32,175 @@ define(function (require, exports, module) { const SOFT_TABS = EditorPreferences.SOFT_TABS; + /*Start of modified code: https://www.npmjs.com/package/detect-indent + * We modified the code to specify `scanLineLimit` for partial scans in a large text*/ + // Detect either spaces or tabs but not both to properly handle tabs for indentation and spaces for alignment + const INDENT_REGEX = /^(?:( )+|\t+)/; + + const INDENT_TYPE_SPACE = 'space'; + const INDENT_TYPE_TAB = 'tab'; + + /** + Make a Map that counts how many indents/unindents have occurred for a given size and how many lines follow a given indentation. + + The key is a concatenation of the indentation type (s = space and t = tab) and the size of the indents/unindents. + + ``` + indents = { + t3: [1, 0], + t4: [1, 5], + s5: [1, 0], + s12: [1, 0], + } + ``` + */ + function makeIndentsMap(string, ignoreSingleSpaces, scanLineLimit) { + const indents = new Map(); + + // Remember the size of previous line's indentation + let previousSize = 0; + let previousIndentType; + + // Indents key (ident type + size of the indents/unindents) + let key; + + for (const line of string.split(/\n/g, scanLineLimit)) { + if (!line) { + // Ignore empty lines + continue; + } + + let indent; + let indentType; + let use; + let weight; + let entry; + const matches = line.match(INDENT_REGEX); + + if (matches === null) { + previousSize = 0; + previousIndentType = ''; + } else { + indent = matches[0].length; + indentType = matches[1] ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB; + + // Ignore single space unless it's the only indent detected to prevent common false positives + if (ignoreSingleSpaces && indentType === INDENT_TYPE_SPACE && indent === 1) { + continue; + } + + if (indentType !== previousIndentType) { + previousSize = 0; + } + + previousIndentType = indentType; + + use = 1; + weight = 0; + + const indentDifference = indent - previousSize; + previousSize = indent; + + // Previous line have same indent? + if (indentDifference === 0) { + // Not a new "use" of the current indent: + use = 0; + // But do add a bit to it for breaking ties: + weight = 1; + // We use the key from previous loop + } else { + const absoluteIndentDifference = indentDifference > 0 ? indentDifference : -indentDifference; + key = encodeIndentsKey(indentType, absoluteIndentDifference); + } + + // Update the stats + entry = indents.get(key); + entry = entry === undefined ? [1, 0] : [entry[0] + use, entry[1] + weight]; + + indents.set(key, entry); + } + } + + return indents; + } + + // Encode the indent type and amount as a string (e.g. 's4') for use as a compound key in the indents Map. + function encodeIndentsKey(indentType, indentAmount) { + const typeCharacter = indentType === INDENT_TYPE_SPACE ? 's' : 't'; + return typeCharacter + String(indentAmount); + } + + // Extract the indent type and amount from a key of the indents Map. + function decodeIndentsKey(indentsKey) { + const keyHasTypeSpace = indentsKey[0] === 's'; + const type = keyHasTypeSpace ? INDENT_TYPE_SPACE : INDENT_TYPE_TAB; + + const amount = Number(indentsKey.slice(1)); + + return {type, amount}; + } + + // Return the key (e.g. 's4') from the indents Map that represents the most common indent, + // or return undefined if there are no indents. + function getMostUsedKey(indents) { + let result; + let maxUsed = 0; + let maxWeight = 0; + + for (const [key, [usedCount, weight]] of indents) { + if (usedCount > maxUsed || (usedCount === maxUsed && weight > maxWeight)) { + maxUsed = usedCount; + maxWeight = weight; + result = key; + } + } + + return result; + } + + function makeIndentString(type, amount) { + const indentCharacter = type === INDENT_TYPE_SPACE ? ' ' : '\t'; + return indentCharacter.repeat(amount); + } + + /** + * computes the tab/spaces config for the file + * @param string the text to determine the indent config + * @param scanLineLimit - the number of lines to scan. This can be used if you dont want to scan + * full text for large texts + * @returns {{amount: number, indent: string, type: (string)}} + */ + function detectIndent(string, scanLineLimit) { + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } + + // Identify indents while skipping single space indents to avoid common edge cases (e.g. code comments) + // If no indents are identified, run again and include all indents for comprehensive detection + let indents = makeIndentsMap(string, true, scanLineLimit); + if (indents.size === 0) { + indents = makeIndentsMap(string, false, scanLineLimit); + } + + const keyOfMostUsedIndent = getMostUsedKey(indents); + + let type; + let amount = 0; + let indent = ''; + + if (keyOfMostUsedIndent !== undefined) { + ({type, amount} = decodeIndentsKey(keyOfMostUsedIndent)); + indent = makeIndentString(type, amount); + } + + return { + amount, + type, + indent + }; + } + /*End of copied code: https://www.npmjs.com/package/detect-indent*/ + /** * Helper function for `_handleTabKey()` (case 2) - see comment in that function. * @param {Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, reversed:boolean, primary:boolean}>} selections @@ -250,4 +419,5 @@ define(function (require, exports, module) { } exports.addHelpers =addHelpers; + exports.detectIndent =detectIndent; }); diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index 082880fd7a..dabc72a612 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -287,7 +287,8 @@ define(function (require, exports, module) { if (!current) { StatusBar.hideAllPanes(); } else { - var fullPath = current.document.file.fullPath; + Editor._autoDetectTabSpaces(current); + const fullPath = current.document.file.fullPath; StatusBar.showAllPanes(); current.on("cursorActivity.statusbar", _updateCursorInfo); diff --git a/src/extensions/default/Phoenix-prettier/main.js b/src/extensions/default/Phoenix-prettier/main.js index ea5d3a6742..16bf1d6af1 100644 --- a/src/extensions/default/Phoenix-prettier/main.js +++ b/src/extensions/default/Phoenix-prettier/main.js @@ -232,7 +232,7 @@ define(function (require, exports, module) { options._usePlugin = parsersForLanguage[languageId]; Object.assign(options, { parser: parsersForLanguage[languageId], - tabWidth: indentWithTabs ? Editor.getTabSize() : Editor.getSpaceUnits(), + tabWidth: indentWithTabs ? Editor.getTabSize(filepath) : Editor.getSpaceUnits(filepath), useTabs: indentWithTabs, filepath: filepath, printWidth: printWidth, diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 2d57eff789..c95c6cc54c 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -1028,6 +1028,7 @@ define({ "DESCRIPTION_STYLE_ACTIVE_LINE": "true to highlight background color of the line the cursor is on", "DESCRIPTION_TAB_SIZE": "Number of spaces to display for tabs", "DESCRIPTION_USE_TAB_CHAR": "true to use tabs instead of spaces", + "DESCRIPTION_AUTO_TAB_SPACE": "true to auto detect tabs and spaces used in current file", "DESCRIPTION_UPPERCASE_COLORS": "true to generate uppercase hex colors in Inline Color Editor", "DESCRIPTION_WORD_WRAP": "Wrap lines that exceed the viewport width", "DESCRIPTION_SEARCH_AUTOHIDE": "Close the search as soon as the editor is focused", diff --git a/src/thirdparty/licences/credits.md b/src/thirdparty/licences/credits.md index 73a5f43ea1..edb9783af2 100644 --- a/src/thirdparty/licences/credits.md +++ b/src/thirdparty/licences/credits.md @@ -9,6 +9,7 @@ Please find all other licenses and attributions below: ## Other Libraries used 1. https://www.npmjs.com/package/@pixelbrackets/gfm-stylesheet under AGPL 2.0 license +2. https://www.npmjs.com/package/detect-indent detect-indent MIT Copyright (c) Sindre Sorhus (https://sindresorhus.com) ## Attributions for Images and other assets used From 99abb0021c3be46a2e06ff2aca2f321e7ed94a21 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 12:53:29 +0530 Subject: [PATCH 02/12] chore: optimize performance of auto tab spacing detection --- src/editor/Editor.js | 7 ++++--- src/editor/EditorHelper/IndentHelper.js | 28 ++++++++++++------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 09f7e7e1cc..5c89e22588 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -2621,7 +2621,8 @@ define(function (require, exports, module) { }; let computedTabSpaces = new Map(); - Editor._autoDetectTabSpaces = function (editor) { + const MAX_LINES_TO_SCAN_FOR_INDENT = 700; // this is high to account for any js docs/ file comments + Editor._autoDetectTabSpaces = function (editor, scanFullFile) { if(!editor){ return; } @@ -2633,8 +2634,8 @@ define(function (require, exports, module) { editor._updateOption(AUTO_TAB_SPACES); return; } - const text = editor.document.getText(); - const detectedVals = IndentHelper.detectIndent(text); + // we only scan the first 200 lines of text to determine the spaces. + const detectedVals = editor._detectIndent(scanFullFile? undefined : MAX_LINES_TO_SCAN_FOR_INDENT); const useTabChar = (detectedVals.type === "tab"); let amount = detectedVals.amount; if(!detectedVals.type || !amount){ diff --git a/src/editor/EditorHelper/IndentHelper.js b/src/editor/EditorHelper/IndentHelper.js index 1c7c2a8eda..9425066a0e 100644 --- a/src/editor/EditorHelper/IndentHelper.js +++ b/src/editor/EditorHelper/IndentHelper.js @@ -33,7 +33,7 @@ define(function (require, exports, module) { const SOFT_TABS = EditorPreferences.SOFT_TABS; /*Start of modified code: https://www.npmjs.com/package/detect-indent - * We modified the code to specify `scanLineLimit` for partial scans in a large text*/ + * We modified the code to work on editor as well as text and adds `scanLineLimit` for partial scans in large text*/ // Detect either spaces or tabs but not both to properly handle tabs for indentation and spaces for alignment const INDENT_REGEX = /^(?:( )+|\t+)/; @@ -54,7 +54,7 @@ define(function (require, exports, module) { } ``` */ - function makeIndentsMap(string, ignoreSingleSpaces, scanLineLimit) { + function makeIndentsMap(editor, ignoreSingleSpaces, scanLineLimit) { const indents = new Map(); // Remember the size of previous line's indentation @@ -62,9 +62,11 @@ define(function (require, exports, module) { let previousIndentType; // Indents key (ident type + size of the indents/unindents) - let key; + const totalLines = editor.lineCount(); + let key, scanLimit = scanLineLimit ? Math.min(scanLineLimit, totalLines) : totalLines; - for (const line of string.split(/\n/g, scanLineLimit)) { + for (let i =0; i < scanLimit; i++) { + const line = editor.document.getLine(i); if (!line) { // Ignore empty lines continue; @@ -165,21 +167,17 @@ define(function (require, exports, module) { /** * computes the tab/spaces config for the file - * @param string the text to determine the indent config - * @param scanLineLimit - the number of lines to scan. This can be used if you dont want to scan - * full text for large texts + * @param {number} [scanLineLimit] - Optional number of lines to scan. This can be used if you dont want to scan + * full text for large texts. default scans all text. * @returns {{amount: number, indent: string, type: (string)}} */ - function detectIndent(string, scanLineLimit) { - if (typeof string !== 'string') { - throw new TypeError('Expected a string'); - } - + function _detectIndent(scanLineLimit) { + const editor = this; // Identify indents while skipping single space indents to avoid common edge cases (e.g. code comments) // If no indents are identified, run again and include all indents for comprehensive detection - let indents = makeIndentsMap(string, true, scanLineLimit); + let indents = makeIndentsMap(editor, true, scanLineLimit); if (indents.size === 0) { - indents = makeIndentsMap(string, false, scanLineLimit); + indents = makeIndentsMap(editor, false, scanLineLimit); } const keyOfMostUsedIndent = getMostUsedKey(indents); @@ -416,8 +414,8 @@ define(function (require, exports, module) { Editor.prototype._autoIndentEachSelection = _autoIndentEachSelection; Editor.prototype._handleTabKey = _handleTabKey; Editor.prototype._handleSoftTabNavigation = _handleSoftTabNavigation; + Editor.prototype._detectIndent = _detectIndent; } exports.addHelpers =addHelpers; - exports.detectIndent =detectIndent; }); From ba630150591f57ec6ca7c89150fb7f6916ad1e56 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 14:21:07 +0530 Subject: [PATCH 03/12] chore: switching between auto and fixed indents in statusbar --- src/editor/Editor.js | 34 ++++++++++++++++++++-------------- src/editor/EditorStatusBar.js | 24 ++++++++++++++++++++++++ src/index.html | 1 + src/nls/root/strings.js | 2 ++ src/styles/brackets.less | 4 ++-- 5 files changed, 49 insertions(+), 16 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 5c89e22588..4611915194 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -106,8 +106,6 @@ define(function (require, exports, module) { EditorPreferences.init(cmOptions); - const MAX_LINES_TO_SCAN_INDENT = 1000; - const CLOSE_BRACKETS = EditorPreferences.CLOSE_BRACKETS, CLOSE_TAGS = EditorPreferences.CLOSE_TAGS, DRAG_DROP = EditorPreferences.DRAG_DROP, @@ -130,7 +128,7 @@ define(function (require, exports, module) { LINE_NUMBER_GUTTER_PRIORITY = EditorPreferences.LINE_NUMBER_GUTTER_PRIORITY, CODE_FOLDING_GUTTER_PRIORITY = EditorPreferences.CODE_FOLDING_GUTTER_PRIORITY; - let editorOptions = Object.keys(cmOptions); + let editorOptions = [...Object.keys(cmOptions), AUTO_TAB_SPACES]; /** Editor preferences */ @@ -2304,6 +2302,9 @@ define(function (require, exports, module) { newValue = Editor.getTabSize(fullPath); } else { const newTabSpaceCfg = Editor.getAutoTabSpaces(fullPath); + if(newTabSpaceCfg){ + _computeTabSpaces(this); + } const newUseTabCharCfg = Editor.getUseTabChar(fullPath); if(this._currentOptions[AUTO_TAB_SPACES] === newTabSpaceCfg && this._currentOptions[USE_TAB_CHAR] === newUseTabCharCfg) { @@ -2622,16 +2623,9 @@ define(function (require, exports, module) { let computedTabSpaces = new Map(); const MAX_LINES_TO_SCAN_FOR_INDENT = 700; // this is high to account for any js docs/ file comments - Editor._autoDetectTabSpaces = function (editor, scanFullFile) { - if(!editor){ - return; - } + function _computeTabSpaces(editor, scanFullFile, recompute) { const fullPath = editor.document.file.fullPath; - if(!Editor.getAutoTabSpaces(fullPath)){ - return; // auto detect is disabled - } - if(computedTabSpaces.has(fullPath)) { - editor._updateOption(AUTO_TAB_SPACES); + if(computedTabSpaces.has(fullPath) && !recompute) { return; } // we only scan the first 200 lines of text to determine the spaces. @@ -2647,6 +2641,20 @@ define(function (require, exports, module) { tabSize: useTabChar ? Math.min(amount, EditorPreferences.MAX_TAB_SIZE) : 0, spaceUnits: useTabChar ? 0 : Math.min(amount, EditorPreferences.MAX_SPACE_UNITS) }); + } + Editor._autoDetectTabSpaces = function (editor, scanFullFile, recompute) { + if(!editor){ + return; + } + const fullPath = editor.document.file.fullPath; + if(!Editor.getAutoTabSpaces(fullPath)){ + return; // auto detect is disabled + } + if(computedTabSpaces.has(fullPath) && !recompute) { + editor._updateOption(AUTO_TAB_SPACES); + return; + } + _computeTabSpaces(editor, scanFullFile, recompute); editor._updateOption(AUTO_TAB_SPACES); }; @@ -2658,8 +2666,6 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setAutoTabSpaces = function (value, fullPath) { - computedTabSpaces = new Map(); // reset all computed values as user may want to recompute tabSpaces of - // current file by double clicking the statusbar icon const options = fullPath && {context: fullPath}; return PreferencesManager.set(AUTO_TAB_SPACES, value, options); }; diff --git a/src/editor/EditorStatusBar.js b/src/editor/EditorStatusBar.js index dabc72a612..cb325d312d 100644 --- a/src/editor/EditorStatusBar.js +++ b/src/editor/EditorStatusBar.js @@ -60,6 +60,7 @@ define(function (require, exports, module) { $cursorInfo, $fileInfo, $indentType, + $indentAuto, $indentWidthLabel, $indentWidthInput, $statusOverwrite; @@ -125,6 +126,11 @@ define(function (require, exports, module) { $indentWidthLabel.attr("title", indentWithTabs ? Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_TABS : Strings.STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES); } + function _updateAutoIndent(fullPath) { + const autoIndent = Editor.getAutoTabSpaces(fullPath); + $indentAuto.html(autoIndent ? Strings.STATUSBAR_AUTO_INDENT : Strings.STATUSBAR_FIXED_INDENT); + } + /** * Get indent size based on type * @param {string} fullPath Path to file in current editor @@ -153,6 +159,20 @@ define(function (require, exports, module) { Editor.setUseTabChar(!Editor.getUseTabChar(fullPath), fullPath); _updateIndentType(fullPath); + _updateAutoIndent(fullPath); + _updateIndentSize(fullPath); + } + + function _toggleAutoIndent() { + const current = EditorManager.getActiveEditor(), + fullPath = current && current.document.file.fullPath; + Editor.setAutoTabSpaces(!Editor.getAutoTabSpaces(fullPath), fullPath); + if(Editor.getAutoTabSpaces(fullPath)){ + // if the user explicitly clicked on the auto indent status bar icon, he might mean to recompute it + Editor._autoDetectTabSpaces(current, true, true); + } + _updateIndentType(fullPath); + _updateAutoIndent(fullPath); _updateIndentSize(fullPath); } @@ -294,6 +314,7 @@ define(function (require, exports, module) { current.on("cursorActivity.statusbar", _updateCursorInfo); current.on("optionChange.statusbar", function () { _updateIndentType(fullPath); + _updateAutoIndent(fullPath); _updateIndentSize(fullPath); }); current.on("change.statusbar", function () { @@ -313,6 +334,7 @@ define(function (require, exports, module) { _updateFileInfo(current); _initOverwriteMode(current); _updateIndentType(fullPath); + _updateAutoIndent(fullPath); _updateIndentSize(fullPath); } } @@ -372,6 +394,7 @@ define(function (require, exports, module) { $cursorInfo = $("#status-cursor"); $fileInfo = $("#status-file"); $indentType = $("#indent-type"); + $indentAuto = $("#indent-auto"); $indentWidthLabel = $("#indent-width-label"); $indentWidthInput = $("#indent-width-input"); $statusOverwrite = $("#status-overwrite"); @@ -461,6 +484,7 @@ define(function (require, exports, module) { // indentation event handlers $indentType.on("click", _toggleIndentType); + $indentAuto.on("click", _toggleAutoIndent); $indentWidthLabel .on("click", function () { // update the input value before displaying diff --git a/src/index.html b/src/index.html index 1461f82f97..cc6463fec0 100644 --- a/src/index.html +++ b/src/index.html @@ -855,6 +855,7 @@
+
diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index c95c6cc54c..f25adee5fc 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -399,6 +399,8 @@ define({ "STATUSBAR_INDENT_SIZE_TOOLTIP_TABS": "Click to change tab character width", "STATUSBAR_SPACES": "Spaces:", "STATUSBAR_TAB_SIZE": "Tab Size:", + "STATUSBAR_AUTO_INDENT": "Auto", + "STATUSBAR_FIXED_INDENT": "Fixed", "STATUSBAR_LINE_COUNT_SINGULAR": "\u2014 {0} Line", "STATUSBAR_LINE_COUNT_PLURAL": "\u2014 {0} Lines", "STATUSBAR_USER_EXTENSIONS_DISABLED": "Extensions Disabled", diff --git a/src/styles/brackets.less b/src/styles/brackets.less index 8d22438a7f..55c686c31e 100644 --- a/src/styles/brackets.less +++ b/src/styles/brackets.less @@ -406,12 +406,12 @@ a, img { display: none; } -#indent-type, #indent-width-label { +#indent-type, #indent-auto, #indent-width-label { cursor: pointer; margin-right: 3px; } -#status-overwrite:hover, #indent-type:hover, #indent-width-label:hover { +#status-overwrite:hover, #indent-type:hover, #indent-auto:hover, #indent-width-label:hover { text-decoration: underline; } From 9e2f7632b907edf4205592163fa41b9710b624a8 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 16:19:48 +0530 Subject: [PATCH 04/12] chore: allow manual overrides for automatic tab spacing detection --- src/editor/Editor.js | 19 +++++++++++++++++++ src/editor/EditorHelper/EditorPreferences.js | 10 ++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 4611915194..8980c12332 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -2579,6 +2579,11 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setUseTabChar = function (value, fullPath) { + let computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues) { + computedValues.useTabChar = value; + return true; + } var options = fullPath && {context: fullPath}; return PreferencesManager.set(USE_TAB_CHAR, value, options); }; @@ -2604,6 +2609,13 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setTabSize = function (value, fullPath) { + let computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues) { + if(EditorPreferences.isValidTabSize(value)){ + computedValues.tabSize = value; + } + return true; + } var options = fullPath && {context: fullPath}; return PreferencesManager.set(TAB_SIZE, value, options); }; @@ -2687,6 +2699,13 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setSpaceUnits = function (value, fullPath) { + let computedValues = computedTabSpaces.get(fullPath); + if(Editor.getAutoTabSpaces(fullPath) && computedValues) { + if(EditorPreferences.isValidSpaceUnit(value)){ + computedValues.spaceUnits = value; + } + return true; + } var options = fullPath && {context: fullPath}; return PreferencesManager.set(SPACE_UNITS, value, options); }; diff --git a/src/editor/EditorHelper/EditorPreferences.js b/src/editor/EditorHelper/EditorPreferences.js index 50b73b1a63..8993580b32 100644 --- a/src/editor/EditorHelper/EditorPreferences.js +++ b/src/editor/EditorHelper/EditorPreferences.js @@ -166,6 +166,14 @@ define(function (require, exports, module) { description: Strings.DESCRIPTION_INPUT_STYLE }); + function isValidTabSize (size) { + return ValidationUtils.isIntegerInRange(size, MIN_TAB_SIZE, MAX_TAB_SIZE); + } + + function isValidSpaceUnit (size) { + return ValidationUtils.isIntegerInRange(size, MIN_SPACE_UNITS, MAX_SPACE_UNITS); + } + function init(cmOptions) { // Mappings from Brackets preferences to CodeMirror options cmOptions[CLOSE_BRACKETS] = "autoCloseBrackets"; @@ -218,4 +226,6 @@ define(function (require, exports, module) { exports.CODE_FOLDING_GUTTER_PRIORITY = CODE_FOLDING_GUTTER_PRIORITY; exports.init =init; + exports.isValidTabSize = isValidTabSize; + exports.isValidSpaceUnit = isValidSpaceUnit; }); From dd2256b4f26e131b81372d2726702cb35a2cc1f9 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 16:30:30 +0530 Subject: [PATCH 05/12] refactor: remove _migrateLegacyStateFile as it has no usage in metrics --- src/preferences/PreferencesImpl.js | 1 - src/preferences/StateManager.js | 60 ------------------------------ 2 files changed, 61 deletions(-) diff --git a/src/preferences/PreferencesImpl.js b/src/preferences/PreferencesImpl.js index bb5434ccd2..0191582b13 100644 --- a/src/preferences/PreferencesImpl.js +++ b/src/preferences/PreferencesImpl.js @@ -85,7 +85,6 @@ define(function (require, exports, module) { var userScopeLoading = manager.addScope("user", userScope); _addScopePromises.push(userScopeLoading); - _addScopePromises.push(StateManager._migrateLegacyStateFile()); // Set up the .phcode.json file handling userScopeLoading diff --git a/src/preferences/StateManager.js b/src/preferences/StateManager.js index f266e26982..f69c00a22d 100644 --- a/src/preferences/StateManager.js +++ b/src/preferences/StateManager.js @@ -29,12 +29,8 @@ define(function (require, exports, module) { const _ = require("thirdparty/lodash"), EventDispatcher = require("utils/EventDispatcher"), - Metrics = require("utils/Metrics"), ProjectManager = require("project/ProjectManager"); - const LEGACY_STATE_MANAGER_MIGRATED = "ph_state_manager_migrated"; - const LEGACY_STATE_FILE_PATH = "/fs/app/state.json"; - const PROJECT_CONTEXT = "project"; const GLOBAL_CONTEXT = "global"; const PROJECT_THEN_GLOBAL_CONTEXT = "any"; @@ -240,62 +236,6 @@ define(function (require, exports, module) { return createExtensionStateManager(prefix); } - /** - * We used file based state.json in the earlier state manager impl. no we moved to phstore. So we have to move - * earlier users to phstore to prevent losing their files. This code can be deleted anytime after 4 months - * from this commit. - * @private - */ - function _migrateLegacyStateFile() { - if(Phoenix.isTestWindow || getVal(LEGACY_STATE_MANAGER_MIGRATED)){ - return new $.Deferred().resolve().promise(); - } - const _migrated = new $.Deferred(); - console.log("Migrating legacy state file", LEGACY_STATE_FILE_PATH); - fs.readFile(LEGACY_STATE_FILE_PATH, "utf8", function (err, data) { - setVal(LEGACY_STATE_MANAGER_MIGRATED, true); - if (err) { - // if error, ignore and continue. state file not found(unlikely to be here) - _migrated.resolve(); - return; - } - try{ - const keysToMigrate = [ - "afterFirstLaunch", - "sidebar", - "workingSetSortMethod", - "healthDataUsage", - "main-toolbar", - "recentProjects", - "healthDataNotificationShown", - "searchHistory", - "problems-panel" - ]; - const oldState = JSON.parse(data); - for(let key of keysToMigrate) { - if(oldState[key]){ - console.log("Migrated Legacy state: ", key, oldState[key]); - setVal(key, oldState[key]); - } - } - fs.unlink(LEGACY_STATE_FILE_PATH, (unlinkErr)=>{ - if(unlinkErr){ - console.error(`Error deleting legacy state file ${LEGACY_STATE_FILE_PATH}`, unlinkErr); - } - }); - } catch (e) { - console.error("Error migrating legacy state file", LEGACY_STATE_FILE_PATH); - } - Metrics.countEvent(Metrics.EVENT_TYPE.PLATFORM, "legacyState", "migrated"); - _migrated.resolve(); - console.log("Legacy state migration completed"); - }); - return _migrated.promise(); - } - - // private API - exports._migrateLegacyStateFile = _migrateLegacyStateFile; - // public api exports.get = getVal; exports.set = setVal; From db693ad702ce770c4ebfbbcdca676bf8ce277294 Mon Sep 17 00:00:00 2001 From: abose Date: Wed, 14 Aug 2024 18:30:04 +0530 Subject: [PATCH 06/12] chore: remember overriden space config in auto space detect mode --- src/editor/Editor.js | 37 ++++++++++++++++++++++-------- src/preferences/PreferencesImpl.js | 1 - src/preferences/StateManager.js | 12 ++++++++-- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 8980c12332..81f092a4d0 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -78,6 +78,7 @@ define(function (require, exports, module) { EventDispatcher = require("utils/EventDispatcher"), PerfUtils = require("utils/PerfUtils"), PreferencesManager = require("preferences/PreferencesManager"), + StateManager = require("preferences/StateManager"), TextRange = require("document/TextRange").TextRange, TokenUtils = require("utils/TokenUtils"), HTMLUtils = require("language/HTMLUtils"), @@ -85,6 +86,8 @@ define(function (require, exports, module) { Metrics = require("utils/Metrics"), _ = require("thirdparty/lodash"); + const tabSpacesStateManager = StateManager._createInternalStateManager(StateManager._INTERNAL_STATES.TAB_SPACES); + /** Editor helpers */ let IndentHelper = require("./EditorHelper/IndentHelper"), @@ -2570,6 +2573,16 @@ define(function (require, exports, module) { }; // Global settings that affect Editor instances that share the same preference locations + let computedTabSpaces = new Map(); + function _getCachedSpaceCfg(key) { + // there are two storages for auto detected spaces for files. IF the user has explicitly set the spacing + // through the status bar, its stored permanently. else its computed on the fly + let cachedCfg = tabSpacesStateManager.get(key); + if(cachedCfg){ + return cachedCfg; + } + return computedTabSpaces.get(key); + } /** * Sets whether to use tab characters (vs. spaces) when inserting new text. @@ -2579,9 +2592,11 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setUseTabChar = function (value, fullPath) { - let computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues) { computedValues.useTabChar = value; + // persist explicitly user set values to storage + tabSpacesStateManager.set(fullPath, computedValues); return true; } var options = fullPath && {context: fullPath}; @@ -2594,7 +2609,7 @@ define(function (require, exports, module) { * @return {boolean} */ Editor.getUseTabChar = function (fullPath) { - const computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues) { return computedValues.useTabChar; } @@ -2609,10 +2624,12 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setTabSize = function (value, fullPath) { - let computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues) { if(EditorPreferences.isValidTabSize(value)){ computedValues.tabSize = value; + // persist explicitly user set values to storage + tabSpacesStateManager.set(fullPath, computedValues); } return true; } @@ -2626,18 +2643,17 @@ define(function (require, exports, module) { * @return {number} */ Editor.getTabSize = function (fullPath) { - const computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues && computedValues.tabSize) { return computedValues.tabSize; } return PreferencesManager.get(TAB_SIZE, _buildPreferencesContext(fullPath)); }; - let computedTabSpaces = new Map(); const MAX_LINES_TO_SCAN_FOR_INDENT = 700; // this is high to account for any js docs/ file comments function _computeTabSpaces(editor, scanFullFile, recompute) { const fullPath = editor.document.file.fullPath; - if(computedTabSpaces.has(fullPath) && !recompute) { + if(_getCachedSpaceCfg(fullPath) && !recompute) { return; } // we only scan the first 200 lines of text to determine the spaces. @@ -2648,6 +2664,7 @@ define(function (require, exports, module) { // this happens if the util cant find out the tab/spacing config amount = EditorPreferences.DEFAULT_SPACE_UNITS; } + tabSpacesStateManager.set(fullPath, null); // we dont have a remove api, so just nulling for now computedTabSpaces.set(fullPath, { useTabChar, tabSize: useTabChar ? Math.min(amount, EditorPreferences.MAX_TAB_SIZE) : 0, @@ -2662,7 +2679,7 @@ define(function (require, exports, module) { if(!Editor.getAutoTabSpaces(fullPath)){ return; // auto detect is disabled } - if(computedTabSpaces.has(fullPath) && !recompute) { + if(_getCachedSpaceCfg(fullPath) && !recompute) { editor._updateOption(AUTO_TAB_SPACES); return; } @@ -2699,10 +2716,12 @@ define(function (require, exports, module) { * @return {boolean} true if value was valid */ Editor.setSpaceUnits = function (value, fullPath) { - let computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues) { if(EditorPreferences.isValidSpaceUnit(value)){ computedValues.spaceUnits = value; + // persist explicitly user set values to storage + tabSpacesStateManager.set(fullPath, computedValues); } return true; } @@ -2716,7 +2735,7 @@ define(function (require, exports, module) { * @return {number} */ Editor.getSpaceUnits = function (fullPath) { - const computedValues = computedTabSpaces.get(fullPath); + let computedValues = _getCachedSpaceCfg(fullPath); if(Editor.getAutoTabSpaces(fullPath) && computedValues && computedValues.spaceUnits) { return computedValues.spaceUnits; } diff --git a/src/preferences/PreferencesImpl.js b/src/preferences/PreferencesImpl.js index 0191582b13..4b5cb0dc08 100644 --- a/src/preferences/PreferencesImpl.js +++ b/src/preferences/PreferencesImpl.js @@ -30,7 +30,6 @@ define(function (require, exports, module) { const PreferencesBase = require("./PreferencesBase"), Async = require("utils/Async"), - StateManager = require("preferences/StateManager"), // The SETTINGS_FILENAME is used with a preceding "." within user projects SETTINGS_FILENAME = "phcode.json", diff --git a/src/preferences/StateManager.js b/src/preferences/StateManager.js index f69c00a22d..760419d34d 100644 --- a/src/preferences/StateManager.js +++ b/src/preferences/StateManager.js @@ -141,8 +141,8 @@ define(function (require, exports, module) { /** * returns a preference instance that can be listened `.on("change", cbfn(changeType))` . The callback fucntion will be called * whenever there is a change in the supplied id with a changeType argument. The change type can be one of the two: - * CHANGE_TYPE_INTERNAL - if change is made within the current editor - * CHANGE_TYPE_EXTERNAL - if change is made within the current editor + * CHANGE_TYPE_INTERNAL - if change is made within the current app window/browser tap + * CHANGE_TYPE_EXTERNAL - if change is made in a different app window/browser tab * * @param id * @param type @@ -236,6 +236,14 @@ define(function (require, exports, module) { return createExtensionStateManager(prefix); } + // private api for internal use only + // All internal states must be registered here to prevent collisions in internal codebase state managers + const _INTERNAL_STATES = { + TAB_SPACES: "TAB_SPC_" + }; + exports._INTERNAL_STATES = _INTERNAL_STATES; + exports._createInternalStateManager = createExtensionStateManager; + // public api exports.get = getVal; exports.set = setVal; From e25dc3082800c91212be12152e16d68ccce25a25 Mon Sep 17 00:00:00 2001 From: abose Date: Sat, 17 Aug 2024 18:28:11 +0530 Subject: [PATCH 07/12] chore: fix too many bug snag reports from few users on storage/metric put fails --- src/storage.js | 39 ++++++++++++++++++++++++++++++++++++++- src/utils/Metrics.js | 30 +++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/storage.js b/src/storage.js index 490ae64e4c..433cafaa41 100644 --- a/src/storage.js +++ b/src/storage.js @@ -152,6 +152,41 @@ import {set, entries, createStore} from './thirdparty/idb-keyval.js'; return null; } + const MINUTES_10 = 10*1000; + let errorCountReported = 0, sendOnceMore = false, noFurtherReporting = false; + function _reportPutItemError(err) { + // in bugsnag, we get errors from a few users(line 2-3 users), who might likely have crashed + // phnode and dont know how to kill the process and the window is still open in back for several + // days(send 22+days). This was being caught every minute and reported in bugsnag which + // generates like 20K+ reports per user per month. So we only report error once in an hour. + if(window.debugMode){ + console.error(err); + } + const logger = window.logger; + if(!logger || noFurtherReporting){ + return; + } + // we only report 1 error once to prevent too many Bugsnag reports. We seen in bugsnag that like 2-3 + // users triggers thousands of this error in bugsnag report per day as they send continuous error reports + // every minute due to this error. We throttle to send only 2 errors to bugsnag any minute at app level, + // so this will starve other genuine errors as well if not captured here. + errorCountReported ++; + if(sendOnceMore){ + // we send the crash stack once and then another report 10 minutes later. After that, this is likely + // to fail always. + noFurtherReporting = true; + logger.reportError(err, + `${errorCountReported} tauri:storage:setItem failures in ${MINUTES_10/1000} minutes`); + } + if(errorCountReported !== 1){ + return; + } + logger.reportError(err); + setTimeout(()=>{ + sendOnceMore = true; + }, MINUTES_10); + } + /** * Sets the value of a specified key in the localStorage. * @@ -168,7 +203,9 @@ import {set, entries, createStore} from './thirdparty/idb-keyval.js'; }; if(!Phoenix.isTestWindow || key === _testKey) { if(isDesktop) { - storageNodeConnector.execPeer("putItem", {key, value: valueToStore}); + storageNodeConnector + .execPeer("putItem", {key, value: valueToStore}) + .catch(_reportPutItemError); // this is an in-memory tauri store that takes care of multi window case, since we have a single // instance, all windows share this and can reconstruct the full view from the dumb file + this map // when the editor boots up instead of having to write the dump file frequently. diff --git a/src/utils/Metrics.js b/src/utils/Metrics.js index 49a547662b..5aa4edf312 100644 --- a/src/utils/Metrics.js +++ b/src/utils/Metrics.js @@ -19,7 +19,7 @@ * */ -/*global gtag, analytics, mixpanel*/ +/*global gtag, analytics, logger*/ // @INCLUDE_IN_API_DOCS /** @@ -144,11 +144,39 @@ define(function (require, exports, module) { _createAnalyticsShims(); + const MINUTES_10 = 10*1000; + let tauriGaErrorCountSent = 0, sendOnceMore = false, noFurtherReporting = false; function _sendTauriGAEvent(analyticsID, customUserID, events=[]) { window.__TAURI__.event.emit("health", { analyticsID: analyticsID, customUserID: customUserID, events + }).catch(err=>{ + if(window.debugMode){ + console.error(err); + } + if(noFurtherReporting){ + return; + } + // we only report 1 error once to prevent too many Bugsnag reports. We seen in bugsnag that like 2-3 + // users triggers thousands of this error in bugsnag report per day as they send continuous error reports + // every minute due to this error. We throttle to send only 2 errors to bugsnag any minute at app level, + // so this will starve other genuine errors as well if not captured here. + tauriGaErrorCountSent ++; + if(sendOnceMore){ + // we send the crash stack once and then another report 10 minutes later. After that, this is likeley + // to fail always. + noFurtherReporting = true; + logger.reportError(err, + `${tauriGaErrorCountSent} _sendTauriGAEvent failures in ${MINUTES_10/1000} minutes`); + } + if(tauriGaErrorCountSent !== 1){ + return; + } + logger.reportError(err); + setTimeout(()=>{ + sendOnceMore = true; + }, MINUTES_10); }); } From 39dcf4236b2822be1a5472fa22a326790a36200b Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 20 Aug 2024 09:03:54 +0530 Subject: [PATCH 08/12] test: for auto indent detection in prettier --- .../default/Phoenix-prettier/unittests.js | 18 +++++++++++++++++- test/spec/SpecRunnerUtils.js | 1 + .../js/test-pretty-8-indent.js | 11 +++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 test/spec/prettier-test-files/js/test-pretty-8-indent.js diff --git a/src/extensions/default/Phoenix-prettier/unittests.js b/src/extensions/default/Phoenix-prettier/unittests.js index ec4371d40b..71ec32f0ac 100644 --- a/src/extensions/default/Phoenix-prettier/unittests.js +++ b/src/extensions/default/Phoenix-prettier/unittests.js @@ -19,7 +19,7 @@ * */ -/*global describe, it, expect, beforeEach, afterEach, awaitsForDone*/ +/*global describe, it, expect, afterAll, afterEach, beforeAll*/ define(function (require, exports, module) { @@ -36,6 +36,7 @@ define(function (require, exports, module) { const jsFile = require("text!../../../../test/spec/prettier-test-files/js/test.js"), jsPrettyFile = require("text!../../../../test/spec/prettier-test-files/js/test-pretty.js"), + jsPrettyFile8Indent = require("text!../../../../test/spec/prettier-test-files/js/test-pretty-8-indent.js"), jsPrettyRulerFile = require("text!../../../../test/spec/prettier-test-files/js/test-pretty-ruler.js"), jsPrettySelection = require("text!../../../../test/spec/prettier-test-files/js/test-pretty-selection.js"), jsPrettySelectionOffset = require("text!../../../../test/spec/prettier-test-files/js/test-pretty-selection-offset.js"), @@ -63,6 +64,13 @@ define(function (require, exports, module) { describe("integration: Phoenix Prettier", function () { let testEditor, testDocument; + beforeAll(async ()=>{ + Editor.setAutoTabSpaces(false); + }); + afterAll(()=>{ + Editor.setAutoTabSpaces(true); + }); + function createMockEditor(text, language, filename) { let mock = SpecRunnerUtils.createMockEditor(text, language, undefined, {filename: filename}); @@ -73,6 +81,7 @@ define(function (require, exports, module) { describe("JS Beautify", function (){ afterEach(async function () { SpecRunnerUtils.destroyMockEditor(testDocument); + Editor.setAutoTabSpaces(false); Editor.setUseTabChar(false); Editor.setSpaceUnits(4); }); @@ -83,6 +92,13 @@ define(function (require, exports, module) { expect(testEditor.document.getText(true)).toBe(jsPrettyFile); }); + it("should beautify editor for js with auto detected spacing", async function () { + Editor.setAutoTabSpaces(true); + createMockEditor(jsFile, "javascript", "/test.js"); + await BeautificationManager.beautifyEditor(testEditor); + expect(testEditor.document.getText(true)).toBe(jsPrettyFile8Indent); + }); + it("should use line max length from editor rulers by default", async function () { const PREFERENCES_EDITOR_RULERS = "editor.rulers"; PreferencesManager.set(PREFERENCES_EDITOR_RULERS, [10, 20]); diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index c22d47caed..51869dc1e3 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -438,6 +438,7 @@ define(function (require, exports, module) { Editor.setUseTabChar(EDITOR_USE_TABS); Editor.setSpaceUnits(EDITOR_SPACE_UNITS); + Editor._autoDetectTabSpaces(editor, undefined, true); if (pane.addView) { pane.addView(editor); diff --git a/test/spec/prettier-test-files/js/test-pretty-8-indent.js b/test/spec/prettier-test-files/js/test-pretty-8-indent.js new file mode 100644 index 0000000000..2d3f76c3e9 --- /dev/null +++ b/test/spec/prettier-test-files/js/test-pretty-8-indent.js @@ -0,0 +1,11 @@ +function hello() { + console.log("hello"); +} + +function world() { + console.log("world"); +} + +function yo() { + console.log("yo"); +} From 6c0b6173d5f055153290941f826cf700812f5090 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 20 Aug 2024 10:10:38 +0530 Subject: [PATCH 09/12] test: fix doc ref counting test fail due to auto indent detect --- src/brackets.js | 1 + src/editor/Editor.js | 4 ---- test/spec/Document-integ-test.js | 4 +++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/brackets.js b/src/brackets.js index 880d9e2c3b..2da29fae29 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -249,6 +249,7 @@ define(function (require, exports, module) { DocumentModule: require("document/Document"), DragAndDrop: require("utils/DragAndDrop"), EditorManager: require("editor/EditorManager"), + Editor: require("editor/Editor"), EventManager: require("utils/EventManager"), ExtensionLoader: require("utils/ExtensionLoader"), ExtensionUtils: require("utils/ExtensionUtils"), diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 81f092a4d0..3ed0db87ab 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -2932,10 +2932,6 @@ define(function (require, exports, module) { }); }); }); - - function f() { - - } // Define public API exports.Editor = Editor; diff --git a/test/spec/Document-integ-test.js b/test/spec/Document-integ-test.js index 1de762b546..56b2ccd74d 100644 --- a/test/spec/Document-integ-test.js +++ b/test/spec/Document-integ-test.js @@ -30,6 +30,7 @@ define(function (require, exports, module) { EditorManager, // loaded from brackets.test DocumentModule, // loaded from brackets.test DocumentManager, // loaded from brackets.test + Editor, // loaded from brackets.test MainViewManager, // loaded from brackets.test SpecRunnerUtils = require("spec/SpecRunnerUtils"); @@ -50,6 +51,7 @@ define(function (require, exports, module) { DocumentModule = testWindow.brackets.test.DocumentModule; DocumentManager = testWindow.brackets.test.DocumentManager; MainViewManager = testWindow.brackets.test.MainViewManager; + Editor = testWindow.brackets.test.Editor; await SpecRunnerUtils.loadProjectInTestWindow(testPath); }, 30000); @@ -294,7 +296,7 @@ define(function (require, exports, module) { var promise, cssDoc, cssMasterEditor; - + Editor.Editor.setAutoTabSpaces(false); promise = CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, {fullPath: HTML_FILE}); await awaitsForDone(promise, "Open into working set"); From e63fbb3cd7646b5b1e40f6fe123782acb5d1a6fd Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 20 Aug 2024 10:30:02 +0530 Subject: [PATCH 10/12] test: doc ref counting integ test with tab spacing config --- test/spec/Document-integ-test.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/spec/Document-integ-test.js b/test/spec/Document-integ-test.js index 56b2ccd74d..5e53eb8bea 100644 --- a/test/spec/Document-integ-test.js +++ b/test/spec/Document-integ-test.js @@ -292,11 +292,11 @@ define(function (require, exports, module) { // TODO: additional, simpler ref counting test cases such as Live Development, open/close inline editor (refs from // both editor & rule list TextRanges), navigate files w/o adding to working set, etc. - it("should clean up (later) a master Editor auto-created by calling read-only Document API, if Editor not used by UI", async function () { + async function testRef(useAutoTabSpaces) { var promise, cssDoc, cssMasterEditor; - Editor.Editor.setAutoTabSpaces(false); + Editor.Editor.setAutoTabSpaces(useAutoTabSpaces); promise = CommandManager.execute(Commands.CMD_ADD_TO_WORKINGSET_AND_OPEN, {fullPath: HTML_FILE}); await awaitsForDone(promise, "Open into working set"); @@ -309,8 +309,12 @@ define(function (require, exports, module) { // Force creation of master editor for CSS file cssDoc = DocumentManager.getOpenDocumentForPath(CSS_FILE); - expect(cssDoc._masterEditor).toBeFalsy(); - DocumentManager.getOpenDocumentForPath(CSS_FILE).getLine(0); + if(!useAutoTabSpaces){ + // if auto tab spaces is enabled, the space detect algo will read the file contents for detecting + // spacing, so these 2 lines won't apply. + expect(cssDoc._masterEditor).toBeFalsy(); + DocumentManager.getOpenDocumentForPath(CSS_FILE).getLine(0); + } expect(cssDoc._masterEditor).toBeTruthy(); // Close inline editor @@ -337,6 +341,14 @@ define(function (require, exports, module) { expect(testWindow.$(".CodeMirror").length).toBe(2); // HTML editor (working set) & JS editor (current) cssDoc = cssMasterEditor = null; + } + + it("should clean up (later) a master Editor auto-created by calling read-only Document API, if Editor not used by UI", async function () { + await testRef(false); + }); + + it("should clean up (later) a master Editor in auto tab space detect mode, auto-created by calling read-only Document API, if Editor not used by UI", async function () { + await testRef(true); }); }); }); From da60c71b75e86d264a8fc37b14f949d1f5681710 Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 20 Aug 2024 16:00:01 +0530 Subject: [PATCH 11/12] test: integ tests for auto tab space detection --- .../default/DebugCommands/MacroRunner.js | 30 ++++- test/UnitTestSuite.js | 1 + test/spec/space-detect-test-files/space-1.js | 3 + test/spec/space-detect-test-files/space-12.js | 3 + .../space-detect-test-files/space-none.js | 1 + test/spec/space-detect-test-files/tab-12.js | 3 + test/spec/space-detect-test-files/tab-2.js | 3 + test/spec/spacing-auto-detect-integ-test.js | 112 ++++++++++++++++++ 8 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 test/spec/space-detect-test-files/space-1.js create mode 100644 test/spec/space-detect-test-files/space-12.js create mode 100644 test/spec/space-detect-test-files/space-none.js create mode 100644 test/spec/space-detect-test-files/tab-12.js create mode 100644 test/spec/space-detect-test-files/tab-2.js create mode 100644 test/spec/spacing-auto-detect-integ-test.js diff --git a/src/extensions/default/DebugCommands/MacroRunner.js b/src/extensions/default/DebugCommands/MacroRunner.js index d82f894f49..3f344fa061 100644 --- a/src/extensions/default/DebugCommands/MacroRunner.js +++ b/src/extensions/default/DebugCommands/MacroRunner.js @@ -50,6 +50,8 @@ define(function (require, exports, module) { KeyEvent = brackets.getModule("utils/KeyEvent"), Commands = brackets.getModule("command/Commands"), PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Editor = brackets.getModule("editor/Editor"), + _ = brackets.getModule("thirdparty/lodash"), ProjectManager = brackets.getModule("project/ProjectManager"); /** @@ -304,6 +306,12 @@ define(function (require, exports, module) { } } + function validateEqual(obj1, obj2) { + if(!_.isEqual(obj1, obj2)){ + throw new Error(`validateEqual: expected ${JSON.stringify(obj1)} to equal ${JSON.stringify(obj2)}`); + } + } + /** * validates if the given mark type is present in the specified selections * @param {string} markType @@ -355,9 +363,29 @@ define(function (require, exports, module) { return PreferencesManager.get(key); } + const EDITING = { + setEditorSpacing: function (useTabs, spaceOrTabCount, isAutoMode) { + const activeEditor = EditorManager.getActiveEditor(); + if(!activeEditor){ + throw new Error(`No active editor found to setEditorSpacing`); + } + const fullPath = activeEditor.document.file.fullPath; + if(Editor.Editor.getAutoTabSpaces(fullPath) !== isAutoMode){ + Editor.Editor.setAutoTabSpaces(isAutoMode, fullPath); + isAutoMode && Editor.Editor._autoDetectTabSpaces(activeEditor, true, true); + } + Editor.Editor.setUseTabChar(useTabs); + if(useTabs) { + Editor.Editor.setTabSize(spaceOrTabCount); + } else { + Editor.Editor.setSpaceUnits(spaceOrTabCount); + } + } + }; + const __PR= { openFile, setCursors, expectCursorsToBe, keydown, typeAtCursor, validateText, validateAllMarks, validateMarks, - closeFile, closeAll, undo, redo, setPreference, getPreference + closeFile, closeAll, undo, redo, setPreference, getPreference, validateEqual, EDITING }; async function runMacro(macroText) { diff --git a/test/UnitTestSuite.js b/test/UnitTestSuite.js index af105b356d..a6197b59b6 100644 --- a/test/UnitTestSuite.js +++ b/test/UnitTestSuite.js @@ -117,6 +117,7 @@ define(function (require, exports, module) { require("spec/StateManager-test"); require("spec/TaskManager-integ-test"); require("spec/Generic-integ-test"); + require("spec/spacing-auto-detect-integ-test"); // Integrated extension tests require("spec/Extn-InAppNotifications-integ-test"); require("spec/Extn-RemoteFileAdapter-integ-test"); diff --git a/test/spec/space-detect-test-files/space-1.js b/test/spec/space-detect-test-files/space-1.js new file mode 100644 index 0000000000..313279e79a --- /dev/null +++ b/test/spec/space-detect-test-files/space-1.js @@ -0,0 +1,3 @@ +function f() { + console.log("hello"); +} \ No newline at end of file diff --git a/test/spec/space-detect-test-files/space-12.js b/test/spec/space-detect-test-files/space-12.js new file mode 100644 index 0000000000..b5e04a93a9 --- /dev/null +++ b/test/spec/space-detect-test-files/space-12.js @@ -0,0 +1,3 @@ +function f() { + console.log("hello"); +} \ No newline at end of file diff --git a/test/spec/space-detect-test-files/space-none.js b/test/spec/space-detect-test-files/space-none.js new file mode 100644 index 0000000000..e83f18a0aa --- /dev/null +++ b/test/spec/space-detect-test-files/space-none.js @@ -0,0 +1 @@ +function f() {console.log("hello");} \ No newline at end of file diff --git a/test/spec/space-detect-test-files/tab-12.js b/test/spec/space-detect-test-files/tab-12.js new file mode 100644 index 0000000000..0bab5b5416 --- /dev/null +++ b/test/spec/space-detect-test-files/tab-12.js @@ -0,0 +1,3 @@ +function x(){ + console.log(); +} diff --git a/test/spec/space-detect-test-files/tab-2.js b/test/spec/space-detect-test-files/tab-2.js new file mode 100644 index 0000000000..a01a65a230 --- /dev/null +++ b/test/spec/space-detect-test-files/tab-2.js @@ -0,0 +1,3 @@ +function x(){ + console.log(); +} diff --git a/test/spec/spacing-auto-detect-integ-test.js b/test/spec/spacing-auto-detect-integ-test.js new file mode 100644 index 0000000000..1de7c12095 --- /dev/null +++ b/test/spec/spacing-auto-detect-integ-test.js @@ -0,0 +1,112 @@ +/* + * GNU AGPL-3.0 License + * + * Copyright (c) 2021 - present core.ai . All rights reserved. + * Original work Copyright (c) 2013 - 2021 Adobe Systems Incorporated. All rights reserved. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License + * for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see https://opensource.org/licenses/AGPL-3.0. + * + */ + +/*global describe, it, beforeAll, afterAll */ + +define(function (require, exports, module) { + + + var SpecRunnerUtils = require("spec/SpecRunnerUtils"); + + describe("integration:Auto space and tabs detect", function () { + const testRootSpec = "/spec/space-detect-test-files/"; + let testProjectsFolder = SpecRunnerUtils.getTestPath(testRootSpec), + testWindow, + $, + __PR; // __PR can be debugged using debug menu> phoenix code diag tools> test builder + + beforeAll(async function () { + testWindow = await SpecRunnerUtils.createTestWindowAndRun(); + // Load module instances from brackets.test + await SpecRunnerUtils.loadProjectInTestWindow(testProjectsFolder); + __PR = testWindow.__PR; + $ = testWindow.$; + }, 30000); + + afterAll(async function () { + await __PR.closeAll(); + testWindow = null; + __PR = null; + $ = null; + await SpecRunnerUtils.closeTestWindow(); + }, 30000); + + function validateSpacing(type, value, autoMode) { + __PR.validateEqual($("#indent-type").text(), type); + __PR.validateEqual($("#indent-width-label").text(), value); + __PR.validateEqual($("#indent-auto").text(), autoMode); + } + + it(`should detect 1 space auto`, async function () { + await __PR.openFile("space-1.js"); + validateSpacing("Spaces:", "1", "Auto"); + await __PR.closeFile(); + }); + + it(`should detect 12 space as 10 spaces auto`, async function () { + await __PR.openFile("space-12.js"); + validateSpacing("Spaces:", "10", "Auto"); + await __PR.closeFile(); + }); + + it(`should detect no space as default 4 spaces auto`, async function () { + await __PR.openFile("space-none.js"); + validateSpacing("Spaces:", "4", "Auto"); + await __PR.closeFile(); + }); + + it(`should detect 2 tabs auto`, async function () { + await __PR.openFile("tab-2.js"); + validateSpacing("Tab Size:", "2", "Auto"); + await __PR.closeFile(); + }); + + it(`should detect 12 tabs as 10 tabs auto`, async function () { + await __PR.openFile("tab-12.js"); + validateSpacing("Tab Size:", "10", "Auto"); + await __PR.closeFile(); + }); + + it(`should be able to override individual file's tab/spacing in auto mode`, async function () { + await __PR.openFile("space-1.js"); + validateSpacing("Spaces:", "1", "Auto"); + $("#indent-type").click(); + validateSpacing("Tab Size:", "4", "Auto"); + // now switch to another file + await __PR.openFile("tab-2.js"); + validateSpacing("Tab Size:", "2", "Auto"); + // now switch back and it should remember the overridden settings + await __PR.openFile("space-1.js"); + validateSpacing("Tab Size:", "4", "Auto"); + // now change the tab units + __PR.EDITING.setEditorSpacing(true, 6, true); + validateSpacing("Tab Size:", "6", "Auto"); + // now close the file and switch to another file + await __PR.closeFile(); + await __PR.openFile("tab-2.js"); + validateSpacing("Tab Size:", "2", "Auto"); + // now switch back and it should remember the overridden settings + await __PR.openFile("space-1.js"); + validateSpacing("Tab Size:", "6", "Auto"); + await __PR.closeFile(); + }); + }); +}); From 4d5c0a01a089814e13db42783c7b5835c376da6e Mon Sep 17 00:00:00 2001 From: abose Date: Tue, 20 Aug 2024 16:49:29 +0530 Subject: [PATCH 12/12] test: integ tests for auto tab space detection --- src/editor/Editor.js | 23 +++++++++++-- .../default/DebugCommands/MacroRunner.js | 6 ++-- test/spec/spacing-auto-detect-integ-test.js | 33 +++++++++++++++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/editor/Editor.js b/src/editor/Editor.js index 3ed0db87ab..c578826781 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -2597,6 +2597,9 @@ define(function (require, exports, module) { computedValues.useTabChar = value; // persist explicitly user set values to storage tabSpacesStateManager.set(fullPath, computedValues); + Editor.forEveryEditor(editor=>{ + editor._updateOption(USE_TAB_CHAR); + }, fullPath); return true; } var options = fullPath && {context: fullPath}; @@ -2630,6 +2633,9 @@ define(function (require, exports, module) { computedValues.tabSize = value; // persist explicitly user set values to storage tabSpacesStateManager.set(fullPath, computedValues); + Editor.forEveryEditor(editor=>{ + editor._updateOption(TAB_SIZE); + }, fullPath); } return true; } @@ -2722,6 +2728,9 @@ define(function (require, exports, module) { computedValues.spaceUnits = value; // persist explicitly user set values to storage tabSpacesStateManager.set(fullPath, computedValues); + Editor.forEveryEditor(editor=>{ + editor._updateOption(SPACE_UNITS); + }, fullPath); } return true; } @@ -2847,11 +2856,19 @@ define(function (require, exports, module) { }; /** - * Runs callback for every Editor instance that currently exists + * Runs callback for every Editor instance that currently exists or only the editors matching the given fullPath. * @param {!function(!Editor)} callback + * @param {string} [fullPath] an optional second argument, if given will only callback for all editors + * that is editing the file for the given fullPath */ - Editor.forEveryEditor = function (callback) { - _instances.forEach(callback); + Editor.forEveryEditor = function (callback, fullPath) { + _instances.forEach(function (editor) { + if(!fullPath) { + callback(editor); + } else if(editor.document.file.fullPath === fullPath) { + callback(editor); + } + }); }; /** diff --git a/src/extensions/default/DebugCommands/MacroRunner.js b/src/extensions/default/DebugCommands/MacroRunner.js index 3f344fa061..0ae5f27a77 100644 --- a/src/extensions/default/DebugCommands/MacroRunner.js +++ b/src/extensions/default/DebugCommands/MacroRunner.js @@ -374,11 +374,11 @@ define(function (require, exports, module) { Editor.Editor.setAutoTabSpaces(isAutoMode, fullPath); isAutoMode && Editor.Editor._autoDetectTabSpaces(activeEditor, true, true); } - Editor.Editor.setUseTabChar(useTabs); + Editor.Editor.setUseTabChar(useTabs, fullPath); if(useTabs) { - Editor.Editor.setTabSize(spaceOrTabCount); + Editor.Editor.setTabSize(spaceOrTabCount, fullPath); } else { - Editor.Editor.setSpaceUnits(spaceOrTabCount); + Editor.Editor.setSpaceUnits(spaceOrTabCount, fullPath); } } }; diff --git a/test/spec/spacing-auto-detect-integ-test.js b/test/spec/spacing-auto-detect-integ-test.js index 1de7c12095..04ebf53d43 100644 --- a/test/spec/spacing-auto-detect-integ-test.js +++ b/test/spec/spacing-auto-detect-integ-test.js @@ -108,5 +108,38 @@ define(function (require, exports, module) { validateSpacing("Tab Size:", "6", "Auto"); await __PR.closeFile(); }); + + it(`should switching to fixed mode default to 4 spaces for all files`, async function () { + await __PR.openFile("tab-2.js"); + validateSpacing("Tab Size:", "2", "Auto"); + $("#indent-auto").click(); + validateSpacing("Spaces:", "4", "Fixed"); + await __PR.openFile("space-1.js"); + validateSpacing("Spaces:", "4", "Fixed"); + await __PR.closeFile(); + }); + + it(`should be able to change spacing/tabs settings of fixed mode`, async function () { + await __PR.openFile("tab-2.js"); + // now change the fixed width + __PR.EDITING.setEditorSpacing(true, 6, false); + validateSpacing("Tab Size:", "6", "Fixed"); + await __PR.openFile("space-12.js"); + validateSpacing("Tab Size:", "6", "Fixed"); + // revert back to defaults + __PR.EDITING.setEditorSpacing(false, 4, false); + await __PR.closeFile(); + }); + + it(`should toggling auto mode recompute the spacing`, async function () { + await __PR.openFile("tab-2.js"); + __PR.EDITING.setEditorSpacing(false, 3, true); + validateSpacing("Spaces:", "3", "Auto"); + // now toggle the auto to fixed and then to auto once to force recompute spacing + $("#indent-auto").click(); + $("#indent-auto").click(); + validateSpacing("Tab Size:", "2", "Auto"); + await __PR.closeFile(); + }); }); });