From 73796db47d8084ec4c917e028a10867bcdf09e5a Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Thu, 17 Oct 2019 17:59:07 +0200 Subject: [PATCH 1/5] Replace MATLAB's smart indent function #75. --- +MBeautifier/MIndenter.m | 181 +++++++++++++++++++++++++++++++++++++++ MBeautify.m | 32 +++++++ 2 files changed, 213 insertions(+) create mode 100644 +MBeautifier/MIndenter.m diff --git a/+MBeautifier/MIndenter.m b/+MBeautifier/MIndenter.m new file mode 100644 index 0000000..c18b96c --- /dev/null +++ b/+MBeautifier/MIndenter.m @@ -0,0 +1,181 @@ +classdef MIndenter < handle + % Performs code indenting. Should not be used directly but only by + % MBeautify. + + properties (Constant) + Delimiters = {' ', '\f', '\n', '\r', '\t', '\v', ... + ','}; + KeywordsIncrease = {'function', 'classdef', 'properties', ... + 'methods', 'if', 'for', 'parfor', 'switch', 'try', 'while'}; + KeywordsSandwich = {'else', 'elseif', 'case', 'otherwise', ... + 'catch'}; + KeywordsDecrease = {'end', 'end;'}; + end + + properties (Access = private) + Configuration; + end + + methods + function obj = MIndenter(configuration) + % Creates a new formatter using the passed configuration. + obj.Configuration = configuration; + end + + function indentedSource = performIndenting(obj, source) + % indentation strategy: + % allfunctions (default) = AllFunctionIndent + % nestedfunctions = MixedFunctionIndent (MATLAB default) + % noindent = ClassicFunctionIndent + strategy = obj.Configuration.specialRule('Indentation_Strategy').Value; + + % determine indent string + indentationCharacter = obj.Configuration.specialRule('IndentationCharacter').Value; + indentationCount = obj.Configuration.specialRule('IndentationCount').ValueAsDouble; + if strcmpi(indentationCharacter, 'white-space') + indent = ' '; + for i = 2:indentationCount + indent = [' ', indent]; + end + elseif strcmpi(indentationCharacter, 'tab') + indent = '\t'; + else + warning('MBeautifier:IllegalSetting:IndentationCharacter', 'MBeautifier: The indentation character must be set to "white-space" or "tab". MBeautifier using MATLAB defaults.'); + indent = ' '; + end + + % TODO + %makeBlankLinesEmpty = Configuration.specialRule('Indentation_TrimBlankLines').ValueAsDouble; + + % currently in continuation mode (line before ended with ...)? + continuationMode = 0; + % layer of indentatino (next line) + layerNext = 0; + % this stack keeps track of the keywords + stack = {}; + + % start indenting + lines = splitlines(source); + for linect = 1:numel(lines) + % layer of indentation (current line) + layer = layerNext; + + % remove existing indentation and whitespace + lines{linect} = strtrim(lines{linect}); + + % split line in words + words = split(lines{linect}, obj.Delimiters); + + % ignore empty lines and comments + if (~isempty(lines{linect}) && (lines{linect}(1) ~= '%')) + % find keywords and adjust indent + for wordct = 1:numel(words) + % look for keywords that increase indent + if (sum(strcmp(words{wordct}, obj.KeywordsIncrease))) + layerNext = layerNext + 1; + % push keyword onto stack + stack = [stack, words{wordct}]; + + % correction for function keywords according to + % configuration + if (strcmp(stack{end}, 'function')) + switch (strategy) + case 'nestedfunctions' + if (numel(stack) == 1) + % top level function + layerNext = layerNext - 1; + else + % nested function + layer = layer + 1; + layerNext = layerNext + 1; + end + case 'noindent' + layerNext = layerNext - 1; + otherwise + end + end + + % correction for switch + if (strcmp(stack{end}, 'switch')) + layerNext = layerNext + 1; + end + end + + % look for sandwich keywords + if (sum(strcmp(words{wordct}, obj.KeywordsSandwich))) + if (wordct == 1) + % at the beginning, decrease only current indent + layer = layer - 1; + end + end + + % look for end that decreases the indent + if (sum(strcmp(words{wordct}, obj.KeywordsDecrease))) + if (wordct == 1) + % end at the beginning decreases indent of this line + layer = layer - 1; + end + % inline end may alter the indent of the next line + layerNext = layerNext - 1; + + % correction for function keywords according to + % configuration + if (strcmp(stack{end}, 'function')) + switch (strategy) + case 'nestedfunctions' + if (numel(stack) == 1) + % top level function + layerNext = layerNext + 1; + else + % nested function + layerNext = layerNext - 1; + end + case 'noindent' + if (wordct == 1) + % end at the beginning decreases indent of this line + layer = layer + 1; + end + layerNext = layerNext + 1; + otherwise + % do nothing + end + end + + % correction for switch + if (strcmp(stack{end}, 'switch')) + if (wordct == 1) + % end at the beginning decreases indent of this line + layer = layer - 1; + end + layerNext = layerNext - 1; + end + + % pop keyword + stack(end) = []; + end + end + + % look for continuation lines + if (strcmp(words{end}, '...')) + if (~continuationMode) + continuationMode = 1; + layerNext = layerNext + 1; + end + else + if (continuationMode) + continuationMode = 0; + layerNext = layerNext - 1; + end + end + end + + % add correct indentation + for ict = 1:layer + lines{linect} = [indent, lines{linect}]; + end + end + complete = join(lines, newline); + indentedSource = complete{1}; + end + end +end diff --git a/MBeautify.m b/MBeautify.m index 543f441..71396f0 100644 --- a/MBeautify.m +++ b/MBeautify.m @@ -29,6 +29,38 @@ %% Public API methods (Static = true) + + function formatFileNoEditor(file, outFile) + % Formats the file specified in the first argument. If the + % second argument is also specified, the formatted source is + % saved to this file. The input and the output file can be the + % same, in which case the format operation is carried out + % in-place. + % + if ~exist(file, 'file') + return; + end + + text = fileread(file); + + % Format the code + configuration = MBeautify.getConfiguration(); + formatter = MBeautifier.MFormatter(configuration); + text = formatter.performFormatting(text); + + % Indent the code + indenter = MBeautifier.MIndenter(configuration); + text = indenter.performIndenting(text); + + if (nargin == 1) + outFile = file; + end + + % write formatted text to file + fid = fopen(outFile, 'wt'); + fprintf(fid, '%s', text); + fclose(fid); + end function formatFile(file, outFile) % Formats the file specified in the first argument. The file is opened in the Matlab Editor. If the second From 67fdcfe523a07dcd5db249fa5e90bbc61c2ada7d Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Thu, 17 Oct 2019 18:18:30 +0200 Subject: [PATCH 2/5] Set default indentation strategy to NestedFunctions. --- resources/settings/MBeautyConfigurationRules.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/settings/MBeautyConfigurationRules.xml b/resources/settings/MBeautyConfigurationRules.xml index 8f5bb6d..578c6e0 100644 --- a/resources/settings/MBeautyConfigurationRules.xml +++ b/resources/settings/MBeautyConfigurationRules.xml @@ -127,7 +127,7 @@ : - + case @@ -174,7 +174,7 @@ 1 - + MaximalNewLines @@ -238,7 +238,7 @@ Indentation_Strategy - AllFunctions + NestedFunctions - \ No newline at end of file + From 2c30ff6898ffc71cc4d046c635ceb831aa99a683 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Thu, 17 Oct 2019 18:36:12 +0200 Subject: [PATCH 3/5] Fix: Added missing lower. --- +MBeautifier/MIndenter.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+MBeautifier/MIndenter.m b/+MBeautifier/MIndenter.m index c18b96c..c6428d6 100644 --- a/+MBeautifier/MIndenter.m +++ b/+MBeautifier/MIndenter.m @@ -79,7 +79,7 @@ % correction for function keywords according to % configuration if (strcmp(stack{end}, 'function')) - switch (strategy) + switch lower(strategy) case 'nestedfunctions' if (numel(stack) == 1) % top level function @@ -121,7 +121,7 @@ % correction for function keywords according to % configuration if (strcmp(stack{end}, 'function')) - switch (strategy) + switch lower(strategy) case 'nestedfunctions' if (numel(stack) == 1) % top level function From 0e0e6d5f79f6142935dbd8b2e2ce4b9908b09a57 Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 21 Oct 2019 14:54:32 +0200 Subject: [PATCH 4/5] Fix: Added if to ignore end of line comments. --- +MBeautifier/MIndenter.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/+MBeautifier/MIndenter.m b/+MBeautifier/MIndenter.m index c6428d6..6318478 100644 --- a/+MBeautifier/MIndenter.m +++ b/+MBeautifier/MIndenter.m @@ -70,6 +70,11 @@ if (~isempty(lines{linect}) && (lines{linect}(1) ~= '%')) % find keywords and adjust indent for wordct = 1:numel(words) + % detect end of line comments + if (strcmp(words{wordct}, '%')) + break; + end + % look for keywords that increase indent if (sum(strcmp(words{wordct}, obj.KeywordsIncrease))) layerNext = layerNext + 1; From b718765120fa80eb258429e0c3788c9fdee9d0eb Mon Sep 17 00:00:00 2001 From: Michael Riesch Date: Mon, 21 Oct 2019 15:13:06 +0200 Subject: [PATCH 5/5] Fix: Added case that considers class methods. --- +MBeautifier/MIndenter.m | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/+MBeautifier/MIndenter.m b/+MBeautifier/MIndenter.m index 6318478..dc87aa4 100644 --- a/+MBeautifier/MIndenter.m +++ b/+MBeautifier/MIndenter.m @@ -90,9 +90,14 @@ % top level function layerNext = layerNext - 1; else - % nested function - layer = layer + 1; - layerNext = layerNext + 1; + if (strcmp(stack{end-1}, 'function')) + % nested function + layer = layer + 1; + layerNext = layerNext + 1; + else + % class method + % do nothing + end end case 'noindent' layerNext = layerNext - 1; @@ -132,8 +137,13 @@ % top level function layerNext = layerNext + 1; else - % nested function - layerNext = layerNext - 1; + if (strcmp(stack{end-1}, 'function')) + % nested function + layer = layer - 1; + else + % class method + % do nothing + end end case 'noindent' if (wordct == 1)