From 2e40702e0f5ac7dc237e83aee232c2d1455146d2 Mon Sep 17 00:00:00 2001 From: Azat Alimov <32402726+mkslanc@users.noreply.github.com> Date: Wed, 27 Mar 2024 13:43:58 +0400 Subject: [PATCH] folding for php alternative syntax (#5491) --- src/mode/folding/php.js | 127 +++++++++++++++++++++++++++++++++++ src/mode/folding/php_test.js | 57 ++++++++++++++++ src/mode/php.js | 6 +- 3 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 src/mode/folding/php.js create mode 100644 src/mode/folding/php_test.js diff --git a/src/mode/folding/php.js b/src/mode/folding/php.js new file mode 100644 index 00000000000..0b20322fe3e --- /dev/null +++ b/src/mode/folding/php.js @@ -0,0 +1,127 @@ +"use strict"; + +var oop = require("../../lib/oop"); +var MixedFoldMode = require("./mixed").FoldMode; +var CstyleFoldMode = require("./cstyle").FoldMode; +var Range = require("../../range").Range; +var TokenIterator = require("../../token_iterator").TokenIterator; + + +var FoldMode = exports.FoldMode = function () { + this.cstyleFoldMode = new CstyleFoldMode(); + MixedFoldMode.call(this, this, { + "js-": new CstyleFoldMode(), + "css-": new CstyleFoldMode(), + "php-": this + }); +}; + +oop.inherits(FoldMode, MixedFoldMode); + +(function () { + this.indentKeywords = { + "if": 1, + "while": 1, + "for": 1, + "foreach": 1, + "switch": 1, + "else": 0, + "elseif": 0, + "endif": -1, + "endwhile": -1, + "endfor": -1, + "endforeach": -1, + "endswitch": -1 + }; + + this.foldingStartMarker = /(?:\s|^)(if|else|elseif|while|for|foreach|switch).*\:/i; + this.foldingStopMarker = /(?:\s|^)(endif|endwhile|endfor|endforeach|endswitch)\;/i; + + this.getFoldWidgetRange = function (session, foldStyle, row) { + var line = session.doc.getLine(row); + var match = this.foldingStartMarker.exec(line); + if (match) { + return this.phpBlock(session, row, match.index + 2); + } + + var match = this.foldingStopMarker.exec(line); + if (match) { + return this.phpBlock(session, row, match.index + 2); + } + return this.cstyleFoldMode.getFoldWidgetRange(session, foldStyle, row); + }; + + + // must return "" if there's no fold, to enable caching + this.getFoldWidget = function (session, foldStyle, row) { + var line = session.getLine(row); + var isStart = this.foldingStartMarker.test(line); + var isEnd = this.foldingStopMarker.test(line); + if (isStart && !isEnd) { + var match = this.foldingStartMarker.exec(line); + var keyword = match && match[1].toLowerCase(); + if (keyword) { + var type = session.getTokenAt(row, match.index + 2).type; + if (type == "keyword") { + return "start"; + } + } + } + if (isEnd && foldStyle === "markbeginend") { + var match = this.foldingStopMarker.exec(line); + var keyword = match && match[1].toLowerCase(); + if (keyword) { + var type = session.getTokenAt(row, match.index + 2).type; + if (type == "keyword") { + return "end"; + } + } + } + return this.cstyleFoldMode.getFoldWidget(session, foldStyle, row); + }; + + this.phpBlock = function (session, row, column, tokenRange) { + var stream = new TokenIterator(session, row, column); + + var token = stream.getCurrentToken(); + if (!token || token.type != "keyword") return; + + var val = token.value; + var stack = [val]; + var dir = this.indentKeywords[val]; + + if (val === "else" || val === "elseif") { + dir = 1; + } + + if (!dir) return; + + var startColumn = dir === -1 ? stream.getCurrentTokenColumn() : session.getLine(row).length; + var startRow = row; + + stream.step = dir === -1 ? stream.stepBackward : stream.stepForward; + while (token = stream.step()) { + if (token.type !== "keyword") continue; + var level = dir * this.indentKeywords[token.value]; + + if (level > 0) { + stack.unshift(token.value); + } + else if (level <= 0) { + stack.shift(); + if (!stack.length) break; + if (level === 0) stack.unshift(token.value); + } + } + + if (!token) return null; + + if (tokenRange) return stream.getCurrentTokenRange(); + + var row = stream.getCurrentTokenRow(); + if (dir === -1) return new Range( + row, session.getLine(row).length, startRow, startColumn); else return new Range( + startRow, startColumn, row, stream.getCurrentTokenColumn()); + }; + +}).call(FoldMode.prototype); diff --git a/src/mode/folding/php_test.js b/src/mode/folding/php_test.js new file mode 100644 index 00000000000..84c1200f697 --- /dev/null +++ b/src/mode/folding/php_test.js @@ -0,0 +1,57 @@ +if (typeof process !== "undefined") require("amd-loader"); + +"use strict"; + +var PHPMode = require("../php").Mode; +var EditSession = require("../../edit_session").EditSession; +var assert = require("../../test/assertions"); + +module.exports = { + setUp: function () { + this.mode = new PHPMode(); + }, + + "test: php folding with alternative syntax": function () { + var session = new EditSession([ + ' 0):', + ' echo "Number is positive";', ' else:', + ' echo "Number is negative";', 'endif;', ' break;', ' default:', + ' echo "Number is not zero";', ' }', 'foreach (array(1, 2, 3) as $num):', + ' echo "Num: $num";', ' endforeach;', '}', '?>' + ]); + + session.setFoldStyle("markbeginend"); + session.setMode(this.mode); + session.bgTokenizer.$worker(); + + assert.equal(session.getFoldWidget(0), ""); + assert.equal(session.getFoldWidget(1), ""); + assert.equal(session.getFoldWidget(2), "start"); + assert.equal(session.getFoldWidget(3), "start"); + assert.equal(session.getFoldWidget(4), ""); + assert.equal(session.getFoldWidget(5), ""); + assert.equal(session.getFoldWidget(6), "start"); + assert.equal(session.getFoldWidget(7), ""); + assert.equal(session.getFoldWidget(8), "start"); + assert.equal(session.getFoldWidget(10), "start"); + assert.equal(session.getFoldWidget(12), "end"); + assert.equal(session.getFoldWidget(16), "end"); + assert.equal(session.getFoldWidget(17), "start"); + assert.equal(session.getFoldWidget(19), "end"); + assert.equal(session.getFoldWidget(20), "end"); + + assert.range(session.getFoldWidgetRange(2), 2, 1, 20, 0); // Range for the function's foldable section + assert.range(session.getFoldWidgetRange(3), 3, 21, 16, 7); // Range for the 'switch' statement + assert.range(session.getFoldWidgetRange(6), 6, 29, 8, 11); // Range for the 'if' block + assert.range(session.getFoldWidgetRange(8), 8, 32, 10, 11); // Range for the 'elseif' block + assert.range(session.getFoldWidgetRange(10), 10, 16, 12, 0); // Range for the 'else' block + assert.range(session.getFoldWidgetRange(12), 10, 16, 12, 0); // Range for the 'endif' line + assert.range(session.getFoldWidgetRange(17), 17, 33, 19, 3); + assert.range(session.getFoldWidgetRange(19), 17, 33, 19, 3); + } +}; + + +if (typeof module !== "undefined" && module === require.main) require("asyncjs").test.testcase(module.exports).exec(); diff --git a/src/mode/php.js b/src/mode/php.js index 459f02661c6..b665067bb8d 100644 --- a/src/mode/php.js +++ b/src/mode/php.js @@ -7,7 +7,7 @@ var PhpLangHighlightRules = require("./php_highlight_rules").PhpLangHighlightRul var MatchingBraceOutdent = require("./matching_brace_outdent").MatchingBraceOutdent; var WorkerClient = require("../worker/worker_client").WorkerClient; var PhpCompletions = require("./php_completions").PhpCompletions; -var CStyleFoldMode = require("./folding/cstyle").FoldMode; +var PhpFoldMode = require("./folding/php").FoldMode; var unicode = require("../unicode"); var HtmlMode = require("./html").Mode; var JavaScriptMode = require("./javascript").Mode; @@ -18,7 +18,7 @@ var PhpMode = function(opts) { this.$outdent = new MatchingBraceOutdent(); this.$behaviour = this.$defaultBehaviour; this.$completer = new PhpCompletions(); - this.foldingRules = new CStyleFoldMode(); + this.foldingRules = new PhpFoldMode(); }; oop.inherits(PhpMode, TextMode); @@ -91,7 +91,7 @@ var Mode = function(opts) { "css-": CssMode, "php-": PhpMode }); - this.foldingRules.subModes["php-"] = new CStyleFoldMode(); + this.foldingRules = new PhpFoldMode(); }; oop.inherits(Mode, HtmlMode);