├── .gitignore ├── .npmignore ├── LICENSE ├── package.json ├── readme.md ├── src ├── common │ ├── charCode.ts │ ├── position.ts │ └── range.ts ├── index.ts ├── pieceTreeBase.ts ├── pieceTreeBuilder.ts ├── rbTreeBase.ts └── test │ └── piecetree.test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .node_modules/ 3 | built/* 4 | tests/cases/rwc/* 5 | tests/cases/test262/* 6 | tests/cases/perf/* 7 | !tests/cases/webharness/compilerToString.js 8 | test-args.txt 9 | ~*.docx 10 | \#*\# 11 | .\#* 12 | tests/baselines/local/* 13 | tests/baselines/local.old/* 14 | tests/services/baselines/local/* 15 | tests/baselines/prototyping/local/* 16 | tests/baselines/rwc/* 17 | tests/baselines/test262/* 18 | tests/baselines/reference/projectOutput/* 19 | tests/baselines/local/projectOutput/* 20 | tests/baselines/reference/testresults.tap 21 | tests/services/baselines/prototyping/local/* 22 | tests/services/browser/typescriptServices.js 23 | src/harness/*.js 24 | src/compiler/diagnosticInformationMap.generated.ts 25 | src/compiler/diagnosticMessages.generated.json 26 | src/parser/diagnosticInformationMap.generated.ts 27 | src/parser/diagnosticMessages.generated.json 28 | rwc-report.html 29 | *.swp 30 | build.json 31 | *.actual 32 | tests/webTestServer.js 33 | tests/webTestServer.js.map 34 | tests/webhost/*.d.ts 35 | tests/webhost/webtsc.js 36 | tests/cases/**/*.js 37 | tests/cases/**/*.js.map 38 | *.config 39 | scripts/debug.bat 40 | scripts/run.bat 41 | scripts/word2md.js 42 | scripts/buildProtocol.js 43 | scripts/ior.js 44 | scripts/authors.js 45 | scripts/configurePrerelease.js 46 | scripts/open-user-pr.js 47 | scripts/processDiagnosticMessages.d.ts 48 | scripts/processDiagnosticMessages.js 49 | scripts/produceLKG.js 50 | scripts/importDefinitelyTypedTests/importDefinitelyTypedTests.js 51 | scripts/generateLocalizedDiagnosticMessages.js 52 | scripts/*.js.map 53 | scripts/typings/ 54 | coverage/ 55 | internal/ 56 | **/.DS_Store 57 | .settings 58 | **/.vs 59 | **/.vscode 60 | !**/.vscode/tasks.json 61 | !tests/cases/projects/projectOption/**/node_modules 62 | !tests/cases/projects/NodeModulesSearch/**/* 63 | !tests/baselines/reference/project/nodeModules*/**/* 64 | .idea 65 | yarn.lock 66 | yarn-error.log 67 | .parallelperf.* 68 | tests/cases/user/*/package-lock.json 69 | tests/cases/user/*/node_modules/ 70 | tests/cases/user/*/**/*.js 71 | tests/cases/user/*/**/*.js.map 72 | tests/cases/user/*/**/*.d.ts 73 | !tests/cases/user/zone.js/ 74 | !tests/cases/user/bignumber.js/ 75 | !tests/cases/user/discord.js/ 76 | tests/baselines/reference/dt 77 | .failed-tests 78 | TEST-results.xml 79 | package-lock.json 80 | 81 | out 82 | lib -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-piece-tree", 3 | "version": "1.2.0", 4 | "description": "Piece Tree", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "start": "npm run build && npm run watch", 9 | "build": "tsc", 10 | "watch": "tsc -w", 11 | "pretest": "npm run build", 12 | "test": "jest --forceExit", 13 | "fasttest": "jest --forceExit" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/rebornix/PieceTree.git" 18 | }, 19 | "author": "rebornix", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/rebornix/PieceTree/issues" 23 | }, 24 | "homepage": "https://github.com/rebornix/PieceTree#readme", 25 | "devDependencies": { 26 | "@types/jest": "^21.1.8", 27 | "jest": "^21.2.1", 28 | "ts-jest": "^21.2.4", 29 | "typescript": "^3.4.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Piece Tree 2 | 3 | The underling text buffer used in VS Code/Monaco. For detailed architecture behind it, please read [Text Buffer Reimplementation](https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation). 4 | 5 | ``` 6 | npm install vscode-piece-tree 7 | ``` 8 | ## API 9 | 10 | ```typescript 11 | const pieceTreeTextBufferBuilder = new PieceTreeTextBufferBuilder(); 12 | pieceTreeTextBufferBuilder.acceptChunk('abc\n'); 13 | pieceTreeTextBufferBuilder.acceptChunk('def'); 14 | const pieceTreeFactory = pieceTreeTextBufferBuilder.finish(true); 15 | const pieceTree = pieceTreeFactory.create(DefaultEndOfLine.LF); 16 | 17 | pieceTree.getLineCount(); // 2 18 | pieceTree.getLineContent(1); // 'abc' 19 | pieceTree.getLineContent(2); // 'def' 20 | 21 | pieceTree.insert(1, '+'); 22 | pieceTree.getLineCount(); // 2 23 | pieceTree.getLineContent(1); // 'a+bc' 24 | pieceTree.getLineContent(2); // 'def' 25 | ``` 26 | -------------------------------------------------------------------------------- /src/common/charCode.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | export const enum CharCode { 7 | Null = 0, 8 | /** 9 | * The `\b` character. 10 | */ 11 | Backspace = 8, 12 | /** 13 | * The `\t` character. 14 | */ 15 | Tab = 9, 16 | /** 17 | * The `\n` character. 18 | */ 19 | LineFeed = 10, 20 | /** 21 | * The `\r` character. 22 | */ 23 | CarriageReturn = 13, 24 | Space = 32, 25 | /** 26 | * The `!` character. 27 | */ 28 | ExclamationMark = 33, 29 | /** 30 | * The `"` character. 31 | */ 32 | DoubleQuote = 34, 33 | /** 34 | * The `#` character. 35 | */ 36 | Hash = 35, 37 | /** 38 | * The `$` character. 39 | */ 40 | DollarSign = 36, 41 | /** 42 | * The `%` character. 43 | */ 44 | PercentSign = 37, 45 | /** 46 | * The `&` character. 47 | */ 48 | Ampersand = 38, 49 | /** 50 | * The `'` character. 51 | */ 52 | SingleQuote = 39, 53 | /** 54 | * The `(` character. 55 | */ 56 | OpenParen = 40, 57 | /** 58 | * The `)` character. 59 | */ 60 | CloseParen = 41, 61 | /** 62 | * The `*` character. 63 | */ 64 | Asterisk = 42, 65 | /** 66 | * The `+` character. 67 | */ 68 | Plus = 43, 69 | /** 70 | * The `,` character. 71 | */ 72 | Comma = 44, 73 | /** 74 | * The `-` character. 75 | */ 76 | Dash = 45, 77 | /** 78 | * The `.` character. 79 | */ 80 | Period = 46, 81 | /** 82 | * The `/` character. 83 | */ 84 | Slash = 47, 85 | 86 | Digit0 = 48, 87 | Digit1 = 49, 88 | Digit2 = 50, 89 | Digit3 = 51, 90 | Digit4 = 52, 91 | Digit5 = 53, 92 | Digit6 = 54, 93 | Digit7 = 55, 94 | Digit8 = 56, 95 | Digit9 = 57, 96 | 97 | /** 98 | * The `:` character. 99 | */ 100 | Colon = 58, 101 | /** 102 | * The `;` character. 103 | */ 104 | Semicolon = 59, 105 | /** 106 | * The `<` character. 107 | */ 108 | LessThan = 60, 109 | /** 110 | * The `=` character. 111 | */ 112 | Equals = 61, 113 | /** 114 | * The `>` character. 115 | */ 116 | GreaterThan = 62, 117 | /** 118 | * The `?` character. 119 | */ 120 | QuestionMark = 63, 121 | /** 122 | * The `@` character. 123 | */ 124 | AtSign = 64, 125 | 126 | A = 65, 127 | B = 66, 128 | C = 67, 129 | D = 68, 130 | E = 69, 131 | F = 70, 132 | G = 71, 133 | H = 72, 134 | I = 73, 135 | J = 74, 136 | K = 75, 137 | L = 76, 138 | M = 77, 139 | N = 78, 140 | O = 79, 141 | P = 80, 142 | Q = 81, 143 | R = 82, 144 | S = 83, 145 | T = 84, 146 | U = 85, 147 | V = 86, 148 | W = 87, 149 | X = 88, 150 | Y = 89, 151 | Z = 90, 152 | 153 | /** 154 | * The `[` character. 155 | */ 156 | OpenSquareBracket = 91, 157 | /** 158 | * The `\` character. 159 | */ 160 | Backslash = 92, 161 | /** 162 | * The `]` character. 163 | */ 164 | CloseSquareBracket = 93, 165 | /** 166 | * The `^` character. 167 | */ 168 | Caret = 94, 169 | /** 170 | * The `_` character. 171 | */ 172 | Underline = 95, 173 | /** 174 | * The ``(`)`` character. 175 | */ 176 | BackTick = 96, 177 | 178 | a = 97, 179 | b = 98, 180 | c = 99, 181 | d = 100, 182 | e = 101, 183 | f = 102, 184 | g = 103, 185 | h = 104, 186 | i = 105, 187 | j = 106, 188 | k = 107, 189 | l = 108, 190 | m = 109, 191 | n = 110, 192 | o = 111, 193 | p = 112, 194 | q = 113, 195 | r = 114, 196 | s = 115, 197 | t = 116, 198 | u = 117, 199 | v = 118, 200 | w = 119, 201 | x = 120, 202 | y = 121, 203 | z = 122, 204 | 205 | /** 206 | * The `{` character. 207 | */ 208 | OpenCurlyBrace = 123, 209 | /** 210 | * The `|` character. 211 | */ 212 | Pipe = 124, 213 | /** 214 | * The `}` character. 215 | */ 216 | CloseCurlyBrace = 125, 217 | /** 218 | * The `~` character. 219 | */ 220 | Tilde = 126, 221 | 222 | U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent 223 | U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent 224 | U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent 225 | U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde 226 | U_Combining_Macron = 0x0304, // U+0304 Combining Macron 227 | U_Combining_Overline = 0x0305, // U+0305 Combining Overline 228 | U_Combining_Breve = 0x0306, // U+0306 Combining Breve 229 | U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above 230 | U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis 231 | U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above 232 | U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above 233 | U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent 234 | U_Combining_Caron = 0x030C, // U+030C Combining Caron 235 | U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above 236 | U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above 237 | U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent 238 | U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu 239 | U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve 240 | U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above 241 | U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above 242 | U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above 243 | U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right 244 | U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below 245 | U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below 246 | U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below 247 | U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below 248 | U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above 249 | U_Combining_Horn = 0x031B, // U+031B Combining Horn 250 | U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below 251 | U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below 252 | U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below 253 | U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below 254 | U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below 255 | U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below 256 | U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below 257 | U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below 258 | U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below 259 | U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below 260 | U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below 261 | U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla 262 | U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek 263 | U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below 264 | U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below 265 | U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below 266 | U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below 267 | U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below 268 | U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below 269 | U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below 270 | U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below 271 | U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below 272 | U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line 273 | U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line 274 | U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay 275 | U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay 276 | U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay 277 | U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay 278 | U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay 279 | U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below 280 | U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below 281 | U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below 282 | U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below 283 | U_Combining_X_Above = 0x033D, // U+033D Combining X Above 284 | U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde 285 | U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline 286 | U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark 287 | U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark 288 | U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni 289 | U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis 290 | U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos 291 | U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni 292 | U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above 293 | U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below 294 | U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below 295 | U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below 296 | U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above 297 | U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above 298 | U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above 299 | U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below 300 | U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below 301 | U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner 302 | U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above 303 | U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above 304 | U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata 305 | U_Combining_X_Below = 0x0353, // U+0353 Combining X Below 306 | U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below 307 | U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below 308 | U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below 309 | U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above 310 | U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right 311 | U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below 312 | U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below 313 | U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above 314 | U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below 315 | U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve 316 | U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron 317 | U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below 318 | U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde 319 | U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve 320 | U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below 321 | U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A 322 | U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E 323 | U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I 324 | U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O 325 | U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U 326 | U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C 327 | U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D 328 | U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H 329 | U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M 330 | U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R 331 | U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T 332 | U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V 333 | U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X 334 | 335 | /** 336 | * Unicode Character 'LINE SEPARATOR' (U+2028) 337 | * http://www.fileformat.info/info/unicode/char/2028/index.htm 338 | */ 339 | LINE_SEPARATOR_2028 = 8232, 340 | 341 | // http://www.fileformat.info/info/unicode/category/Sk/list.htm 342 | U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX 343 | U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT 344 | U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS 345 | U_MACRON = 0x00AF, // U+00AF MACRON 346 | U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT 347 | U_CEDILLA = 0x00B8, // U+00B8 CEDILLA 348 | U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD 349 | U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD 350 | U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD 351 | U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD 352 | U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING 353 | U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING 354 | U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK 355 | U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK 356 | U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN 357 | U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN 358 | U_BREVE = 0x02D8, // U+02D8 BREVE 359 | U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE 360 | U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE 361 | U_OGONEK = 0x02DB, // U+02DB OGONEK 362 | U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE 363 | U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT 364 | U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK 365 | U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT 366 | U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR 367 | U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR 368 | U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR 369 | U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR 370 | U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR 371 | U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK 372 | U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK 373 | U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED 374 | U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD 375 | U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD 376 | U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD 377 | U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD 378 | U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING 379 | U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT 380 | U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT 381 | U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT 382 | U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE 383 | U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON 384 | U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE 385 | U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE 386 | U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE 387 | U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE 388 | U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF 389 | U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF 390 | U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW 391 | U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN 392 | U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS 393 | U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS 394 | U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS 395 | U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI 396 | U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI 397 | U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI 398 | U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA 399 | U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA 400 | U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI 401 | U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA 402 | U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA 403 | U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI 404 | U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA 405 | U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA 406 | U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA 407 | U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA 408 | U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA 409 | 410 | 411 | U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE' 412 | 413 | /** 414 | * UTF-8 BOM 415 | * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF) 416 | * http://www.fileformat.info/info/unicode/char/feff/index.htm 417 | */ 418 | UTF8_BOM = 65279 419 | } -------------------------------------------------------------------------------- /src/common/position.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /** 7 | * A position in the editor. This interface is suitable for serialization. 8 | */ 9 | export interface IPosition { 10 | /** 11 | * line number (starts at 1) 12 | */ 13 | readonly lineNumber: number; 14 | /** 15 | * column (the first character in a line is between column 1 and column 2) 16 | */ 17 | readonly column: number; 18 | } 19 | 20 | /** 21 | * A position in the editor. 22 | */ 23 | export class Position { 24 | /** 25 | * line number (starts at 1) 26 | */ 27 | public readonly lineNumber: number; 28 | /** 29 | * column (the first character in a line is between column 1 and column 2) 30 | */ 31 | public readonly column: number; 32 | 33 | constructor(lineNumber: number, column: number) { 34 | this.lineNumber = lineNumber; 35 | this.column = column; 36 | } 37 | 38 | /** 39 | * Create a new postion from this position. 40 | * 41 | * @param newLineNumber new line number 42 | * @param newColumn new column 43 | */ 44 | with(newLineNumber: number = this.lineNumber, newColumn: number = this.column): Position { 45 | if (newLineNumber === this.lineNumber && newColumn === this.column) { 46 | return this; 47 | } else { 48 | return new Position(newLineNumber, newColumn); 49 | } 50 | } 51 | 52 | /** 53 | * Derive a new position from this position. 54 | * 55 | * @param deltaLineNumber line number delta 56 | * @param deltaColumn column delta 57 | */ 58 | delta(deltaLineNumber: number = 0, deltaColumn: number = 0): Position { 59 | return this.with(this.lineNumber + deltaLineNumber, this.column + deltaColumn); 60 | } 61 | 62 | /** 63 | * Test if this position equals other position 64 | */ 65 | public equals(other: IPosition): boolean { 66 | return Position.equals(this, other); 67 | } 68 | 69 | /** 70 | * Test if position `a` equals position `b` 71 | */ 72 | public static equals(a: IPosition | null, b: IPosition | null): boolean { 73 | if (!a && !b) { 74 | return true; 75 | } 76 | return ( 77 | !!a && 78 | !!b && 79 | a.lineNumber === b.lineNumber && 80 | a.column === b.column 81 | ); 82 | } 83 | 84 | /** 85 | * Test if this position is before other position. 86 | * If the two positions are equal, the result will be false. 87 | */ 88 | public isBefore(other: IPosition): boolean { 89 | return Position.isBefore(this, other); 90 | } 91 | 92 | /** 93 | * Test if position `a` is before position `b`. 94 | * If the two positions are equal, the result will be false. 95 | */ 96 | public static isBefore(a: IPosition, b: IPosition): boolean { 97 | if (a.lineNumber < b.lineNumber) { 98 | return true; 99 | } 100 | if (b.lineNumber < a.lineNumber) { 101 | return false; 102 | } 103 | return a.column < b.column; 104 | } 105 | 106 | /** 107 | * Test if this position is before other position. 108 | * If the two positions are equal, the result will be true. 109 | */ 110 | public isBeforeOrEqual(other: IPosition): boolean { 111 | return Position.isBeforeOrEqual(this, other); 112 | } 113 | 114 | /** 115 | * Test if position `a` is before position `b`. 116 | * If the two positions are equal, the result will be true. 117 | */ 118 | public static isBeforeOrEqual(a: IPosition, b: IPosition): boolean { 119 | if (a.lineNumber < b.lineNumber) { 120 | return true; 121 | } 122 | if (b.lineNumber < a.lineNumber) { 123 | return false; 124 | } 125 | return a.column <= b.column; 126 | } 127 | 128 | /** 129 | * A function that compares positions, useful for sorting 130 | */ 131 | public static compare(a: IPosition, b: IPosition): number { 132 | let aLineNumber = a.lineNumber | 0; 133 | let bLineNumber = b.lineNumber | 0; 134 | 135 | if (aLineNumber === bLineNumber) { 136 | let aColumn = a.column | 0; 137 | let bColumn = b.column | 0; 138 | return aColumn - bColumn; 139 | } 140 | 141 | return aLineNumber - bLineNumber; 142 | } 143 | 144 | /** 145 | * Clone this position. 146 | */ 147 | public clone(): Position { 148 | return new Position(this.lineNumber, this.column); 149 | } 150 | 151 | /** 152 | * Convert to a human-readable representation. 153 | */ 154 | public toString(): string { 155 | return '(' + this.lineNumber + ',' + this.column + ')'; 156 | } 157 | 158 | // --- 159 | 160 | /** 161 | * Create a `Position` from an `IPosition`. 162 | */ 163 | public static lift(pos: IPosition): Position { 164 | return new Position(pos.lineNumber, pos.column); 165 | } 166 | 167 | /** 168 | * Test if `obj` is an `IPosition`. 169 | */ 170 | public static isIPosition(obj: any): obj is IPosition { 171 | return ( 172 | obj 173 | && (typeof obj.lineNumber === 'number') 174 | && (typeof obj.column === 'number') 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/common/range.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { IPosition, Position } from './position'; 7 | 8 | /** 9 | * A range in the editor. This interface is suitable for serialization. 10 | */ 11 | export interface IRange { 12 | /** 13 | * Line number on which the range starts (starts at 1). 14 | */ 15 | readonly startLineNumber: number; 16 | /** 17 | * Column on which the range starts in line `startLineNumber` (starts at 1). 18 | */ 19 | readonly startColumn: number; 20 | /** 21 | * Line number on which the range ends. 22 | */ 23 | readonly endLineNumber: number; 24 | /** 25 | * Column on which the range ends in line `endLineNumber`. 26 | */ 27 | readonly endColumn: number; 28 | } 29 | 30 | /** 31 | * A range in the editor. (startLineNumber,startColumn) is <= (endLineNumber,endColumn) 32 | */ 33 | export class Range { 34 | 35 | /** 36 | * Line number on which the range starts (starts at 1). 37 | */ 38 | public readonly startLineNumber: number; 39 | /** 40 | * Column on which the range starts in line `startLineNumber` (starts at 1). 41 | */ 42 | public readonly startColumn: number; 43 | /** 44 | * Line number on which the range ends. 45 | */ 46 | public readonly endLineNumber: number; 47 | /** 48 | * Column on which the range ends in line `endLineNumber`. 49 | */ 50 | public readonly endColumn: number; 51 | 52 | constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) { 53 | if ((startLineNumber > endLineNumber) || (startLineNumber === endLineNumber && startColumn > endColumn)) { 54 | this.startLineNumber = endLineNumber; 55 | this.startColumn = endColumn; 56 | this.endLineNumber = startLineNumber; 57 | this.endColumn = startColumn; 58 | } else { 59 | this.startLineNumber = startLineNumber; 60 | this.startColumn = startColumn; 61 | this.endLineNumber = endLineNumber; 62 | this.endColumn = endColumn; 63 | } 64 | } 65 | 66 | /** 67 | * Test if this range is empty. 68 | */ 69 | public isEmpty(): boolean { 70 | return Range.isEmpty(this); 71 | } 72 | 73 | /** 74 | * Test if `range` is empty. 75 | */ 76 | public static isEmpty(range: IRange): boolean { 77 | return (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn); 78 | } 79 | 80 | /** 81 | * Test if position is in this range. If the position is at the edges, will return true. 82 | */ 83 | public containsPosition(position: IPosition): boolean { 84 | return Range.containsPosition(this, position); 85 | } 86 | 87 | /** 88 | * Test if `position` is in `range`. If the position is at the edges, will return true. 89 | */ 90 | public static containsPosition(range: IRange, position: IPosition): boolean { 91 | if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) { 92 | return false; 93 | } 94 | if (position.lineNumber === range.startLineNumber && position.column < range.startColumn) { 95 | return false; 96 | } 97 | if (position.lineNumber === range.endLineNumber && position.column > range.endColumn) { 98 | return false; 99 | } 100 | return true; 101 | } 102 | 103 | /** 104 | * Test if range is in this range. If the range is equal to this range, will return true. 105 | */ 106 | public containsRange(range: IRange): boolean { 107 | return Range.containsRange(this, range); 108 | } 109 | 110 | /** 111 | * Test if `otherRange` is in `range`. If the ranges are equal, will return true. 112 | */ 113 | public static containsRange(range: IRange, otherRange: IRange): boolean { 114 | if (otherRange.startLineNumber < range.startLineNumber || otherRange.endLineNumber < range.startLineNumber) { 115 | return false; 116 | } 117 | if (otherRange.startLineNumber > range.endLineNumber || otherRange.endLineNumber > range.endLineNumber) { 118 | return false; 119 | } 120 | if (otherRange.startLineNumber === range.startLineNumber && otherRange.startColumn < range.startColumn) { 121 | return false; 122 | } 123 | if (otherRange.endLineNumber === range.endLineNumber && otherRange.endColumn > range.endColumn) { 124 | return false; 125 | } 126 | return true; 127 | } 128 | 129 | /** 130 | * A reunion of the two ranges. 131 | * The smallest position will be used as the start point, and the largest one as the end point. 132 | */ 133 | public plusRange(range: IRange): Range { 134 | return Range.plusRange(this, range); 135 | } 136 | 137 | /** 138 | * A reunion of the two ranges. 139 | * The smallest position will be used as the start point, and the largest one as the end point. 140 | */ 141 | public static plusRange(a: IRange, b: IRange): Range { 142 | let startLineNumber: number; 143 | let startColumn: number; 144 | let endLineNumber: number; 145 | let endColumn: number; 146 | 147 | if (b.startLineNumber < a.startLineNumber) { 148 | startLineNumber = b.startLineNumber; 149 | startColumn = b.startColumn; 150 | } else if (b.startLineNumber === a.startLineNumber) { 151 | startLineNumber = b.startLineNumber; 152 | startColumn = Math.min(b.startColumn, a.startColumn); 153 | } else { 154 | startLineNumber = a.startLineNumber; 155 | startColumn = a.startColumn; 156 | } 157 | 158 | if (b.endLineNumber > a.endLineNumber) { 159 | endLineNumber = b.endLineNumber; 160 | endColumn = b.endColumn; 161 | } else if (b.endLineNumber === a.endLineNumber) { 162 | endLineNumber = b.endLineNumber; 163 | endColumn = Math.max(b.endColumn, a.endColumn); 164 | } else { 165 | endLineNumber = a.endLineNumber; 166 | endColumn = a.endColumn; 167 | } 168 | 169 | return new Range(startLineNumber, startColumn, endLineNumber, endColumn); 170 | } 171 | 172 | /** 173 | * A intersection of the two ranges. 174 | */ 175 | public intersectRanges(range: IRange): Range | null { 176 | return Range.intersectRanges(this, range); 177 | } 178 | 179 | /** 180 | * A intersection of the two ranges. 181 | */ 182 | public static intersectRanges(a: IRange, b: IRange): Range | null { 183 | let resultStartLineNumber = a.startLineNumber; 184 | let resultStartColumn = a.startColumn; 185 | let resultEndLineNumber = a.endLineNumber; 186 | let resultEndColumn = a.endColumn; 187 | let otherStartLineNumber = b.startLineNumber; 188 | let otherStartColumn = b.startColumn; 189 | let otherEndLineNumber = b.endLineNumber; 190 | let otherEndColumn = b.endColumn; 191 | 192 | if (resultStartLineNumber < otherStartLineNumber) { 193 | resultStartLineNumber = otherStartLineNumber; 194 | resultStartColumn = otherStartColumn; 195 | } else if (resultStartLineNumber === otherStartLineNumber) { 196 | resultStartColumn = Math.max(resultStartColumn, otherStartColumn); 197 | } 198 | 199 | if (resultEndLineNumber > otherEndLineNumber) { 200 | resultEndLineNumber = otherEndLineNumber; 201 | resultEndColumn = otherEndColumn; 202 | } else if (resultEndLineNumber === otherEndLineNumber) { 203 | resultEndColumn = Math.min(resultEndColumn, otherEndColumn); 204 | } 205 | 206 | // Check if selection is now empty 207 | if (resultStartLineNumber > resultEndLineNumber) { 208 | return null; 209 | } 210 | if (resultStartLineNumber === resultEndLineNumber && resultStartColumn > resultEndColumn) { 211 | return null; 212 | } 213 | return new Range(resultStartLineNumber, resultStartColumn, resultEndLineNumber, resultEndColumn); 214 | } 215 | 216 | /** 217 | * Test if this range equals other. 218 | */ 219 | public equalsRange(other: IRange | null): boolean { 220 | return Range.equalsRange(this, other); 221 | } 222 | 223 | /** 224 | * Test if range `a` equals `b`. 225 | */ 226 | public static equalsRange(a: IRange | null, b: IRange | null): boolean { 227 | return ( 228 | !!a && 229 | !!b && 230 | a.startLineNumber === b.startLineNumber && 231 | a.startColumn === b.startColumn && 232 | a.endLineNumber === b.endLineNumber && 233 | a.endColumn === b.endColumn 234 | ); 235 | } 236 | 237 | /** 238 | * Return the end position (which will be after or equal to the start position) 239 | */ 240 | public getEndPosition(): Position { 241 | return new Position(this.endLineNumber, this.endColumn); 242 | } 243 | 244 | /** 245 | * Return the start position (which will be before or equal to the end position) 246 | */ 247 | public getStartPosition(): Position { 248 | return new Position(this.startLineNumber, this.startColumn); 249 | } 250 | 251 | /** 252 | * Transform to a user presentable string representation. 253 | */ 254 | public toString(): string { 255 | return '[' + this.startLineNumber + ',' + this.startColumn + ' -> ' + this.endLineNumber + ',' + this.endColumn + ']'; 256 | } 257 | 258 | /** 259 | * Create a new range using this range's start position, and using endLineNumber and endColumn as the end position. 260 | */ 261 | public setEndPosition(endLineNumber: number, endColumn: number): Range { 262 | return new Range(this.startLineNumber, this.startColumn, endLineNumber, endColumn); 263 | } 264 | 265 | /** 266 | * Create a new range using this range's end position, and using startLineNumber and startColumn as the start position. 267 | */ 268 | public setStartPosition(startLineNumber: number, startColumn: number): Range { 269 | return new Range(startLineNumber, startColumn, this.endLineNumber, this.endColumn); 270 | } 271 | 272 | /** 273 | * Create a new empty range using this range's start position. 274 | */ 275 | public collapseToStart(): Range { 276 | return Range.collapseToStart(this); 277 | } 278 | 279 | /** 280 | * Create a new empty range using this range's start position. 281 | */ 282 | public static collapseToStart(range: IRange): Range { 283 | return new Range(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn); 284 | } 285 | 286 | // --- 287 | 288 | public static fromPositions(start: IPosition, end: IPosition = start): Range { 289 | return new Range(start.lineNumber, start.column, end.lineNumber, end.column); 290 | } 291 | 292 | /** 293 | * Create a `Range` from an `IRange`. 294 | */ 295 | public static lift(range: undefined | null): null; 296 | public static lift(range: IRange): Range; 297 | public static lift(range: IRange | undefined | null): Range | null { 298 | if (!range) { 299 | return null; 300 | } 301 | return new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); 302 | } 303 | 304 | /** 305 | * Test if `obj` is an `IRange`. 306 | */ 307 | public static isIRange(obj: any): obj is IRange { 308 | return ( 309 | obj 310 | && (typeof obj.startLineNumber === 'number') 311 | && (typeof obj.startColumn === 'number') 312 | && (typeof obj.endLineNumber === 'number') 313 | && (typeof obj.endColumn === 'number') 314 | ); 315 | } 316 | 317 | /** 318 | * Test if the two ranges are touching in any way. 319 | */ 320 | public static areIntersectingOrTouching(a: IRange, b: IRange): boolean { 321 | // Check if `a` is before `b` 322 | if (a.endLineNumber < b.startLineNumber || (a.endLineNumber === b.startLineNumber && a.endColumn < b.startColumn)) { 323 | return false; 324 | } 325 | 326 | // Check if `b` is before `a` 327 | if (b.endLineNumber < a.startLineNumber || (b.endLineNumber === a.startLineNumber && b.endColumn < a.startColumn)) { 328 | return false; 329 | } 330 | 331 | // These ranges must intersect 332 | return true; 333 | } 334 | 335 | /** 336 | * Test if the two ranges are intersecting. If the ranges are touching it returns true. 337 | */ 338 | public static areIntersecting(a: IRange, b: IRange): boolean { 339 | // Check if `a` is before `b` 340 | if (a.endLineNumber < b.startLineNumber || (a.endLineNumber === b.startLineNumber && a.endColumn <= b.startColumn)) { 341 | return false; 342 | } 343 | 344 | // Check if `b` is before `a` 345 | if (b.endLineNumber < a.startLineNumber || (b.endLineNumber === a.startLineNumber && b.endColumn <= a.startColumn)) { 346 | return false; 347 | } 348 | 349 | // These ranges must intersect 350 | return true; 351 | } 352 | 353 | /** 354 | * A function that compares ranges, useful for sorting ranges 355 | * It will first compare ranges on the startPosition and then on the endPosition 356 | */ 357 | public static compareRangesUsingStarts(a: IRange | null | undefined, b: IRange | null | undefined): number { 358 | if (a && b) { 359 | const aStartLineNumber = a.startLineNumber | 0; 360 | const bStartLineNumber = b.startLineNumber | 0; 361 | 362 | if (aStartLineNumber === bStartLineNumber) { 363 | const aStartColumn = a.startColumn | 0; 364 | const bStartColumn = b.startColumn | 0; 365 | 366 | if (aStartColumn === bStartColumn) { 367 | const aEndLineNumber = a.endLineNumber | 0; 368 | const bEndLineNumber = b.endLineNumber | 0; 369 | 370 | if (aEndLineNumber === bEndLineNumber) { 371 | const aEndColumn = a.endColumn | 0; 372 | const bEndColumn = b.endColumn | 0; 373 | return aEndColumn - bEndColumn; 374 | } 375 | return aEndLineNumber - bEndLineNumber; 376 | } 377 | return aStartColumn - bStartColumn; 378 | } 379 | return aStartLineNumber - bStartLineNumber; 380 | } 381 | const aExists = (a ? 1 : 0); 382 | const bExists = (b ? 1 : 0); 383 | return aExists - bExists; 384 | } 385 | 386 | /** 387 | * A function that compares ranges, useful for sorting ranges 388 | * It will first compare ranges on the endPosition and then on the startPosition 389 | */ 390 | public static compareRangesUsingEnds(a: IRange, b: IRange): number { 391 | if (a.endLineNumber === b.endLineNumber) { 392 | if (a.endColumn === b.endColumn) { 393 | if (a.startLineNumber === b.startLineNumber) { 394 | return a.startColumn - b.startColumn; 395 | } 396 | return a.startLineNumber - b.startLineNumber; 397 | } 398 | return a.endColumn - b.endColumn; 399 | } 400 | return a.endLineNumber - b.endLineNumber; 401 | } 402 | 403 | /** 404 | * Test if the range spans multiple lines. 405 | */ 406 | public static spansMultipleLines(range: IRange): boolean { 407 | return range.endLineNumber > range.startLineNumber; 408 | } 409 | } 410 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './pieceTreeBase'; 2 | export * from './pieceTreeBuilder'; -------------------------------------------------------------------------------- /src/pieceTreeBase.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { CharCode } from './common/charCode'; 7 | import { Position } from './common/position'; 8 | import { Range } from './common/range'; 9 | import { NodeColor, SENTINEL, TreeNode, fixInsert, leftest, rbDelete, righttest, updateTreeMetadata } from './rbTreeBase'; 10 | 11 | export interface ITextSnapshot { 12 | read(): string | null; 13 | } 14 | // const lfRegex = new RegExp(/\r\n|\r|\n/g); 15 | export const AverageBufferSize = 65535; 16 | 17 | export function createUintArray(arr: number[]): Uint32Array | Uint16Array { 18 | let r; 19 | if (arr[arr.length - 1] < 65536) { 20 | r = new Uint16Array(arr.length); 21 | } else { 22 | r = new Uint32Array(arr.length); 23 | } 24 | r.set(arr, 0); 25 | return r; 26 | } 27 | 28 | export class LineStarts { 29 | constructor( 30 | public readonly lineStarts: Uint32Array | Uint16Array | number[], 31 | public readonly cr: number, 32 | public readonly lf: number, 33 | public readonly crlf: number, 34 | public readonly isBasicASCII: boolean 35 | ) { } 36 | } 37 | 38 | export function createLineStartsFast(str: string, readonly: boolean = true): Uint32Array | Uint16Array | number[] { 39 | let r: number[] = [0], rLength = 1; 40 | 41 | for (let i = 0, len = str.length; i < len; i++) { 42 | const chr = str.charCodeAt(i); 43 | 44 | if (chr === CharCode.CarriageReturn) { 45 | if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) { 46 | // \r\n... case 47 | r[rLength++] = i + 2; 48 | i++; // skip \n 49 | } else { 50 | // \r... case 51 | r[rLength++] = i + 1; 52 | } 53 | } else if (chr === CharCode.LineFeed) { 54 | r[rLength++] = i + 1; 55 | } 56 | } 57 | if (readonly) { 58 | return createUintArray(r); 59 | } else { 60 | return r; 61 | } 62 | } 63 | 64 | export function createLineStarts(r: number[], str: string): LineStarts { 65 | r.length = 0; 66 | r[0] = 0; 67 | let rLength = 1; 68 | let cr = 0, lf = 0, crlf = 0; 69 | let isBasicASCII = true; 70 | for (let i = 0, len = str.length; i < len; i++) { 71 | const chr = str.charCodeAt(i); 72 | 73 | if (chr === CharCode.CarriageReturn) { 74 | if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) { 75 | // \r\n... case 76 | crlf++; 77 | r[rLength++] = i + 2; 78 | i++; // skip \n 79 | } else { 80 | cr++; 81 | // \r... case 82 | r[rLength++] = i + 1; 83 | } 84 | } else if (chr === CharCode.LineFeed) { 85 | lf++; 86 | r[rLength++] = i + 1; 87 | } else { 88 | if (isBasicASCII) { 89 | if (chr !== CharCode.Tab && (chr < 32 || chr > 126)) { 90 | isBasicASCII = false; 91 | } 92 | } 93 | } 94 | } 95 | const result = new LineStarts(createUintArray(r), cr, lf, crlf, isBasicASCII); 96 | r.length = 0; 97 | 98 | return result; 99 | } 100 | 101 | export interface NodePosition { 102 | /** 103 | * Piece Index 104 | */ 105 | node: TreeNode; 106 | /** 107 | * remainer in current piece. 108 | */ 109 | remainder: number; 110 | /** 111 | * node start offset in document. 112 | */ 113 | nodeStartOffset: number; 114 | } 115 | 116 | export interface BufferCursor { 117 | /** 118 | * Line number in current buffer 119 | */ 120 | line: number; 121 | /** 122 | * Column number in current buffer 123 | */ 124 | column: number; 125 | } 126 | 127 | export class Piece { 128 | readonly bufferIndex: number; 129 | readonly start: BufferCursor; 130 | readonly end: BufferCursor; 131 | readonly length: number; 132 | readonly lineFeedCnt: number; 133 | 134 | constructor(bufferIndex: number, start: BufferCursor, end: BufferCursor, lineFeedCnt: number, length: number) { 135 | this.bufferIndex = bufferIndex; 136 | this.start = start; 137 | this.end = end; 138 | this.lineFeedCnt = lineFeedCnt; 139 | this.length = length; 140 | } 141 | } 142 | 143 | export class StringBuffer { 144 | buffer: string; 145 | lineStarts: Uint32Array | Uint16Array | number[]; 146 | 147 | constructor(buffer: string, lineStarts: Uint32Array | Uint16Array | number[]) { 148 | this.buffer = buffer; 149 | this.lineStarts = lineStarts; 150 | } 151 | } 152 | 153 | /** 154 | * Readonly snapshot for piece tree. 155 | * In a real multiple thread environment, to make snapshot reading always work correctly, we need to 156 | * 1. Make TreeNode.piece immutable, then reading and writing can run in parallel. 157 | * 2. TreeNode/Buffers normalization should not happen during snapshot reading. 158 | */ 159 | class PieceTreeSnapshot implements ITextSnapshot { 160 | private readonly _pieces: Piece[]; 161 | private _index: number; 162 | private readonly _tree: PieceTreeBase; 163 | private readonly _BOM: string; 164 | 165 | constructor(tree: PieceTreeBase, BOM: string) { 166 | this._pieces = []; 167 | this._tree = tree; 168 | this._BOM = BOM; 169 | this._index = 0; 170 | if (tree.root !== SENTINEL) { 171 | tree.iterate(tree.root, node => { 172 | if (node !== SENTINEL) { 173 | this._pieces.push(node.piece); 174 | } 175 | return true; 176 | }); 177 | } 178 | } 179 | 180 | read(): string | null { 181 | if (this._pieces.length === 0) { 182 | if (this._index === 0) { 183 | this._index++; 184 | return this._BOM; 185 | } else { 186 | return null; 187 | } 188 | } 189 | 190 | if (this._index > this._pieces.length - 1) { 191 | return null; 192 | } 193 | 194 | if (this._index === 0) { 195 | return this._BOM + this._tree.getPieceContent(this._pieces[this._index++]); 196 | } 197 | return this._tree.getPieceContent(this._pieces[this._index++]); 198 | } 199 | } 200 | 201 | interface CacheEntry { 202 | node: TreeNode; 203 | nodeStartOffset: number; 204 | nodeStartLineNumber?: number; 205 | } 206 | 207 | class PieceTreeSearchCache { 208 | private readonly _limit: number; 209 | private _cache: CacheEntry[]; 210 | 211 | constructor(limit: number) { 212 | this._limit = limit; 213 | this._cache = []; 214 | } 215 | 216 | public get(offset: number): CacheEntry | null { 217 | for (let i = this._cache.length - 1; i >= 0; i--) { 218 | let nodePos = this._cache[i]; 219 | if (nodePos.nodeStartOffset <= offset && nodePos.nodeStartOffset + nodePos.node.piece.length >= offset) { 220 | return nodePos; 221 | } 222 | } 223 | return null; 224 | } 225 | 226 | public get2(lineNumber: number): { node: TreeNode, nodeStartOffset: number, nodeStartLineNumber: number } | null { 227 | for (let i = this._cache.length - 1; i >= 0; i--) { 228 | let nodePos = this._cache[i]; 229 | if (nodePos.nodeStartLineNumber && nodePos.nodeStartLineNumber < lineNumber && nodePos.nodeStartLineNumber + nodePos.node.piece.lineFeedCnt >= lineNumber) { 230 | return <{ node: TreeNode, nodeStartOffset: number, nodeStartLineNumber: number }>nodePos; 231 | } 232 | } 233 | return null; 234 | } 235 | 236 | public set(nodePosition: CacheEntry) { 237 | if (this._cache.length >= this._limit) { 238 | this._cache.shift(); 239 | } 240 | this._cache.push(nodePosition); 241 | } 242 | 243 | public valdiate(offset: number) { 244 | let hasInvalidVal = false; 245 | let tmp: Array = this._cache; 246 | for (let i = 0; i < tmp.length; i++) { 247 | let nodePos = tmp[i]!; 248 | if (nodePos.node.parent === null || nodePos.nodeStartOffset >= offset) { 249 | tmp[i] = null; 250 | hasInvalidVal = true; 251 | continue; 252 | } 253 | } 254 | 255 | if (hasInvalidVal) { 256 | let newArr: CacheEntry[] = []; 257 | for (const entry of tmp) { 258 | if (entry !== null) { 259 | newArr.push(entry); 260 | } 261 | } 262 | 263 | this._cache = newArr; 264 | } 265 | } 266 | } 267 | 268 | export class PieceTreeBase { 269 | root: TreeNode; 270 | protected _buffers: StringBuffer[]; // 0 is change buffer, others are readonly original buffer. 271 | protected _lineCnt: number; 272 | protected _length: number; 273 | protected _EOL: string; 274 | protected _EOLLength: number; 275 | protected _EOLNormalized: boolean; 276 | private _lastChangeBufferPos: BufferCursor; 277 | private _searchCache: PieceTreeSearchCache; 278 | private _lastVisitedLine: { lineNumber: number; value: string; }; 279 | 280 | constructor(chunks: StringBuffer[], eol: '\r\n' | '\n', eolNormalized: boolean) { 281 | this.create(chunks, eol, eolNormalized); 282 | } 283 | 284 | create(chunks: StringBuffer[], eol: '\r\n' | '\n', eolNormalized: boolean) { 285 | this._buffers = [ 286 | new StringBuffer('', [0]) 287 | ]; 288 | this._lastChangeBufferPos = { line: 0, column: 0 }; 289 | this.root = SENTINEL; 290 | this._lineCnt = 1; 291 | this._length = 0; 292 | this._EOL = eol; 293 | this._EOLLength = eol.length; 294 | this._EOLNormalized = eolNormalized; 295 | 296 | let lastNode: TreeNode | null = null; 297 | for (let i = 0, len = chunks.length; i < len; i++) { 298 | if (chunks[i].buffer.length > 0) { 299 | if (!chunks[i].lineStarts) { 300 | chunks[i].lineStarts = createLineStartsFast(chunks[i].buffer); 301 | } 302 | 303 | let piece = new Piece( 304 | i + 1, 305 | { line: 0, column: 0 }, 306 | { line: chunks[i].lineStarts.length - 1, column: chunks[i].buffer.length - chunks[i].lineStarts[chunks[i].lineStarts.length - 1] }, 307 | chunks[i].lineStarts.length - 1, 308 | chunks[i].buffer.length 309 | ); 310 | this._buffers.push(chunks[i]); 311 | lastNode = this.rbInsertRight(lastNode, piece); 312 | } 313 | } 314 | 315 | this._searchCache = new PieceTreeSearchCache(1); 316 | this._lastVisitedLine = { lineNumber: 0, value: '' }; 317 | this.computeBufferMetadata(); 318 | } 319 | 320 | normalizeEOL(eol: '\r\n' | '\n') { 321 | let averageBufferSize = AverageBufferSize; 322 | let min = averageBufferSize - Math.floor(averageBufferSize / 3); 323 | let max = min * 2; 324 | 325 | let tempChunk = ''; 326 | let tempChunkLen = 0; 327 | let chunks: StringBuffer[] = []; 328 | 329 | this.iterate(this.root, node => { 330 | let str = this.getNodeContent(node); 331 | let len = str.length; 332 | if (tempChunkLen <= min || tempChunkLen + len < max) { 333 | tempChunk += str; 334 | tempChunkLen += len; 335 | return true; 336 | } 337 | 338 | // flush anyways 339 | let text = tempChunk.replace(/\r\n|\r|\n/g, eol); 340 | chunks.push(new StringBuffer(text, createLineStartsFast(text))); 341 | tempChunk = str; 342 | tempChunkLen = len; 343 | return true; 344 | }); 345 | 346 | if (tempChunkLen > 0) { 347 | let text = tempChunk.replace(/\r\n|\r|\n/g, eol); 348 | chunks.push(new StringBuffer(text, createLineStartsFast(text))); 349 | } 350 | 351 | this.create(chunks, eol, true); 352 | } 353 | 354 | // #region Buffer API 355 | public getEOL(): string { 356 | return this._EOL; 357 | } 358 | 359 | public setEOL(newEOL: '\r\n' | '\n'): void { 360 | this._EOL = newEOL; 361 | this._EOLLength = this._EOL.length; 362 | this.normalizeEOL(newEOL); 363 | } 364 | 365 | public createSnapshot(BOM: string): ITextSnapshot { 366 | return new PieceTreeSnapshot(this, BOM); 367 | } 368 | 369 | public equal(other: PieceTreeBase): boolean { 370 | if (this.getLength() !== other.getLength()) { 371 | return false; 372 | } 373 | if (this.getLineCount() !== other.getLineCount()) { 374 | return false; 375 | } 376 | 377 | let offset = 0; 378 | let ret = this.iterate(this.root, node => { 379 | if (node === SENTINEL) { 380 | return true; 381 | } 382 | let str = this.getNodeContent(node); 383 | let len = str.length; 384 | let startPosition = other.nodeAt(offset); 385 | let endPosition = other.nodeAt(offset + len); 386 | let val = other.getValueInRange2(startPosition, endPosition); 387 | 388 | return str === val; 389 | }); 390 | 391 | return ret; 392 | } 393 | 394 | public getOffsetAt(lineNumber: number, column: number): number { 395 | let leftLen = 0; // inorder 396 | 397 | let x = this.root; 398 | 399 | while (x !== SENTINEL) { 400 | if (x.left !== SENTINEL && x.lf_left + 1 >= lineNumber) { 401 | x = x.left; 402 | } else if (x.lf_left + x.piece.lineFeedCnt + 1 >= lineNumber) { 403 | leftLen += x.size_left; 404 | // lineNumber >= 2 405 | let accumualtedValInCurrentIndex = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); 406 | return leftLen += accumualtedValInCurrentIndex + column - 1; 407 | } else { 408 | lineNumber -= x.lf_left + x.piece.lineFeedCnt; 409 | leftLen += x.size_left + x.piece.length; 410 | x = x.right; 411 | } 412 | } 413 | 414 | return leftLen; 415 | } 416 | 417 | public getPositionAt(offset: number): Position { 418 | offset = Math.floor(offset); 419 | offset = Math.max(0, offset); 420 | 421 | let x = this.root; 422 | let lfCnt = 0; 423 | let originalOffset = offset; 424 | 425 | while (x !== SENTINEL) { 426 | if (x.size_left !== 0 && x.size_left >= offset) { 427 | x = x.left; 428 | } else if (x.size_left + x.piece.length >= offset) { 429 | let out = this.getIndexOf(x, offset - x.size_left); 430 | 431 | lfCnt += x.lf_left + out.index; 432 | 433 | if (out.index === 0) { 434 | let lineStartOffset = this.getOffsetAt(lfCnt + 1, 1); 435 | let column = originalOffset - lineStartOffset; 436 | return new Position(lfCnt + 1, column + 1); 437 | } 438 | 439 | return new Position(lfCnt + 1, out.remainder + 1); 440 | } else { 441 | offset -= x.size_left + x.piece.length; 442 | lfCnt += x.lf_left + x.piece.lineFeedCnt; 443 | 444 | if (x.right === SENTINEL) { 445 | // last node 446 | let lineStartOffset = this.getOffsetAt(lfCnt + 1, 1); 447 | let column = originalOffset - offset - lineStartOffset; 448 | return new Position(lfCnt + 1, column + 1); 449 | } else { 450 | x = x.right; 451 | } 452 | } 453 | } 454 | 455 | return new Position(1, 1); 456 | } 457 | 458 | public getValueInRange(range: Range, eol?: string): string { 459 | if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) { 460 | return ''; 461 | } 462 | 463 | let startPosition = this.nodeAt2(range.startLineNumber, range.startColumn); 464 | let endPosition = this.nodeAt2(range.endLineNumber, range.endColumn); 465 | 466 | let value = this.getValueInRange2(startPosition, endPosition); 467 | if (eol) { 468 | if (eol !== this._EOL || !this._EOLNormalized) { 469 | return value.replace(/\r\n|\r|\n/g, eol); 470 | } 471 | 472 | if (eol === this.getEOL() && this._EOLNormalized) { 473 | if (eol === '\r\n') { 474 | 475 | } 476 | return value; 477 | } 478 | return value.replace(/\r\n|\r|\n/g, eol); 479 | } 480 | return value; 481 | } 482 | 483 | public getValueInRange2(startPosition: NodePosition, endPosition: NodePosition): string { 484 | if (startPosition.node === endPosition.node) { 485 | let node = startPosition.node; 486 | let buffer = this._buffers[node.piece.bufferIndex].buffer; 487 | let startOffset = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start); 488 | return buffer.substring(startOffset + startPosition.remainder, startOffset + endPosition.remainder); 489 | } 490 | 491 | let x = startPosition.node; 492 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 493 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 494 | let ret = buffer.substring(startOffset + startPosition.remainder, startOffset + x.piece.length); 495 | 496 | x = x.next(); 497 | while (x !== SENTINEL) { 498 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 499 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 500 | 501 | if (x === endPosition.node) { 502 | ret += buffer.substring(startOffset, startOffset + endPosition.remainder); 503 | break; 504 | } else { 505 | ret += buffer.substr(startOffset, x.piece.length); 506 | } 507 | 508 | x = x.next(); 509 | } 510 | 511 | return ret; 512 | } 513 | 514 | public getLinesContent(): string[] { 515 | return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/); 516 | } 517 | 518 | public getLength(): number { 519 | return this._length; 520 | } 521 | 522 | public getLineCount(): number { 523 | return this._lineCnt; 524 | } 525 | 526 | /** 527 | * @param lineNumber 1 based 528 | */ 529 | public getLineContent(lineNumber: number): string { 530 | if (this._lastVisitedLine.lineNumber === lineNumber) { 531 | return this._lastVisitedLine.value; 532 | } 533 | 534 | this._lastVisitedLine.lineNumber = lineNumber; 535 | 536 | if (lineNumber === this._lineCnt) { 537 | this._lastVisitedLine.value = this.getLineRawContent(lineNumber); 538 | } else if (this._EOLNormalized) { 539 | this._lastVisitedLine.value = this.getLineRawContent(lineNumber, this._EOLLength); 540 | } else { 541 | this._lastVisitedLine.value = this.getLineRawContent(lineNumber).replace(/(\r\n|\r|\n)$/, ''); 542 | } 543 | 544 | return this._lastVisitedLine.value; 545 | } 546 | 547 | public getLineCharCode(lineNumber: number, index: number): number { 548 | let nodePos = this.nodeAt2(lineNumber, index + 1); 549 | if (nodePos.remainder === nodePos.node.piece.length) { 550 | // the char we want to fetch is at the head of next node. 551 | let matchingNode = nodePos.node.next(); 552 | if (!matchingNode) { 553 | return 0; 554 | } 555 | 556 | let buffer = this._buffers[matchingNode.piece.bufferIndex]; 557 | let startOffset = this.offsetInBuffer(matchingNode.piece.bufferIndex, matchingNode.piece.start); 558 | return buffer.buffer.charCodeAt(startOffset); 559 | } else { 560 | let buffer = this._buffers[nodePos.node.piece.bufferIndex]; 561 | let startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start); 562 | let targetOffset = startOffset + nodePos.remainder; 563 | 564 | return buffer.buffer.charCodeAt(targetOffset); 565 | } 566 | } 567 | 568 | public getLineLength(lineNumber: number): number { 569 | if (lineNumber === this.getLineCount()) { 570 | let startOffset = this.getOffsetAt(lineNumber, 1); 571 | return this.getLength() - startOffset; 572 | } 573 | return this.getOffsetAt(lineNumber + 1, 1) - this.getOffsetAt(lineNumber, 1) - this._EOLLength; 574 | } 575 | 576 | // #endregion 577 | 578 | // #region Piece Table 579 | insert(offset: number, value: string, eolNormalized: boolean = false): void { 580 | this._EOLNormalized = this._EOLNormalized && eolNormalized; 581 | this._lastVisitedLine.lineNumber = 0; 582 | this._lastVisitedLine.value = ''; 583 | 584 | if (this.root !== SENTINEL) { 585 | let { node, remainder, nodeStartOffset } = this.nodeAt(offset); 586 | let piece = node.piece; 587 | let bufferIndex = piece.bufferIndex; 588 | let insertPosInBuffer = this.positionInBuffer(node, remainder); 589 | if (node.piece.bufferIndex === 0 && 590 | piece.end.line === this._lastChangeBufferPos.line && 591 | piece.end.column === this._lastChangeBufferPos.column && 592 | (nodeStartOffset + piece.length === offset) && 593 | value.length < AverageBufferSize 594 | ) { 595 | // changed buffer 596 | this.appendToNode(node, value); 597 | this.computeBufferMetadata(); 598 | return; 599 | } 600 | 601 | if (nodeStartOffset === offset) { 602 | this.insertContentToNodeLeft(value, node); 603 | this._searchCache.valdiate(offset); 604 | } else if (nodeStartOffset + node.piece.length > offset) { 605 | // we are inserting into the middle of a node. 606 | let nodesToDel: TreeNode[] = []; 607 | let newRightPiece = new Piece( 608 | piece.bufferIndex, 609 | insertPosInBuffer, 610 | piece.end, 611 | this.getLineFeedCnt(piece.bufferIndex, insertPosInBuffer, piece.end), 612 | this.offsetInBuffer(bufferIndex, piece.end) - this.offsetInBuffer(bufferIndex, insertPosInBuffer) 613 | ); 614 | 615 | if (this.shouldCheckCRLF() && this.endWithCR(value)) { 616 | let headOfRight = this.nodeCharCodeAt(node, remainder); 617 | 618 | if (headOfRight === 10 /** \n */) { 619 | let newStart: BufferCursor = { line: newRightPiece.start.line + 1, column: 0 }; 620 | newRightPiece = new Piece( 621 | newRightPiece.bufferIndex, 622 | newStart, 623 | newRightPiece.end, 624 | this.getLineFeedCnt(newRightPiece.bufferIndex, newStart, newRightPiece.end), 625 | newRightPiece.length - 1 626 | ); 627 | 628 | value += '\n'; 629 | } 630 | } 631 | 632 | // reuse node for content before insertion point. 633 | if (this.shouldCheckCRLF() && this.startWithLF(value)) { 634 | let tailOfLeft = this.nodeCharCodeAt(node, remainder - 1); 635 | if (tailOfLeft === 13 /** \r */) { 636 | let previousPos = this.positionInBuffer(node, remainder - 1); 637 | this.deleteNodeTail(node, previousPos); 638 | value = '\r' + value; 639 | 640 | if (node.piece.length === 0) { 641 | nodesToDel.push(node); 642 | } 643 | } else { 644 | this.deleteNodeTail(node, insertPosInBuffer); 645 | } 646 | } else { 647 | this.deleteNodeTail(node, insertPosInBuffer); 648 | } 649 | 650 | let newPieces = this.createNewPieces(value); 651 | if (newRightPiece.length > 0) { 652 | this.rbInsertRight(node, newRightPiece); 653 | } 654 | 655 | let tmpNode = node; 656 | for (let k = 0; k < newPieces.length; k++) { 657 | tmpNode = this.rbInsertRight(tmpNode, newPieces[k]); 658 | } 659 | this.deleteNodes(nodesToDel); 660 | } else { 661 | this.insertContentToNodeRight(value, node); 662 | } 663 | } else { 664 | // insert new node 665 | let pieces = this.createNewPieces(value); 666 | let node = this.rbInsertLeft(null, pieces[0]); 667 | 668 | for (let k = 1; k < pieces.length; k++) { 669 | node = this.rbInsertRight(node, pieces[k]); 670 | } 671 | } 672 | 673 | // todo, this is too brutal. Total line feed count should be updated the same way as lf_left. 674 | this.computeBufferMetadata(); 675 | } 676 | 677 | delete(offset: number, cnt: number): void { 678 | this._lastVisitedLine.lineNumber = 0; 679 | this._lastVisitedLine.value = ''; 680 | 681 | if (cnt <= 0 || this.root === SENTINEL) { 682 | return; 683 | } 684 | 685 | let startPosition = this.nodeAt(offset); 686 | let endPosition = this.nodeAt(offset + cnt); 687 | let startNode = startPosition.node; 688 | let endNode = endPosition.node; 689 | 690 | if (startNode === endNode) { 691 | let startSplitPosInBuffer = this.positionInBuffer(startNode, startPosition.remainder); 692 | let endSplitPosInBuffer = this.positionInBuffer(startNode, endPosition.remainder); 693 | 694 | if (startPosition.nodeStartOffset === offset) { 695 | if (cnt === startNode.piece.length) { // delete node 696 | let next = startNode.next(); 697 | rbDelete(this, startNode); 698 | this.validateCRLFWithPrevNode(next); 699 | this.computeBufferMetadata(); 700 | return; 701 | } 702 | this.deleteNodeHead(startNode, endSplitPosInBuffer); 703 | this._searchCache.valdiate(offset); 704 | this.validateCRLFWithPrevNode(startNode); 705 | this.computeBufferMetadata(); 706 | return; 707 | } 708 | 709 | if (startPosition.nodeStartOffset + startNode.piece.length === offset + cnt) { 710 | this.deleteNodeTail(startNode, startSplitPosInBuffer); 711 | this.validateCRLFWithNextNode(startNode); 712 | this.computeBufferMetadata(); 713 | return; 714 | } 715 | 716 | // delete content in the middle, this node will be splitted to nodes 717 | this.shrinkNode(startNode, startSplitPosInBuffer, endSplitPosInBuffer); 718 | this.computeBufferMetadata(); 719 | return; 720 | } 721 | 722 | let nodesToDel: TreeNode[] = []; 723 | 724 | let startSplitPosInBuffer = this.positionInBuffer(startNode, startPosition.remainder); 725 | this.deleteNodeTail(startNode, startSplitPosInBuffer); 726 | this._searchCache.valdiate(offset); 727 | if (startNode.piece.length === 0) { 728 | nodesToDel.push(startNode); 729 | } 730 | 731 | // update last touched node 732 | let endSplitPosInBuffer = this.positionInBuffer(endNode, endPosition.remainder); 733 | this.deleteNodeHead(endNode, endSplitPosInBuffer); 734 | if (endNode.piece.length === 0) { 735 | nodesToDel.push(endNode); 736 | } 737 | 738 | // delete nodes in between 739 | let secondNode = startNode.next(); 740 | for (let node = secondNode; node !== SENTINEL && node !== endNode; node = node.next()) { 741 | nodesToDel.push(node); 742 | } 743 | 744 | let prev = startNode.piece.length === 0 ? startNode.prev() : startNode; 745 | this.deleteNodes(nodesToDel); 746 | this.validateCRLFWithNextNode(prev); 747 | this.computeBufferMetadata(); 748 | } 749 | 750 | insertContentToNodeLeft(value: string, node: TreeNode) { 751 | // we are inserting content to the beginning of node 752 | let nodesToDel: TreeNode[] = []; 753 | if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) { 754 | // move `\n` to new node. 755 | 756 | let piece = node.piece; 757 | let newStart: BufferCursor = { line: piece.start.line + 1, column: 0 }; 758 | let nPiece = new Piece( 759 | piece.bufferIndex, 760 | newStart, 761 | piece.end, 762 | this.getLineFeedCnt(piece.bufferIndex, newStart, piece.end), 763 | piece.length - 1 764 | ); 765 | 766 | node.piece = nPiece; 767 | 768 | value += '\n'; 769 | updateTreeMetadata(this, node, -1, -1); 770 | 771 | if (node.piece.length === 0) { 772 | nodesToDel.push(node); 773 | } 774 | } 775 | 776 | let newPieces = this.createNewPieces(value); 777 | let newNode = this.rbInsertLeft(node, newPieces[newPieces.length - 1]); 778 | for (let k = newPieces.length - 2; k >= 0; k--) { 779 | newNode = this.rbInsertLeft(newNode, newPieces[k]); 780 | } 781 | this.validateCRLFWithPrevNode(newNode); 782 | this.deleteNodes(nodesToDel); 783 | } 784 | 785 | insertContentToNodeRight(value: string, node: TreeNode) { 786 | // we are inserting to the right of this node. 787 | if (this.adjustCarriageReturnFromNext(value, node)) { 788 | // move \n to the new node. 789 | value += '\n'; 790 | } 791 | 792 | let newPieces = this.createNewPieces(value); 793 | let newNode = this.rbInsertRight(node, newPieces[0]); 794 | let tmpNode = newNode; 795 | 796 | for (let k = 1; k < newPieces.length; k++) { 797 | tmpNode = this.rbInsertRight(tmpNode, newPieces[k]); 798 | } 799 | 800 | this.validateCRLFWithPrevNode(newNode); 801 | } 802 | 803 | positionInBuffer(node: TreeNode, remainder: number): BufferCursor; 804 | positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null; 805 | positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null { 806 | let piece = node.piece; 807 | let bufferIndex = node.piece.bufferIndex; 808 | let lineStarts = this._buffers[bufferIndex].lineStarts; 809 | 810 | let startOffset = lineStarts[piece.start.line] + piece.start.column; 811 | 812 | let offset = startOffset + remainder; 813 | 814 | // binary search offset between startOffset and endOffset 815 | let low = piece.start.line; 816 | let high = piece.end.line; 817 | 818 | let mid: number = 0; 819 | let midStop: number = 0; 820 | let midStart: number = 0; 821 | 822 | while (low <= high) { 823 | mid = low + ((high - low) / 2) | 0; 824 | midStart = lineStarts[mid]; 825 | 826 | if (mid === high) { 827 | break; 828 | } 829 | 830 | midStop = lineStarts[mid + 1]; 831 | 832 | if (offset < midStart) { 833 | high = mid - 1; 834 | } else if (offset >= midStop) { 835 | low = mid + 1; 836 | } else { 837 | break; 838 | } 839 | } 840 | 841 | if (ret) { 842 | ret.line = mid; 843 | ret.column = offset - midStart; 844 | return null; 845 | } 846 | 847 | return { 848 | line: mid, 849 | column: offset - midStart 850 | }; 851 | } 852 | 853 | getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number { 854 | // we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start. 855 | // now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1 856 | if (end.column === 0) { 857 | return end.line - start.line; 858 | } 859 | 860 | let lineStarts = this._buffers[bufferIndex].lineStarts; 861 | if (end.line === lineStarts.length - 1) { // it means, there is no \n after end, otherwise, there will be one more lineStart. 862 | return end.line - start.line; 863 | } 864 | 865 | let nextLineStartOffset = lineStarts[end.line + 1]; 866 | let endOffset = lineStarts[end.line] + end.column; 867 | if (nextLineStartOffset > endOffset + 1) { // there are more than 1 character after end, which means it can't be \n 868 | return end.line - start.line; 869 | } 870 | // endOffset + 1 === nextLineStartOffset 871 | // character at endOffset is \n, so we check the character before first 872 | // if character at endOffset is \r, end.column is 0 and we can't get here. 873 | let previousCharOffset = endOffset - 1; // end.column > 0 so it's okay. 874 | let buffer = this._buffers[bufferIndex].buffer; 875 | 876 | if (buffer.charCodeAt(previousCharOffset) === 13) { 877 | return end.line - start.line + 1; 878 | } else { 879 | return end.line - start.line; 880 | } 881 | } 882 | 883 | offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number { 884 | let lineStarts = this._buffers[bufferIndex].lineStarts; 885 | return lineStarts[cursor.line] + cursor.column; 886 | } 887 | 888 | deleteNodes(nodes: TreeNode[]): void { 889 | for (let i = 0; i < nodes.length; i++) { 890 | rbDelete(this, nodes[i]); 891 | } 892 | } 893 | 894 | createNewPieces(text: string): Piece[] { 895 | if (text.length > AverageBufferSize) { 896 | // the content is large, operations like substring, charCode becomes slow 897 | // so here we split it into smaller chunks, just like what we did for CR/LF normalization 898 | let newPieces: Piece[] = []; 899 | while (text.length > AverageBufferSize) { 900 | const lastChar = text.charCodeAt(AverageBufferSize - 1); 901 | let splitText; 902 | if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xD800 && lastChar <= 0xDBFF)) { 903 | // last character is \r or a high surrogate => keep it back 904 | splitText = text.substring(0, AverageBufferSize - 1); 905 | text = text.substring(AverageBufferSize - 1); 906 | } else { 907 | splitText = text.substring(0, AverageBufferSize); 908 | text = text.substring(AverageBufferSize); 909 | } 910 | 911 | let lineStarts = createLineStartsFast(splitText); 912 | newPieces.push(new Piece( 913 | this._buffers.length, /* buffer index */ 914 | { line: 0, column: 0 }, 915 | { line: lineStarts.length - 1, column: splitText.length - lineStarts[lineStarts.length - 1] }, 916 | lineStarts.length - 1, 917 | splitText.length 918 | )); 919 | this._buffers.push(new StringBuffer(splitText, lineStarts)); 920 | } 921 | 922 | let lineStarts = createLineStartsFast(text); 923 | newPieces.push(new Piece( 924 | this._buffers.length, /* buffer index */ 925 | { line: 0, column: 0 }, 926 | { line: lineStarts.length - 1, column: text.length - lineStarts[lineStarts.length - 1] }, 927 | lineStarts.length - 1, 928 | text.length 929 | )); 930 | this._buffers.push(new StringBuffer(text, lineStarts)); 931 | 932 | return newPieces; 933 | } 934 | 935 | let startOffset = this._buffers[0].buffer.length; 936 | const lineStarts = createLineStartsFast(text, false); 937 | 938 | let start = this._lastChangeBufferPos; 939 | if (this._buffers[0].lineStarts[this._buffers[0].lineStarts.length - 1] === startOffset 940 | && startOffset !== 0 941 | && this.startWithLF(text) 942 | && this.endWithCR(this._buffers[0].buffer) // todo, we can check this._lastChangeBufferPos's column as it's the last one 943 | ) { 944 | this._lastChangeBufferPos = { line: this._lastChangeBufferPos.line, column: this._lastChangeBufferPos.column + 1 }; 945 | start = this._lastChangeBufferPos; 946 | 947 | for (let i = 0; i < lineStarts.length; i++) { 948 | lineStarts[i] += startOffset + 1; 949 | } 950 | 951 | this._buffers[0].lineStarts = (this._buffers[0].lineStarts).concat(lineStarts.slice(1)); 952 | this._buffers[0].buffer += '_' + text; 953 | startOffset += 1; 954 | } else { 955 | if (startOffset !== 0) { 956 | for (let i = 0; i < lineStarts.length; i++) { 957 | lineStarts[i] += startOffset; 958 | } 959 | } 960 | this._buffers[0].lineStarts = (this._buffers[0].lineStarts).concat(lineStarts.slice(1)); 961 | this._buffers[0].buffer += text; 962 | } 963 | 964 | const endOffset = this._buffers[0].buffer.length; 965 | let endIndex = this._buffers[0].lineStarts.length - 1; 966 | let endColumn = endOffset - this._buffers[0].lineStarts[endIndex]; 967 | let endPos = { line: endIndex, column: endColumn }; 968 | let newPiece = new Piece( 969 | 0, /** todo@peng */ 970 | start, 971 | endPos, 972 | this.getLineFeedCnt(0, start, endPos), 973 | endOffset - startOffset 974 | ); 975 | this._lastChangeBufferPos = endPos; 976 | return [newPiece]; 977 | } 978 | 979 | getLinesRawContent(): string { 980 | return this.getContentOfSubTree(this.root); 981 | } 982 | 983 | getLineRawContent(lineNumber: number, endOffset: number = 0): string { 984 | let x = this.root; 985 | 986 | let ret = ''; 987 | let cache = this._searchCache.get2(lineNumber); 988 | if (cache) { 989 | x = cache.node; 990 | let prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - cache.nodeStartLineNumber - 1); 991 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 992 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 993 | if (cache.nodeStartLineNumber + x.piece.lineFeedCnt === lineNumber) { 994 | ret = buffer.substring(startOffset + prevAccumualtedValue, startOffset + x.piece.length); 995 | } else { 996 | let accumualtedValue = this.getAccumulatedValue(x, lineNumber - cache.nodeStartLineNumber); 997 | return buffer.substring(startOffset + prevAccumualtedValue, startOffset + accumualtedValue - endOffset); 998 | } 999 | } else { 1000 | let nodeStartOffset = 0; 1001 | const originalLineNumber = lineNumber; 1002 | while (x !== SENTINEL) { 1003 | if (x.left !== SENTINEL && x.lf_left >= lineNumber - 1) { 1004 | x = x.left; 1005 | } else if (x.lf_left + x.piece.lineFeedCnt > lineNumber - 1) { 1006 | let prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); 1007 | let accumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 1); 1008 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 1009 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 1010 | nodeStartOffset += x.size_left; 1011 | this._searchCache.set({ 1012 | node: x, 1013 | nodeStartOffset, 1014 | nodeStartLineNumber: originalLineNumber - (lineNumber - 1 - x.lf_left) 1015 | }); 1016 | 1017 | return buffer.substring(startOffset + prevAccumualtedValue, startOffset + accumualtedValue - endOffset); 1018 | } else if (x.lf_left + x.piece.lineFeedCnt === lineNumber - 1) { 1019 | let prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); 1020 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 1021 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 1022 | 1023 | ret = buffer.substring(startOffset + prevAccumualtedValue, startOffset + x.piece.length); 1024 | break; 1025 | } else { 1026 | lineNumber -= x.lf_left + x.piece.lineFeedCnt; 1027 | nodeStartOffset += x.size_left + x.piece.length; 1028 | x = x.right; 1029 | } 1030 | } 1031 | } 1032 | 1033 | // search in order, to find the node contains end column 1034 | x = x.next(); 1035 | while (x !== SENTINEL) { 1036 | let buffer = this._buffers[x.piece.bufferIndex].buffer; 1037 | 1038 | if (x.piece.lineFeedCnt > 0) { 1039 | let accumualtedValue = this.getAccumulatedValue(x, 0); 1040 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 1041 | 1042 | ret += buffer.substring(startOffset, startOffset + accumualtedValue - endOffset); 1043 | return ret; 1044 | } else { 1045 | let startOffset = this.offsetInBuffer(x.piece.bufferIndex, x.piece.start); 1046 | ret += buffer.substr(startOffset, x.piece.length); 1047 | } 1048 | 1049 | x = x.next(); 1050 | } 1051 | 1052 | return ret; 1053 | } 1054 | 1055 | computeBufferMetadata() { 1056 | let x = this.root; 1057 | 1058 | let lfCnt = 1; 1059 | let len = 0; 1060 | 1061 | while (x !== SENTINEL) { 1062 | lfCnt += x.lf_left + x.piece.lineFeedCnt; 1063 | len += x.size_left + x.piece.length; 1064 | x = x.right; 1065 | } 1066 | 1067 | this._lineCnt = lfCnt; 1068 | this._length = len; 1069 | this._searchCache.valdiate(this._length); 1070 | } 1071 | 1072 | // #region node operations 1073 | getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } { 1074 | let piece = node.piece; 1075 | let pos = this.positionInBuffer(node, accumulatedValue); 1076 | let lineCnt = pos.line - piece.start.line; 1077 | 1078 | if (this.offsetInBuffer(piece.bufferIndex, piece.end) - this.offsetInBuffer(piece.bufferIndex, piece.start) === accumulatedValue) { 1079 | // we are checking the end of this node, so a CRLF check is necessary. 1080 | let realLineCnt = this.getLineFeedCnt(node.piece.bufferIndex, piece.start, pos); 1081 | if (realLineCnt !== lineCnt) { 1082 | // aha yes, CRLF 1083 | return { index: realLineCnt, remainder: 0 }; 1084 | } 1085 | } 1086 | 1087 | return { index: lineCnt, remainder: pos.column }; 1088 | } 1089 | 1090 | getAccumulatedValue(node: TreeNode, index: number) { 1091 | if (index < 0) { 1092 | return 0; 1093 | } 1094 | let piece = node.piece; 1095 | let lineStarts = this._buffers[piece.bufferIndex].lineStarts; 1096 | let expectedLineStartIndex = piece.start.line + index + 1; 1097 | if (expectedLineStartIndex > piece.end.line) { 1098 | return lineStarts[piece.end.line] + piece.end.column - lineStarts[piece.start.line] - piece.start.column; 1099 | } else { 1100 | return lineStarts[expectedLineStartIndex] - lineStarts[piece.start.line] - piece.start.column; 1101 | } 1102 | } 1103 | 1104 | deleteNodeTail(node: TreeNode, pos: BufferCursor) { 1105 | const piece = node.piece; 1106 | const originalLFCnt = piece.lineFeedCnt; 1107 | const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); 1108 | 1109 | const newEnd = pos; 1110 | const newEndOffset = this.offsetInBuffer(piece.bufferIndex, newEnd); 1111 | const newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, piece.start, newEnd); 1112 | 1113 | const lf_delta = newLineFeedCnt - originalLFCnt; 1114 | const size_delta = newEndOffset - originalEndOffset; 1115 | const newLength = piece.length + size_delta; 1116 | 1117 | node.piece = new Piece( 1118 | piece.bufferIndex, 1119 | piece.start, 1120 | newEnd, 1121 | newLineFeedCnt, 1122 | newLength 1123 | ); 1124 | 1125 | updateTreeMetadata(this, node, size_delta, lf_delta); 1126 | } 1127 | 1128 | deleteNodeHead(node: TreeNode, pos: BufferCursor) { 1129 | const piece = node.piece; 1130 | const originalLFCnt = piece.lineFeedCnt; 1131 | const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); 1132 | 1133 | const newStart = pos; 1134 | const newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, newStart, piece.end); 1135 | const newStartOffset = this.offsetInBuffer(piece.bufferIndex, newStart); 1136 | const lf_delta = newLineFeedCnt - originalLFCnt; 1137 | const size_delta = originalStartOffset - newStartOffset; 1138 | const newLength = piece.length + size_delta; 1139 | node.piece = new Piece( 1140 | piece.bufferIndex, 1141 | newStart, 1142 | piece.end, 1143 | newLineFeedCnt, 1144 | newLength 1145 | ); 1146 | 1147 | updateTreeMetadata(this, node, size_delta, lf_delta); 1148 | } 1149 | 1150 | shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) { 1151 | const piece = node.piece; 1152 | const originalStartPos = piece.start; 1153 | const originalEndPos = piece.end; 1154 | 1155 | // old piece, originalStartPos, start 1156 | const oldLength = piece.length; 1157 | const oldLFCnt = piece.lineFeedCnt; 1158 | const newEnd = start; 1159 | const newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, piece.start, newEnd); 1160 | const newLength = this.offsetInBuffer(piece.bufferIndex, start) - this.offsetInBuffer(piece.bufferIndex, originalStartPos); 1161 | 1162 | node.piece = new Piece( 1163 | piece.bufferIndex, 1164 | piece.start, 1165 | newEnd, 1166 | newLineFeedCnt, 1167 | newLength 1168 | ); 1169 | 1170 | updateTreeMetadata(this, node, newLength - oldLength, newLineFeedCnt - oldLFCnt); 1171 | 1172 | // new right piece, end, originalEndPos 1173 | let newPiece = new Piece( 1174 | piece.bufferIndex, 1175 | end, 1176 | originalEndPos, 1177 | this.getLineFeedCnt(piece.bufferIndex, end, originalEndPos), 1178 | this.offsetInBuffer(piece.bufferIndex, originalEndPos) - this.offsetInBuffer(piece.bufferIndex, end) 1179 | ); 1180 | 1181 | let newNode = this.rbInsertRight(node, newPiece); 1182 | this.validateCRLFWithPrevNode(newNode); 1183 | } 1184 | 1185 | appendToNode(node: TreeNode, value: string): void { 1186 | if (this.adjustCarriageReturnFromNext(value, node)) { 1187 | value += '\n'; 1188 | } 1189 | 1190 | const hitCRLF = this.shouldCheckCRLF() && this.startWithLF(value) && this.endWithCR(node); 1191 | const startOffset = this._buffers[0].buffer.length; 1192 | this._buffers[0].buffer += value; 1193 | const lineStarts = createLineStartsFast(value, false); 1194 | for (let i = 0; i < lineStarts.length; i++) { 1195 | lineStarts[i] += startOffset; 1196 | } 1197 | if (hitCRLF) { 1198 | let prevStartOffset = this._buffers[0].lineStarts[this._buffers[0].lineStarts.length - 2]; 1199 | (this._buffers[0].lineStarts).pop(); 1200 | // _lastChangeBufferPos is already wrong 1201 | this._lastChangeBufferPos = { line: this._lastChangeBufferPos.line - 1, column: startOffset - prevStartOffset }; 1202 | } 1203 | 1204 | this._buffers[0].lineStarts = (this._buffers[0].lineStarts).concat(lineStarts.slice(1)); 1205 | const endIndex = this._buffers[0].lineStarts.length - 1; 1206 | const endColumn = this._buffers[0].buffer.length - this._buffers[0].lineStarts[endIndex]; 1207 | const newEnd = { line: endIndex, column: endColumn }; 1208 | const newLength = node.piece.length + value.length; 1209 | const oldLineFeedCnt = node.piece.lineFeedCnt; 1210 | const newLineFeedCnt = this.getLineFeedCnt(0, node.piece.start, newEnd); 1211 | const lf_delta = newLineFeedCnt - oldLineFeedCnt; 1212 | 1213 | node.piece = new Piece( 1214 | node.piece.bufferIndex, 1215 | node.piece.start, 1216 | newEnd, 1217 | newLineFeedCnt, 1218 | newLength 1219 | ); 1220 | 1221 | this._lastChangeBufferPos = newEnd; 1222 | updateTreeMetadata(this, node, value.length, lf_delta); 1223 | } 1224 | 1225 | nodeAt(offset: number): NodePosition { 1226 | let x = this.root; 1227 | let cache = this._searchCache.get(offset); 1228 | if (cache) { 1229 | return { 1230 | node: cache.node, 1231 | nodeStartOffset: cache.nodeStartOffset, 1232 | remainder: offset - cache.nodeStartOffset 1233 | }; 1234 | } 1235 | 1236 | let nodeStartOffset = 0; 1237 | 1238 | while (x !== SENTINEL) { 1239 | if (x.size_left > offset) { 1240 | x = x.left; 1241 | } else if (x.size_left + x.piece.length >= offset) { 1242 | nodeStartOffset += x.size_left; 1243 | let ret = { 1244 | node: x, 1245 | remainder: offset - x.size_left, 1246 | nodeStartOffset 1247 | }; 1248 | this._searchCache.set(ret); 1249 | return ret; 1250 | } else { 1251 | offset -= x.size_left + x.piece.length; 1252 | nodeStartOffset += x.size_left + x.piece.length; 1253 | x = x.right; 1254 | } 1255 | } 1256 | 1257 | return null!; 1258 | } 1259 | 1260 | nodeAt2(lineNumber: number, column: number): NodePosition { 1261 | let x = this.root; 1262 | let nodeStartOffset = 0; 1263 | 1264 | while (x !== SENTINEL) { 1265 | if (x.left !== SENTINEL && x.lf_left >= lineNumber - 1) { 1266 | x = x.left; 1267 | } else if (x.lf_left + x.piece.lineFeedCnt > lineNumber - 1) { 1268 | let prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); 1269 | let accumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 1); 1270 | nodeStartOffset += x.size_left; 1271 | 1272 | return { 1273 | node: x, 1274 | remainder: Math.min(prevAccumualtedValue + column - 1, accumualtedValue), 1275 | nodeStartOffset 1276 | }; 1277 | } else if (x.lf_left + x.piece.lineFeedCnt === lineNumber - 1) { 1278 | let prevAccumualtedValue = this.getAccumulatedValue(x, lineNumber - x.lf_left - 2); 1279 | if (prevAccumualtedValue + column - 1 <= x.piece.length) { 1280 | return { 1281 | node: x, 1282 | remainder: prevAccumualtedValue + column - 1, 1283 | nodeStartOffset 1284 | }; 1285 | } else { 1286 | column -= x.piece.length - prevAccumualtedValue; 1287 | break; 1288 | } 1289 | } else { 1290 | lineNumber -= x.lf_left + x.piece.lineFeedCnt; 1291 | nodeStartOffset += x.size_left + x.piece.length; 1292 | x = x.right; 1293 | } 1294 | } 1295 | 1296 | // search in order, to find the node contains position.column 1297 | x = x.next(); 1298 | while (x !== SENTINEL) { 1299 | 1300 | if (x.piece.lineFeedCnt > 0) { 1301 | let accumualtedValue = this.getAccumulatedValue(x, 0); 1302 | let nodeStartOffset = this.offsetOfNode(x); 1303 | return { 1304 | node: x, 1305 | remainder: Math.min(column - 1, accumualtedValue), 1306 | nodeStartOffset 1307 | }; 1308 | } else { 1309 | if (x.piece.length >= column - 1) { 1310 | let nodeStartOffset = this.offsetOfNode(x); 1311 | return { 1312 | node: x, 1313 | remainder: column - 1, 1314 | nodeStartOffset 1315 | }; 1316 | } else { 1317 | column -= x.piece.length; 1318 | } 1319 | } 1320 | 1321 | x = x.next(); 1322 | } 1323 | 1324 | return null!; 1325 | } 1326 | 1327 | nodeCharCodeAt(node: TreeNode, offset: number): number { 1328 | if (node.piece.lineFeedCnt < 1) { 1329 | return -1; 1330 | } 1331 | let buffer = this._buffers[node.piece.bufferIndex]; 1332 | let newOffset = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start) + offset; 1333 | return buffer.buffer.charCodeAt(newOffset); 1334 | } 1335 | 1336 | offsetOfNode(node: TreeNode): number { 1337 | if (!node) { 1338 | return 0; 1339 | } 1340 | let pos = node.size_left; 1341 | while (node !== this.root) { 1342 | if (node.parent.right === node) { 1343 | pos += node.parent.size_left + node.parent.piece.length; 1344 | } 1345 | 1346 | node = node.parent; 1347 | } 1348 | 1349 | return pos; 1350 | } 1351 | 1352 | // #endregion 1353 | 1354 | // #region CRLF 1355 | shouldCheckCRLF() { 1356 | return !(this._EOLNormalized && this._EOL === '\n'); 1357 | } 1358 | 1359 | startWithLF(val: string | TreeNode): boolean { 1360 | if (typeof val === 'string') { 1361 | return val.charCodeAt(0) === 10; 1362 | } 1363 | 1364 | if (val === SENTINEL || val.piece.lineFeedCnt === 0) { 1365 | return false; 1366 | } 1367 | 1368 | let piece = val.piece; 1369 | let lineStarts = this._buffers[piece.bufferIndex].lineStarts; 1370 | let line = piece.start.line; 1371 | let startOffset = lineStarts[line] + piece.start.column; 1372 | if (line === lineStarts.length - 1) { 1373 | // last line, so there is no line feed at the end of this line 1374 | return false; 1375 | } 1376 | let nextLineOffset = lineStarts[line + 1]; 1377 | if (nextLineOffset > startOffset + 1) { 1378 | return false; 1379 | } 1380 | return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10; 1381 | } 1382 | 1383 | endWithCR(val: string | TreeNode): boolean { 1384 | if (typeof val === 'string') { 1385 | return val.charCodeAt(val.length - 1) === 13; 1386 | } 1387 | 1388 | if (val === SENTINEL || val.piece.lineFeedCnt === 0) { 1389 | return false; 1390 | } 1391 | 1392 | return this.nodeCharCodeAt(val, val.piece.length - 1) === 13; 1393 | } 1394 | 1395 | validateCRLFWithPrevNode(nextNode: TreeNode) { 1396 | if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) { 1397 | let node = nextNode.prev(); 1398 | if (this.endWithCR(node)) { 1399 | this.fixCRLF(node, nextNode); 1400 | } 1401 | } 1402 | } 1403 | 1404 | validateCRLFWithNextNode(node: TreeNode) { 1405 | if (this.shouldCheckCRLF() && this.endWithCR(node)) { 1406 | let nextNode = node.next(); 1407 | if (this.startWithLF(nextNode)) { 1408 | this.fixCRLF(node, nextNode); 1409 | } 1410 | } 1411 | } 1412 | 1413 | fixCRLF(prev: TreeNode, next: TreeNode) { 1414 | let nodesToDel: TreeNode[] = []; 1415 | // update node 1416 | let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts; 1417 | let newEnd: BufferCursor; 1418 | if (prev.piece.end.column === 0) { 1419 | // it means, last line ends with \r, not \r\n 1420 | newEnd = { line: prev.piece.end.line - 1, column: lineStarts[prev.piece.end.line] - lineStarts[prev.piece.end.line - 1] - 1 }; 1421 | } else { 1422 | // \r\n 1423 | newEnd = { line: prev.piece.end.line, column: prev.piece.end.column - 1 }; 1424 | } 1425 | 1426 | const prevNewLength = prev.piece.length - 1; 1427 | const prevNewLFCnt = prev.piece.lineFeedCnt - 1; 1428 | prev.piece = new Piece( 1429 | prev.piece.bufferIndex, 1430 | prev.piece.start, 1431 | newEnd, 1432 | prevNewLFCnt, 1433 | prevNewLength 1434 | ); 1435 | 1436 | updateTreeMetadata(this, prev, - 1, -1); 1437 | if (prev.piece.length === 0) { 1438 | nodesToDel.push(prev); 1439 | } 1440 | 1441 | // update nextNode 1442 | let newStart: BufferCursor = { line: next.piece.start.line + 1, column: 0 }; 1443 | const newLength = next.piece.length - 1; 1444 | const newLineFeedCnt = this.getLineFeedCnt(next.piece.bufferIndex, newStart, next.piece.end); 1445 | next.piece = new Piece( 1446 | next.piece.bufferIndex, 1447 | newStart, 1448 | next.piece.end, 1449 | newLineFeedCnt, 1450 | newLength 1451 | ); 1452 | 1453 | updateTreeMetadata(this, next, - 1, -1); 1454 | if (next.piece.length === 0) { 1455 | nodesToDel.push(next); 1456 | } 1457 | 1458 | // create new piece which contains \r\n 1459 | let pieces = this.createNewPieces('\r\n'); 1460 | this.rbInsertRight(prev, pieces[0]); 1461 | // delete empty nodes 1462 | 1463 | for (let i = 0; i < nodesToDel.length; i++) { 1464 | rbDelete(this, nodesToDel[i]); 1465 | } 1466 | } 1467 | 1468 | adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean { 1469 | if (this.shouldCheckCRLF() && this.endWithCR(value)) { 1470 | let nextNode = node.next(); 1471 | if (this.startWithLF(nextNode)) { 1472 | // move `\n` forward 1473 | value += '\n'; 1474 | 1475 | if (nextNode.piece.length === 1) { 1476 | rbDelete(this, nextNode); 1477 | } else { 1478 | 1479 | const piece = nextNode.piece; 1480 | const newStart: BufferCursor = { line: piece.start.line + 1, column: 0 }; 1481 | const newLength = piece.length - 1; 1482 | const newLineFeedCnt = this.getLineFeedCnt(piece.bufferIndex, newStart, piece.end); 1483 | nextNode.piece = new Piece( 1484 | piece.bufferIndex, 1485 | newStart, 1486 | piece.end, 1487 | newLineFeedCnt, 1488 | newLength 1489 | ); 1490 | 1491 | updateTreeMetadata(this, nextNode, -1, -1); 1492 | } 1493 | return true; 1494 | } 1495 | } 1496 | 1497 | return false; 1498 | } 1499 | 1500 | // #endregion 1501 | 1502 | // #endregion 1503 | 1504 | // #region Tree operations 1505 | iterate(node: TreeNode, callback: (node: TreeNode) => boolean): boolean { 1506 | if (node === SENTINEL) { 1507 | return callback(SENTINEL); 1508 | } 1509 | 1510 | let leftRet = this.iterate(node.left, callback); 1511 | if (!leftRet) { 1512 | return leftRet; 1513 | } 1514 | 1515 | return callback(node) && this.iterate(node.right, callback); 1516 | } 1517 | 1518 | getNodeContent(node: TreeNode) { 1519 | if (node === SENTINEL) { 1520 | return ''; 1521 | } 1522 | let buffer = this._buffers[node.piece.bufferIndex]; 1523 | let currentContent; 1524 | let piece = node.piece; 1525 | let startOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); 1526 | let endOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); 1527 | currentContent = buffer.buffer.substring(startOffset, endOffset); 1528 | return currentContent; 1529 | } 1530 | 1531 | getPieceContent(piece: Piece) { 1532 | let buffer = this._buffers[piece.bufferIndex]; 1533 | let startOffset = this.offsetInBuffer(piece.bufferIndex, piece.start); 1534 | let endOffset = this.offsetInBuffer(piece.bufferIndex, piece.end); 1535 | let currentContent = buffer.buffer.substring(startOffset, endOffset); 1536 | return currentContent; 1537 | } 1538 | 1539 | /** 1540 | * node node 1541 | * / \ / \ 1542 | * a b <---- a b 1543 | * / 1544 | * z 1545 | */ 1546 | rbInsertRight(node: TreeNode | null, p: Piece): TreeNode { 1547 | let z = new TreeNode(p, NodeColor.Red); 1548 | z.left = SENTINEL; 1549 | z.right = SENTINEL; 1550 | z.parent = SENTINEL; 1551 | z.size_left = 0; 1552 | z.lf_left = 0; 1553 | 1554 | let x = this.root; 1555 | if (x === SENTINEL) { 1556 | this.root = z; 1557 | z.color = NodeColor.Black; 1558 | } else if (node!.right === SENTINEL) { 1559 | node!.right = z; 1560 | z.parent = node!; 1561 | } else { 1562 | let nextNode = leftest(node!.right); 1563 | nextNode.left = z; 1564 | z.parent = nextNode; 1565 | } 1566 | 1567 | fixInsert(this, z); 1568 | return z; 1569 | } 1570 | 1571 | /** 1572 | * node node 1573 | * / \ / \ 1574 | * a b ----> a b 1575 | * \ 1576 | * z 1577 | */ 1578 | rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode { 1579 | let z = new TreeNode(p, NodeColor.Red); 1580 | z.left = SENTINEL; 1581 | z.right = SENTINEL; 1582 | z.parent = SENTINEL; 1583 | z.size_left = 0; 1584 | z.lf_left = 0; 1585 | 1586 | if (this.root === SENTINEL) { 1587 | this.root = z; 1588 | z.color = NodeColor.Black; 1589 | } else if (node!.left === SENTINEL) { 1590 | node!.left = z; 1591 | z.parent = node!; 1592 | } else { 1593 | let prevNode = righttest(node!.left); // a 1594 | prevNode.right = z; 1595 | z.parent = prevNode; 1596 | } 1597 | 1598 | fixInsert(this, z); 1599 | return z; 1600 | } 1601 | 1602 | getContentOfSubTree(node: TreeNode): string { 1603 | let str = ''; 1604 | 1605 | this.iterate(node, node => { 1606 | str += this.getNodeContent(node); 1607 | return true; 1608 | }); 1609 | 1610 | return str; 1611 | } 1612 | // #endregion 1613 | } 1614 | -------------------------------------------------------------------------------- /src/pieceTreeBuilder.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { CharCode } from './common/charCode'; 7 | import { StringBuffer, createLineStarts, createLineStartsFast, PieceTreeBase } from './pieceTreeBase'; 8 | 9 | export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); 10 | 11 | export function startsWithUTF8BOM(str: string): boolean { 12 | return !!(str && str.length > 0 && str.charCodeAt(0) === CharCode.UTF8_BOM); 13 | } 14 | 15 | export const enum DefaultEndOfLine { 16 | /** 17 | * Use line feed (\n) as the end of line character. 18 | */ 19 | LF = 1, 20 | /** 21 | * Use carriage return and line feed (\r\n) as the end of line character. 22 | */ 23 | CRLF = 2 24 | } 25 | 26 | 27 | export class PieceTreeTextBufferFactory { 28 | constructor( 29 | private readonly _chunks: StringBuffer[], 30 | private readonly _bom: string, 31 | private readonly _cr: number, 32 | private readonly _lf: number, 33 | private readonly _crlf: number, 34 | private readonly _normalizeEOL: boolean 35 | ) { } 36 | 37 | private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' { 38 | const totalEOLCount = this._cr + this._lf + this._crlf; 39 | const totalCRCount = this._cr + this._crlf; 40 | if (totalEOLCount === 0) { 41 | // This is an empty file or a file with precisely one line 42 | return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n'); 43 | } 44 | if (totalCRCount > totalEOLCount / 2) { 45 | // More than half of the file contains \r\n ending lines 46 | return '\r\n'; 47 | } 48 | // At least one line more ends in \n 49 | return '\n'; 50 | } 51 | 52 | public create(defaultEOL: DefaultEndOfLine): PieceTreeBase { 53 | const eol = this._getEOL(defaultEOL); 54 | let chunks = this._chunks; 55 | 56 | if (this._normalizeEOL && 57 | ((eol === '\r\n' && (this._cr > 0 || this._lf > 0)) 58 | || (eol === '\n' && (this._cr > 0 || this._crlf > 0))) 59 | ) { 60 | // Normalize pieces 61 | for (let i = 0, len = chunks.length; i < len; i++) { 62 | let str = chunks[i].buffer.replace(/\r\n|\r|\n/g, eol); 63 | let newLineStart = createLineStartsFast(str); 64 | chunks[i] = new StringBuffer(str, newLineStart); 65 | } 66 | } 67 | 68 | return new PieceTreeBase(chunks, eol, this._normalizeEOL); 69 | } 70 | 71 | public getFirstLineText(lengthLimit: number): string { 72 | return this._chunks[0].buffer.substr(0, 100).split(/\r\n|\r|\n/)[0]; 73 | } 74 | } 75 | 76 | export class PieceTreeTextBufferBuilder { 77 | private readonly chunks: StringBuffer[]; 78 | private BOM: string; 79 | 80 | private _hasPreviousChar: boolean; 81 | private _previousChar: number; 82 | private readonly _tmpLineStarts: number[]; 83 | 84 | private cr: number; 85 | private lf: number; 86 | private crlf: number; 87 | 88 | constructor() { 89 | this.chunks = []; 90 | this.BOM = ''; 91 | 92 | this._hasPreviousChar = false; 93 | this._previousChar = 0; 94 | this._tmpLineStarts = []; 95 | 96 | this.cr = 0; 97 | this.lf = 0; 98 | this.crlf = 0; 99 | } 100 | 101 | public acceptChunk(chunk: string): void { 102 | if (chunk.length === 0) { 103 | return; 104 | } 105 | 106 | if (this.chunks.length === 0) { 107 | if (startsWithUTF8BOM(chunk)) { 108 | this.BOM = UTF8_BOM_CHARACTER; 109 | chunk = chunk.substr(1); 110 | } 111 | } 112 | 113 | const lastChar = chunk.charCodeAt(chunk.length - 1); 114 | if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xD800 && lastChar <= 0xDBFF)) { 115 | // last character is \r or a high surrogate => keep it back 116 | this._acceptChunk1(chunk.substr(0, chunk.length - 1), false); 117 | this._hasPreviousChar = true; 118 | this._previousChar = lastChar; 119 | } else { 120 | this._acceptChunk1(chunk, false); 121 | this._hasPreviousChar = false; 122 | this._previousChar = lastChar; 123 | } 124 | } 125 | 126 | private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void { 127 | if (!allowEmptyStrings && chunk.length === 0) { 128 | // Nothing to do 129 | return; 130 | } 131 | 132 | if (this._hasPreviousChar) { 133 | this._acceptChunk2(String.fromCharCode(this._previousChar) + chunk); 134 | } else { 135 | this._acceptChunk2(chunk); 136 | } 137 | } 138 | 139 | private _acceptChunk2(chunk: string): void { 140 | const lineStarts = createLineStarts(this._tmpLineStarts, chunk); 141 | 142 | this.chunks.push(new StringBuffer(chunk, lineStarts.lineStarts)); 143 | this.cr += lineStarts.cr; 144 | this.lf += lineStarts.lf; 145 | this.crlf += lineStarts.crlf; 146 | } 147 | 148 | public finish(normalizeEOL: boolean = true): PieceTreeTextBufferFactory { 149 | this._finish(); 150 | return new PieceTreeTextBufferFactory( 151 | this.chunks, 152 | this.BOM, 153 | this.cr, 154 | this.lf, 155 | this.crlf, 156 | normalizeEOL 157 | ); 158 | } 159 | 160 | private _finish(): void { 161 | if (this.chunks.length === 0) { 162 | this._acceptChunk1('', true); 163 | } 164 | 165 | if (this._hasPreviousChar) { 166 | this._hasPreviousChar = false; 167 | // recreate last chunk 168 | let lastChunk = this.chunks[this.chunks.length - 1]; 169 | lastChunk.buffer += String.fromCharCode(this._previousChar); 170 | let newLineStarts = createLineStartsFast(lastChunk.buffer); 171 | lastChunk.lineStarts = newLineStarts; 172 | if (this._previousChar === CharCode.CarriageReturn) { 173 | this.cr++; 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/rbTreeBase.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | import { Piece, PieceTreeBase } from './pieceTreeBase'; 7 | 8 | export class TreeNode { 9 | parent: TreeNode; 10 | left: TreeNode; 11 | right: TreeNode; 12 | color: NodeColor; 13 | 14 | // Piece 15 | piece: Piece; 16 | size_left: number; // size of the left subtree (not inorder) 17 | lf_left: number; // line feeds cnt in the left subtree (not in order) 18 | 19 | constructor(piece: Piece, color: NodeColor) { 20 | this.piece = piece; 21 | this.color = color; 22 | this.size_left = 0; 23 | this.lf_left = 0; 24 | this.parent = this; 25 | this.left = this; 26 | this.right = this; 27 | } 28 | 29 | public next(): TreeNode { 30 | if (this.right !== SENTINEL) { 31 | return leftest(this.right); 32 | } 33 | 34 | let node: TreeNode = this; 35 | 36 | while (node.parent !== SENTINEL) { 37 | if (node.parent.left === node) { 38 | break; 39 | } 40 | 41 | node = node.parent; 42 | } 43 | 44 | if (node.parent === SENTINEL) { 45 | return SENTINEL; 46 | } else { 47 | return node.parent; 48 | } 49 | } 50 | 51 | public prev(): TreeNode { 52 | if (this.left !== SENTINEL) { 53 | return righttest(this.left); 54 | } 55 | 56 | let node: TreeNode = this; 57 | 58 | while (node.parent !== SENTINEL) { 59 | if (node.parent.right === node) { 60 | break; 61 | } 62 | 63 | node = node.parent; 64 | } 65 | 66 | if (node.parent === SENTINEL) { 67 | return SENTINEL; 68 | } else { 69 | return node.parent; 70 | } 71 | } 72 | 73 | public detach(): void { 74 | this.parent = null!; 75 | this.left = null!; 76 | this.right = null!; 77 | } 78 | } 79 | 80 | export const enum NodeColor { 81 | Black = 0, 82 | Red = 1, 83 | } 84 | 85 | export const SENTINEL: TreeNode = new TreeNode(null!, NodeColor.Black); 86 | SENTINEL.parent = SENTINEL; 87 | SENTINEL.left = SENTINEL; 88 | SENTINEL.right = SENTINEL; 89 | SENTINEL.color = NodeColor.Black; 90 | 91 | export function leftest(node: TreeNode): TreeNode { 92 | while (node.left !== SENTINEL) { 93 | node = node.left; 94 | } 95 | return node; 96 | } 97 | 98 | export function righttest(node: TreeNode): TreeNode { 99 | while (node.right !== SENTINEL) { 100 | node = node.right; 101 | } 102 | return node; 103 | } 104 | 105 | export function calculateSize(node: TreeNode): number { 106 | if (node === SENTINEL) { 107 | return 0; 108 | } 109 | 110 | return node.size_left + node.piece.length + calculateSize(node.right); 111 | } 112 | 113 | export function calculateLF(node: TreeNode): number { 114 | if (node === SENTINEL) { 115 | return 0; 116 | } 117 | 118 | return node.lf_left + node.piece.lineFeedCnt + calculateLF(node.right); 119 | } 120 | 121 | export function resetSentinel(): void { 122 | SENTINEL.parent = SENTINEL; 123 | } 124 | 125 | export function leftRotate(tree: PieceTreeBase, x: TreeNode) { 126 | let y = x.right; 127 | 128 | // fix size_left 129 | y.size_left += x.size_left + (x.piece ? x.piece.length : 0); 130 | y.lf_left += x.lf_left + (x.piece ? x.piece.lineFeedCnt : 0); 131 | x.right = y.left; 132 | 133 | if (y.left !== SENTINEL) { 134 | y.left.parent = x; 135 | } 136 | y.parent = x.parent; 137 | if (x.parent === SENTINEL) { 138 | tree.root = y; 139 | } else if (x.parent.left === x) { 140 | x.parent.left = y; 141 | } else { 142 | x.parent.right = y; 143 | } 144 | y.left = x; 145 | x.parent = y; 146 | } 147 | 148 | export function rightRotate(tree: PieceTreeBase, y: TreeNode) { 149 | let x = y.left; 150 | y.left = x.right; 151 | if (x.right !== SENTINEL) { 152 | x.right.parent = y; 153 | } 154 | x.parent = y.parent; 155 | 156 | // fix size_left 157 | y.size_left -= x.size_left + (x.piece ? x.piece.length : 0); 158 | y.lf_left -= x.lf_left + (x.piece ? x.piece.lineFeedCnt : 0); 159 | 160 | if (y.parent === SENTINEL) { 161 | tree.root = x; 162 | } else if (y === y.parent.right) { 163 | y.parent.right = x; 164 | } else { 165 | y.parent.left = x; 166 | } 167 | 168 | x.right = y; 169 | y.parent = x; 170 | } 171 | 172 | export function rbDelete(tree: PieceTreeBase, z: TreeNode) { 173 | let x: TreeNode; 174 | let y: TreeNode; 175 | 176 | if (z.left === SENTINEL) { 177 | y = z; 178 | x = y.right; 179 | } else if (z.right === SENTINEL) { 180 | y = z; 181 | x = y.left; 182 | } else { 183 | y = leftest(z.right); 184 | x = y.right; 185 | } 186 | 187 | if (y === tree.root) { 188 | tree.root = x; 189 | 190 | // if x is null, we are removing the only node 191 | x.color = NodeColor.Black; 192 | z.detach(); 193 | resetSentinel(); 194 | tree.root.parent = SENTINEL; 195 | 196 | return; 197 | } 198 | 199 | let yWasRed = (y.color === NodeColor.Red); 200 | 201 | if (y === y.parent.left) { 202 | y.parent.left = x; 203 | } else { 204 | y.parent.right = x; 205 | } 206 | 207 | if (y === z) { 208 | x.parent = y.parent; 209 | recomputeTreeMetadata(tree, x); 210 | } else { 211 | if (y.parent === z) { 212 | x.parent = y; 213 | } else { 214 | x.parent = y.parent; 215 | } 216 | 217 | // as we make changes to x's hierarchy, update size_left of subtree first 218 | recomputeTreeMetadata(tree, x); 219 | 220 | y.left = z.left; 221 | y.right = z.right; 222 | y.parent = z.parent; 223 | y.color = z.color; 224 | 225 | if (z === tree.root) { 226 | tree.root = y; 227 | } else { 228 | if (z === z.parent.left) { 229 | z.parent.left = y; 230 | } else { 231 | z.parent.right = y; 232 | } 233 | } 234 | 235 | if (y.left !== SENTINEL) { 236 | y.left.parent = y; 237 | } 238 | if (y.right !== SENTINEL) { 239 | y.right.parent = y; 240 | } 241 | // update metadata 242 | // we replace z with y, so in this sub tree, the length change is z.item.length 243 | y.size_left = z.size_left; 244 | y.lf_left = z.lf_left; 245 | recomputeTreeMetadata(tree, y); 246 | } 247 | 248 | z.detach(); 249 | 250 | if (x.parent.left === x) { 251 | let newSizeLeft = calculateSize(x); 252 | let newLFLeft = calculateLF(x); 253 | if (newSizeLeft !== x.parent.size_left || newLFLeft !== x.parent.lf_left) { 254 | let delta = newSizeLeft - x.parent.size_left; 255 | let lf_delta = newLFLeft - x.parent.lf_left; 256 | x.parent.size_left = newSizeLeft; 257 | x.parent.lf_left = newLFLeft; 258 | updateTreeMetadata(tree, x.parent, delta, lf_delta); 259 | } 260 | } 261 | 262 | recomputeTreeMetadata(tree, x.parent); 263 | 264 | if (yWasRed) { 265 | resetSentinel(); 266 | return; 267 | } 268 | 269 | // RB-DELETE-FIXUP 270 | let w: TreeNode; 271 | while (x !== tree.root && x.color === NodeColor.Black) { 272 | if (x === x.parent.left) { 273 | w = x.parent.right; 274 | 275 | if (w.color === NodeColor.Red) { 276 | w.color = NodeColor.Black; 277 | x.parent.color = NodeColor.Red; 278 | leftRotate(tree, x.parent); 279 | w = x.parent.right; 280 | } 281 | 282 | if (w.left.color === NodeColor.Black && w.right.color === NodeColor.Black) { 283 | w.color = NodeColor.Red; 284 | x = x.parent; 285 | } else { 286 | if (w.right.color === NodeColor.Black) { 287 | w.left.color = NodeColor.Black; 288 | w.color = NodeColor.Red; 289 | rightRotate(tree, w); 290 | w = x.parent.right; 291 | } 292 | 293 | w.color = x.parent.color; 294 | x.parent.color = NodeColor.Black; 295 | w.right.color = NodeColor.Black; 296 | leftRotate(tree, x.parent); 297 | x = tree.root; 298 | } 299 | } else { 300 | w = x.parent.left; 301 | 302 | if (w.color === NodeColor.Red) { 303 | w.color = NodeColor.Black; 304 | x.parent.color = NodeColor.Red; 305 | rightRotate(tree, x.parent); 306 | w = x.parent.left; 307 | } 308 | 309 | if (w.left.color === NodeColor.Black && w.right.color === NodeColor.Black) { 310 | w.color = NodeColor.Red; 311 | x = x.parent; 312 | 313 | } else { 314 | if (w.left.color === NodeColor.Black) { 315 | w.right.color = NodeColor.Black; 316 | w.color = NodeColor.Red; 317 | leftRotate(tree, w); 318 | w = x.parent.left; 319 | } 320 | 321 | w.color = x.parent.color; 322 | x.parent.color = NodeColor.Black; 323 | w.left.color = NodeColor.Black; 324 | rightRotate(tree, x.parent); 325 | x = tree.root; 326 | } 327 | } 328 | } 329 | x.color = NodeColor.Black; 330 | resetSentinel(); 331 | } 332 | 333 | export function fixInsert(tree: PieceTreeBase, x: TreeNode) { 334 | recomputeTreeMetadata(tree, x); 335 | 336 | while (x !== tree.root && x.parent.color === NodeColor.Red) { 337 | if (x.parent === x.parent.parent.left) { 338 | const y = x.parent.parent.right; 339 | 340 | if (y.color === NodeColor.Red) { 341 | x.parent.color = NodeColor.Black; 342 | y.color = NodeColor.Black; 343 | x.parent.parent.color = NodeColor.Red; 344 | x = x.parent.parent; 345 | } else { 346 | if (x === x.parent.right) { 347 | x = x.parent; 348 | leftRotate(tree, x); 349 | } 350 | 351 | x.parent.color = NodeColor.Black; 352 | x.parent.parent.color = NodeColor.Red; 353 | rightRotate(tree, x.parent.parent); 354 | } 355 | } else { 356 | const y = x.parent.parent.left; 357 | 358 | if (y.color === NodeColor.Red) { 359 | x.parent.color = NodeColor.Black; 360 | y.color = NodeColor.Black; 361 | x.parent.parent.color = NodeColor.Red; 362 | x = x.parent.parent; 363 | } else { 364 | if (x === x.parent.left) { 365 | x = x.parent; 366 | rightRotate(tree, x); 367 | } 368 | x.parent.color = NodeColor.Black; 369 | x.parent.parent.color = NodeColor.Red; 370 | leftRotate(tree, x.parent.parent); 371 | } 372 | } 373 | } 374 | 375 | tree.root.color = NodeColor.Black; 376 | } 377 | 378 | export function updateTreeMetadata(tree: PieceTreeBase, x: TreeNode, delta: number, lineFeedCntDelta: number): void { 379 | // node length change or line feed count change 380 | while (x !== tree.root && x !== SENTINEL) { 381 | if (x.parent.left === x) { 382 | x.parent.size_left += delta; 383 | x.parent.lf_left += lineFeedCntDelta; 384 | } 385 | 386 | x = x.parent; 387 | } 388 | } 389 | 390 | export function recomputeTreeMetadata(tree: PieceTreeBase, x: TreeNode) { 391 | let delta = 0; 392 | let lf_delta = 0; 393 | if (x === tree.root) { 394 | return; 395 | } 396 | 397 | if (delta === 0) { 398 | // go upwards till the node whose left subtree is changed. 399 | while (x !== tree.root && x === x.parent.right) { 400 | x = x.parent; 401 | } 402 | 403 | if (x === tree.root) { 404 | // well, it means we add a node to the end (inorder) 405 | return; 406 | } 407 | 408 | // x is the node whose right subtree is changed. 409 | x = x.parent; 410 | 411 | delta = calculateSize(x.left) - x.size_left; 412 | lf_delta = calculateLF(x.left) - x.lf_left; 413 | x.size_left += delta; 414 | x.lf_left += lf_delta; 415 | } 416 | 417 | // go upwards till root. O(logN) 418 | while (x !== tree.root && (delta !== 0 || lf_delta !== 0)) { 419 | if (x.parent.left === x) { 420 | x.parent.size_left += delta; 421 | x.parent.lf_left += lf_delta; 422 | } 423 | 424 | x = x.parent; 425 | } 426 | } 427 | -------------------------------------------------------------------------------- /src/test/piecetree.test.ts: -------------------------------------------------------------------------------- 1 | import { PieceTreeTextBufferBuilder, DefaultEndOfLine } from '../index'; 2 | 3 | describe('random tests', () => { 4 | it('random insert delete', () => { 5 | let pieceTreeTextBufferBuilder = new PieceTreeTextBufferBuilder(); 6 | pieceTreeTextBufferBuilder.acceptChunk('abc\n'); 7 | pieceTreeTextBufferBuilder.acceptChunk('def'); 8 | let pieceTreeFactory = pieceTreeTextBufferBuilder.finish(true); 9 | let pieceTree = pieceTreeFactory.create(DefaultEndOfLine.LF); 10 | 11 | expect(pieceTree.getLineCount()).toEqual(2); 12 | expect(pieceTree.getLineContent(1)).toEqual('abc'); 13 | expect(pieceTree.getLineContent(2)).toEqual('def'); 14 | 15 | pieceTree.insert(1, '+'); 16 | expect(pieceTree.getLineCount()).toEqual(2); 17 | expect(pieceTree.getLineContent(1)).toEqual('a+bc'); 18 | expect(pieceTree.getLineContent(2)).toEqual('def'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 7 | "outDir": "./lib", /* Redirect output structure to the directory. */ 8 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via */ 9 | } 10 | } --------------------------------------------------------------------------------