diff --git a/+MBeautifier/MIndenter.m b/+MBeautifier/MIndenter.m new file mode 100644 index 0000000..dc87aa4 --- /dev/null +++ b/+MBeautifier/MIndenter.m @@ -0,0 +1,196 @@ +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) + % 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; + % push keyword onto stack + stack = [stack, words{wordct}]; + + % correction for function keywords according to + % configuration + if (strcmp(stack{end}, 'function')) + switch lower(strategy) + case 'nestedfunctions' + if (numel(stack) == 1) + % top level function + layerNext = layerNext - 1; + else + 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; + 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 lower(strategy) + case 'nestedfunctions' + if (numel(stack) == 1) + % top level function + layerNext = layerNext + 1; + else + if (strcmp(stack{end-1}, 'function')) + % nested function + layer = layer - 1; + else + % class method + % do nothing + end + 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 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 +