Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
掘金者说
vscode
提交
f4c8522d
V
vscode
项目概览
掘金者说
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
f4c8522d
编写于
1月 04, 2018
作者:
P
Peng Lyu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
safe point
上级
c4c74eb8
变更
13
隐藏空白更改
内联
并排
Showing
13 changed file
with
1883 addition
and
24 deletion
+1883
-24
src/vs/editor/common/model/indentationGuesser.ts
src/vs/editor/common/model/indentationGuesser.ts
+24
-1
src/vs/editor/common/model/modelLine.ts
src/vs/editor/common/model/modelLine.ts
+4
-3
src/vs/editor/common/model/textBuffer.ts
src/vs/editor/common/model/textBuffer.ts
+1
-1
src/vs/editor/common/model/textBuffer2.ts
src/vs/editor/common/model/textBuffer2.ts
+1711
-0
src/vs/editor/common/model/textModel.ts
src/vs/editor/common/model/textModel.ts
+11
-4
src/vs/editor/common/model/textSource.ts
src/vs/editor/common/model/textSource.ts
+28
-6
src/vs/editor/common/services/modelServiceImpl.ts
src/vs/editor/common/services/modelServiceImpl.ts
+17
-2
src/vs/editor/common/viewModel/prefixSumComputer.ts
src/vs/editor/common/viewModel/prefixSumComputer.ts
+1
-1
src/vs/workbench/common/editor/textEditorModel.ts
src/vs/workbench/common/editor/textEditorModel.ts
+3
-1
src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts
...ts/welcome/walkThrough/node/walkThroughContentProvider.ts
+1
-1
src/vs/workbench/services/backup/node/backupFileService.ts
src/vs/workbench/services/backup/node/backupFileService.ts
+1
-1
src/vs/workbench/services/textfile/electron-browser/modelBuilder.ts
...kbench/services/textfile/electron-browser/modelBuilder.ts
+80
-2
src/vs/workbench/test/workbenchTestServices.ts
src/vs/workbench/test/workbenchTestServices.ts
+1
-1
未找到文件。
src/vs/editor/common/model/indentationGuesser.ts
浏览文件 @
f4c8522d
...
...
@@ -6,6 +6,8 @@
import
{
CharCode
}
from
'
vs/base/common/charCode
'
;
import
{
TextBuffer
}
from
'
vs/editor/common/model/textBuffer
'
;
import
{
TextBuffer
as
TextBuffer2
}
from
'
vs/editor/common/model/textBuffer2
'
;
import
{
IRawPTBuffer
}
from
'
./textSource
'
;
export
interface
IIndentationGuesserTarget
{
getLineCount
():
number
;
...
...
@@ -15,7 +17,7 @@ export interface IIndentationGuesserTarget {
export
class
IndentationGuesserTextBufferTarget
implements
IIndentationGuesserTarget
{
constructor
(
private
readonly
_buffer
:
TextBuffer
private
readonly
_buffer
:
TextBuffer
|
TextBuffer2
)
{
}
public
getLineCount
():
number
{
...
...
@@ -42,6 +44,27 @@ export class IndentationGuesserStringArrayTarget implements IIndentationGuesserT
}
}
export
class
IndentationGuesserRawTextBufferTarget
implements
IIndentationGuesserTarget
{
constructor
(
private
readonly
_rawBuffer
:
IRawPTBuffer
)
{
}
public
getLineCount
():
number
{
return
this
.
_rawBuffer
.
length
;
}
public
getLineContent
(
lineNumber
:
number
):
string
{
if
(
lineNumber
===
1
)
{
return
this
.
_rawBuffer
.
text
.
substring
(
0
,
this
.
_rawBuffer
.
lineStarts
[
0
]);
}
else
if
(
lineNumber
===
this
.
_rawBuffer
.
lineStarts
.
length
+
1
)
{
return
this
.
_rawBuffer
.
text
.
substring
(
this
.
_rawBuffer
.
lineStarts
[
this
.
_rawBuffer
.
lineStarts
.
length
-
1
]
+
1
);
}
return
this
.
_rawBuffer
.
text
.
substring
(
this
.
_rawBuffer
.
lineStarts
[
lineNumber
-
2
]
+
1
,
this
.
_rawBuffer
.
lineStarts
[
lineNumber
-
1
]);
}
}
/**
* Compute the diff in spaces between two line's indentation.
*/
...
...
src/vs/editor/common/model/modelLine.ts
浏览文件 @
f4c8522d
...
...
@@ -13,6 +13,7 @@ import { Range } from 'vs/editor/common/core/range';
import
{
IModelTokensChangedEvent
}
from
'
vs/editor/common/model/textModelEvents
'
;
import
{
onUnexpectedError
}
from
'
vs/base/common/errors
'
;
import
{
TextBuffer
}
from
'
vs/editor/common/model/textBuffer
'
;
import
{
TextBuffer
as
TextBuffer2
}
from
'
vs/editor/common/model/textBuffer2
'
;
import
{
TokenizationResult2
}
from
'
vs/editor/common/core/token
'
;
import
{
nullTokenize2
}
from
'
vs/editor/common/modes/nullMode
'
;
...
...
@@ -253,7 +254,7 @@ export class ModelLinesTokens {
return
(
firstInvalidLineNumber
>=
lineNumber
);
}
public
hasLinesToTokenize
(
buffer
:
TextBuffer
):
boolean
{
public
hasLinesToTokenize
(
buffer
:
TextBuffer
|
TextBuffer2
):
boolean
{
return
(
this
.
_invalidLineStartIndex
<
buffer
.
getLineCount
());
}
...
...
@@ -419,7 +420,7 @@ export class ModelLinesTokens {
//#region Tokenization
public
_tokenizeOneLine
(
buffer
:
TextBuffer
,
eventBuilder
:
ModelTokensChangedEventBuilder
):
number
{
public
_tokenizeOneLine
(
buffer
:
TextBuffer
|
TextBuffer2
,
eventBuilder
:
ModelTokensChangedEventBuilder
):
number
{
if
(
!
this
.
hasLinesToTokenize
(
buffer
))
{
return
buffer
.
getLineCount
()
+
1
;
}
...
...
@@ -428,7 +429,7 @@ export class ModelLinesTokens {
return
lineNumber
;
}
public
_updateTokensUntilLine
(
buffer
:
TextBuffer
,
eventBuilder
:
ModelTokensChangedEventBuilder
,
lineNumber
:
number
):
void
{
public
_updateTokensUntilLine
(
buffer
:
TextBuffer
|
TextBuffer2
,
eventBuilder
:
ModelTokensChangedEventBuilder
,
lineNumber
:
number
):
void
{
if
(
!
this
.
tokenizationSupport
)
{
this
.
_invalidLineStartIndex
=
buffer
.
getLineCount
();
return
;
...
...
src/vs/editor/common/model/textBuffer.ts
浏览文件 @
f4c8522d
...
...
@@ -55,7 +55,7 @@ export class TextBuffer {
private
_lineStarts
:
PrefixSumComputer
;
constructor
(
textSource
:
ITextSource
)
{
this
.
_lines
=
textSource
.
lines
.
slice
(
0
);
this
.
_lines
=
(
<
string
[]
>
textSource
.
lines
)
.
slice
(
0
);
this
.
_BOM
=
textSource
.
BOM
;
this
.
_EOL
=
textSource
.
EOL
;
this
.
_mightContainRTL
=
textSource
.
containsRTL
;
...
...
src/vs/editor/common/model/textBuffer2.ts
0 → 100644
浏览文件 @
f4c8522d
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'
use strict
'
;
import
{
Range
}
from
'
vs/editor/common/core/range
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
*
as
editorCommon
from
'
vs/editor/common/editorCommon
'
;
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
PrefixSumComputer
,
PrefixSumIndexOfResult
}
from
'
vs/editor/common/viewModel/prefixSumComputer
'
;
import
{
ITextSource
,
IRawPTBuffer
}
from
'
vs/editor/common/model/textSource
'
;
import
{
ApplyEditResult
,
IInternalModelContentChange
}
from
'
vs/editor/common/model/textBuffer
'
;
import
{
IValidatedEditOperation
}
from
'
vs/editor/common/model/editableTextModel
'
;
import
{
ModelRawChange
,
ModelRawLineChanged
,
ModelRawLinesDeleted
,
ModelRawLinesInserted
}
from
'
vs/editor/common/model/textModelEvents
'
;
export
const
enum
NodeColor
{
Black
=
0
,
Red
=
1
,
}
export
let
error
=
{
sizeLeft
:
false
};
function
getNodeColor
(
node
:
TreeNode
)
{
return
node
.
color
;
}
function
setNodeColor
(
node
:
TreeNode
,
color
:
NodeColor
)
{
node
.
color
=
color
;
}
function
leftest
(
node
:
TreeNode
):
TreeNode
{
while
(
node
.
left
!==
SENTINEL
)
{
node
=
node
.
left
;
}
return
node
;
}
function
righttest
(
node
:
TreeNode
):
TreeNode
{
while
(
node
.
right
!==
SENTINEL
)
{
node
=
node
.
right
;
}
return
node
;
}
function
calculateSize
(
node
:
TreeNode
):
number
{
if
(
node
===
SENTINEL
)
{
return
0
;
}
return
node
.
size_left
+
node
.
piece
.
length
+
calculateSize
(
node
.
right
);
}
function
calculateLF
(
node
:
TreeNode
):
number
{
if
(
node
===
SENTINEL
)
{
return
0
;
}
return
node
.
lf_left
+
node
.
piece
.
lineFeedCnt
+
calculateLF
(
node
.
right
);
}
function
resetSentinel
():
void
{
SENTINEL
.
parent
=
SENTINEL
;
}
export
class
TreeNode
{
parent
:
TreeNode
;
left
:
TreeNode
;
right
:
TreeNode
;
color
:
NodeColor
;
// Piece
piece
:
Piece
;
size_left
:
number
;
// size of the left subtree (not inorder)
lf_left
:
number
;
// line feeds cnt in the left subtree (not in order)
constructor
(
piece
:
Piece
,
color
:
NodeColor
)
{
this
.
piece
=
piece
;
this
.
color
=
color
;
this
.
size_left
=
0
;
this
.
lf_left
=
0
;
this
.
parent
=
null
;
this
.
left
=
null
;
this
.
right
=
null
;
}
public
next
():
TreeNode
{
if
(
this
.
right
!==
SENTINEL
)
{
return
leftest
(
this
.
right
);
}
let
node
:
TreeNode
=
this
;
while
(
node
.
parent
!==
SENTINEL
)
{
if
(
node
.
parent
.
left
===
node
)
{
break
;
}
node
=
node
.
parent
;
}
if
(
node
.
parent
===
SENTINEL
)
{
// root
// if (node.right === SENTINEL) {
return
SENTINEL
;
// }
// return leftest(node.right);
}
else
{
return
node
.
parent
;
}
}
public
prev
():
TreeNode
{
if
(
this
.
left
!==
SENTINEL
)
{
return
righttest
(
this
.
left
);
}
let
node
:
TreeNode
=
this
;
while
(
node
.
parent
!==
SENTINEL
)
{
if
(
node
.
parent
.
right
===
node
)
{
break
;
}
node
=
node
.
parent
;
}
if
(
node
.
parent
===
SENTINEL
)
{
// root
// if (node.left === SENTINEL) {
return
SENTINEL
;
// }
// return righttest(node.left);
}
else
{
return
node
.
parent
;
}
}
public
detach
():
void
{
this
.
parent
=
null
;
this
.
left
=
null
;
this
.
right
=
null
;
}
}
export
const
SENTINEL
:
TreeNode
=
new
TreeNode
(
null
,
NodeColor
.
Black
);
SENTINEL
.
parent
=
SENTINEL
;
SENTINEL
.
left
=
SENTINEL
;
SENTINEL
.
right
=
SENTINEL
;
setNodeColor
(
SENTINEL
,
NodeColor
.
Black
);
export
interface
BufferCursor
{
/**
* Piece Index
*/
node
:
TreeNode
;
/**
* remainer in current piece.
*/
remainder
:
number
;
}
export
class
Piece
{
isOriginalBuffer
:
boolean
;
offset
:
number
;
length
:
number
;
// size of current piece
lineFeedCnt
:
number
;
lineStarts
:
PrefixSumComputer
;
constructor
(
isOriginalBuffer
:
boolean
,
offset
:
number
,
length
:
number
,
lineFeedCnt
:
number
,
lineLengthsVal
:
Uint32Array
)
{
this
.
isOriginalBuffer
=
isOriginalBuffer
;
this
.
offset
=
offset
;
this
.
length
=
length
;
this
.
lineFeedCnt
=
lineFeedCnt
;
this
.
lineStarts
=
null
;
if
(
lineLengthsVal
)
{
let
newVal
=
new
Uint32Array
(
lineLengthsVal
.
length
);
newVal
.
set
(
lineLengthsVal
);
this
.
lineStarts
=
new
PrefixSumComputer
(
newVal
);
}
}
}
export
class
TextBuffer
{
private
_BOM
:
string
;
private
_EOL
:
string
;
private
_mightContainRTL
:
boolean
;
private
_mightContainNonBasicASCII
:
boolean
;
private
_originalBuffer
:
string
;
private
_changeBuffer
:
string
;
private
_regex
:
RegExp
;
root
:
TreeNode
;
constructor
(
textSource
:
ITextSource
)
{
let
rawBuffer
=
<
IRawPTBuffer
>
textSource
.
lines
;
this
.
_originalBuffer
=
rawBuffer
.
text
;
this
.
_changeBuffer
=
''
;
this
.
root
=
SENTINEL
;
this
.
_BOM
=
textSource
.
BOM
;
this
.
_EOL
=
textSource
.
EOL
;
this
.
_mightContainNonBasicASCII
=
!
textSource
.
isBasicASCII
;
this
.
_mightContainRTL
=
textSource
.
containsRTL
;
this
.
_regex
=
new
RegExp
(
/
\r\n
|
\r
|
\n
/g
);
if
(
this
.
_originalBuffer
.
length
>
0
)
{
let
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
this
.
_originalBuffer
);
let
piece
=
new
Piece
(
true
,
0
,
this
.
_originalBuffer
.
length
,
lineFeedCount
,
lineLengths
);
this
.
rbInsertLeft
(
null
,
piece
);
}
}
// #region TextBuffer
public
getLinesContent
()
{
return
this
.
getContentOfSubTree
(
this
.
root
);
}
public
getLineCount
():
number
{
let
x
=
this
.
root
;
let
ret
=
1
;
while
(
x
!==
SENTINEL
)
{
ret
+=
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
;
x
=
x
.
right
;
}
return
ret
;
}
public
getValueInRange
(
range
:
Range
,
eol
:
editorCommon
.
EndOfLinePreference
=
editorCommon
.
EndOfLinePreference
.
TextDefined
):
string
{
// todo, validate range.
if
(
range
.
startLineNumber
===
range
.
endLineNumber
&&
range
.
startColumn
===
range
.
endColumn
)
{
return
''
;
}
let
startPosition
=
this
.
nodeAt2
(
new
Position
(
range
.
startLineNumber
,
range
.
startColumn
));
let
endPosition
=
this
.
nodeAt2
(
new
Position
(
range
.
endLineNumber
,
range
.
endColumn
));
if
(
startPosition
.
node
===
endPosition
.
node
)
{
let
node
=
startPosition
.
node
;
let
buffer
=
node
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
return
buffer
.
substring
(
node
.
piece
.
offset
+
startPosition
.
remainder
,
node
.
piece
.
offset
+
endPosition
.
remainder
);
}
let
x
=
startPosition
.
node
;
let
buffer
=
x
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
let
ret
=
buffer
.
substring
(
x
.
piece
.
offset
+
startPosition
.
remainder
,
x
.
piece
.
offset
+
x
.
piece
.
length
);
x
=
x
.
next
();
while
(
x
!==
SENTINEL
)
{
let
buffer
=
x
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
if
(
x
===
endPosition
.
node
)
{
ret
+=
buffer
.
substring
(
x
.
piece
.
offset
,
x
.
piece
.
offset
+
endPosition
.
remainder
);
break
;
}
else
{
ret
+=
buffer
.
substr
(
x
.
piece
.
offset
,
x
.
piece
.
length
);
}
x
=
x
.
next
();
}
return
ret
;
}
public
getLineContent
(
lineNumber
:
number
):
string
{
let
x
=
this
.
root
;
let
ret
=
''
;
while
(
x
!==
SENTINEL
)
{
if
(
x
.
left
!==
SENTINEL
&&
x
.
lf_left
>=
lineNumber
-
1
)
{
x
=
x
.
left
;
}
else
if
(
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
>
lineNumber
-
1
)
{
let
prevAccumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
2
);
let
accumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
1
);
let
buffer
=
x
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
return
buffer
.
substring
(
x
.
piece
.
offset
+
prevAccumualtedValue
,
x
.
piece
.
offset
+
accumualtedValue
);
}
else
if
(
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
===
lineNumber
-
1
)
{
let
prevAccumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
2
);
let
buffer
=
x
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
ret
=
buffer
.
substring
(
x
.
piece
.
offset
+
prevAccumualtedValue
,
x
.
piece
.
offset
+
x
.
piece
.
length
);
break
;
}
else
{
lineNumber
-=
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
;
x
=
x
.
right
;
}
}
// if (x === SENTINEL) {
// throw('not possible');
// }
// search in order, to find the node contains end column
x
=
x
.
next
();
while
(
x
!==
SENTINEL
)
{
let
buffer
=
x
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
if
(
x
.
piece
.
lineFeedCnt
>
0
)
{
let
accumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
0
);
ret
+=
buffer
.
substring
(
x
.
piece
.
offset
,
x
.
piece
.
offset
+
accumualtedValue
);
return
ret
;
}
else
{
ret
+=
buffer
.
substr
(
x
.
piece
.
offset
,
x
.
piece
.
length
);
}
x
=
x
.
next
();
}
return
ret
;
}
public
getOffsetAt
(
position
:
Position
):
number
{
return
this
.
getOffsetAt2
(
position
.
lineNumber
,
position
.
column
);
}
public
getOffsetAt2
(
lineNumber
:
number
,
column
:
number
):
number
{
let
leftLen
=
0
;
// inorder
let
x
=
this
.
root
;
while
(
x
!==
SENTINEL
)
{
if
(
x
.
left
!==
SENTINEL
&&
x
.
lf_left
+
1
>=
lineNumber
)
{
x
=
x
.
left
;
}
else
if
(
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
+
1
>=
lineNumber
)
{
leftLen
+=
x
.
size_left
;
// lineNumber >= 2
let
accumualtedValInCurrentIndex
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
2
);
return
leftLen
+=
accumualtedValInCurrentIndex
+
column
-
1
;
}
else
{
lineNumber
-=
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
;
leftLen
+=
x
.
size_left
+
x
.
piece
.
length
;
x
=
x
.
right
;
}
}
return
leftLen
;
}
public
getPositionAt
(
offset
:
number
):
Position
{
let
x
=
this
.
root
;
let
lfCnt
=
0
;
while
(
x
!==
SENTINEL
)
{
if
(
x
.
size_left
!==
0
&&
x
.
size_left
>=
offset
)
{
x
=
x
.
left
;
}
else
if
(
x
.
size_left
+
x
.
piece
.
length
>=
offset
)
{
let
out
=
x
.
piece
.
lineStarts
.
getIndexOf
(
offset
-
x
.
size_left
);
let
column
=
0
;
if
(
out
.
index
===
0
)
{
let
prev
=
x
.
prev
();
if
(
prev
!==
SENTINEL
)
{
let
lineLens
=
prev
.
piece
.
lineStarts
.
values
;
column
+=
lineLens
[
lineLens
.
length
-
1
];
}
}
lfCnt
+=
x
.
lf_left
+
out
.
index
;
return
new
Position
(
lfCnt
+
1
,
column
+
out
.
remainder
+
1
);
}
else
{
offset
-=
x
.
size_left
+
x
.
piece
.
length
;
lfCnt
+=
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
;
x
=
x
.
right
;
}
}
return
null
;
}
public
equals
(
other
:
ITextSource
):
boolean
{
if
(
this
.
_BOM
!==
other
.
BOM
)
{
return
false
;
}
if
(
this
.
_EOL
!==
other
.
EOL
)
{
return
false
;
}
if
(
this
.
getLinesContent
()
!==
(
<
IRawPTBuffer
>
other
.
lines
).
text
)
{
return
false
;
}
return
true
;
}
public
mightContainRTL
():
boolean
{
return
this
.
_mightContainRTL
;
}
public
mightContainNonBasicASCII
():
boolean
{
return
this
.
_mightContainNonBasicASCII
;
}
public
getBOM
():
string
{
return
this
.
_BOM
;
}
public
getEOL
():
string
{
return
this
.
_EOL
;
}
public
setEOL
(
newEOL
:
string
):
void
{
this
.
_EOL
=
newEOL
;
// this._constructLineStarts();
}
public
_applyEdits
(
rawOperations
:
editorCommon
.
IIdentifiedSingleEditOperation
[],
recordTrimAutoWhitespace
:
boolean
):
ApplyEditResult
{
let
mightContainRTL
=
this
.
_mightContainRTL
;
let
mightContainNonBasicASCII
=
this
.
_mightContainNonBasicASCII
;
let
canReduceOperations
=
true
;
let
operations
:
IValidatedEditOperation
[]
=
[];
for
(
let
i
=
0
;
i
<
rawOperations
.
length
;
i
++
)
{
let
op
=
rawOperations
[
i
];
if
(
canReduceOperations
&&
op
.
_isTracked
)
{
canReduceOperations
=
false
;
}
let
validatedRange
=
op
.
range
;
if
(
!
mightContainRTL
&&
op
.
text
)
{
// check if the new inserted text contains RTL
mightContainRTL
=
strings
.
containsRTL
(
op
.
text
);
}
if
(
!
mightContainNonBasicASCII
&&
op
.
text
)
{
mightContainNonBasicASCII
=
!
strings
.
isBasicASCII
(
op
.
text
);
}
operations
[
i
]
=
{
sortIndex
:
i
,
identifier
:
op
.
identifier
,
range
:
validatedRange
,
rangeOffset
:
this
.
getOffsetAt
(
validatedRange
.
getStartPosition
()),
rangeLength
:
this
.
getValueLengthInRange
(
validatedRange
),
lines
:
op
.
text
?
op
.
text
.
split
(
/
\r\n
|
\r
|
\n
/
)
:
null
,
forceMoveMarkers
:
op
.
forceMoveMarkers
,
isAutoWhitespaceEdit
:
op
.
isAutoWhitespaceEdit
||
false
};
}
// Sort operations ascending
operations
.
sort
(
TextBuffer
.
_sortOpsAscending
);
for
(
let
i
=
0
,
count
=
operations
.
length
-
1
;
i
<
count
;
i
++
)
{
let
rangeEnd
=
operations
[
i
].
range
.
getEndPosition
();
let
nextRangeStart
=
operations
[
i
+
1
].
range
.
getStartPosition
();
if
(
nextRangeStart
.
isBefore
(
rangeEnd
))
{
// overlapping ranges
throw
new
Error
(
'
Overlapping ranges are not allowed!
'
);
}
}
if
(
canReduceOperations
)
{
// operations = this._reduceOperations(operations);
}
// Delta encode operations
let
reverseRanges
=
TextBuffer
.
_getInverseEditRanges
(
operations
);
let
newTrimAutoWhitespaceCandidates
:
{
lineNumber
:
number
,
oldContent
:
string
}[]
=
[];
for
(
let
i
=
0
;
i
<
operations
.
length
;
i
++
)
{
let
op
=
operations
[
i
];
let
reverseRange
=
reverseRanges
[
i
];
if
(
recordTrimAutoWhitespace
&&
op
.
isAutoWhitespaceEdit
&&
op
.
range
.
isEmpty
())
{
// Record already the future line numbers that might be auto whitespace removal candidates on next edit
for
(
let
lineNumber
=
reverseRange
.
startLineNumber
;
lineNumber
<=
reverseRange
.
endLineNumber
;
lineNumber
++
)
{
let
currentLineContent
=
''
;
if
(
lineNumber
===
reverseRange
.
startLineNumber
)
{
currentLineContent
=
this
.
getLineContent
(
op
.
range
.
startLineNumber
);
if
(
strings
.
firstNonWhitespaceIndex
(
currentLineContent
)
!==
-
1
)
{
continue
;
}
}
newTrimAutoWhitespaceCandidates
.
push
({
lineNumber
:
lineNumber
,
oldContent
:
currentLineContent
});
}
}
}
let
reverseOperations
:
editorCommon
.
IIdentifiedSingleEditOperation
[]
=
[];
for
(
let
i
=
0
;
i
<
operations
.
length
;
i
++
)
{
let
op
=
operations
[
i
];
let
reverseRange
=
reverseRanges
[
i
];
reverseOperations
[
i
]
=
{
identifier
:
op
.
identifier
,
range
:
reverseRange
,
text
:
this
.
getValueInRange
(
op
.
range
),
forceMoveMarkers
:
op
.
forceMoveMarkers
};
}
this
.
_mightContainRTL
=
mightContainRTL
;
this
.
_mightContainNonBasicASCII
=
mightContainNonBasicASCII
;
const
[
rawContentChanges
,
contentChanges
]
=
this
.
_doApplyEdits
(
operations
);
let
trimAutoWhitespaceLineNumbers
:
number
[]
=
null
;
if
(
recordTrimAutoWhitespace
&&
newTrimAutoWhitespaceCandidates
.
length
>
0
)
{
// sort line numbers auto whitespace removal candidates for next edit descending
newTrimAutoWhitespaceCandidates
.
sort
((
a
,
b
)
=>
b
.
lineNumber
-
a
.
lineNumber
);
trimAutoWhitespaceLineNumbers
=
[];
for
(
let
i
=
0
,
len
=
newTrimAutoWhitespaceCandidates
.
length
;
i
<
len
;
i
++
)
{
let
lineNumber
=
newTrimAutoWhitespaceCandidates
[
i
].
lineNumber
;
if
(
i
>
0
&&
newTrimAutoWhitespaceCandidates
[
i
-
1
].
lineNumber
===
lineNumber
)
{
// Do not have the same line number twice
continue
;
}
let
prevContent
=
newTrimAutoWhitespaceCandidates
[
i
].
oldContent
;
let
lineContent
=
this
.
getLineContent
(
lineNumber
);
if
(
lineContent
.
length
===
0
||
lineContent
===
prevContent
||
strings
.
firstNonWhitespaceIndex
(
lineContent
)
!==
-
1
)
{
continue
;
}
trimAutoWhitespaceLineNumbers
.
push
(
lineNumber
);
}
}
return
new
ApplyEditResult
(
reverseOperations
,
rawContentChanges
,
contentChanges
,
trimAutoWhitespaceLineNumbers
);
}
private
_doApplyEdits
(
operations
:
IValidatedEditOperation
[]):
[
ModelRawChange
[],
IInternalModelContentChange
[]]
{
operations
.
sort
(
TextBuffer
.
_sortOpsDescending
);
let
rawContentChanges
:
ModelRawChange
[]
=
[];
let
contentChanges
:
IInternalModelContentChange
[]
=
[];
// operations are from bottom to top
for
(
let
i
=
0
;
i
<
operations
.
length
;
i
++
)
{
let
op
=
operations
[
i
];
const
startLineNumber
=
op
.
range
.
startLineNumber
;
const
startColumn
=
op
.
range
.
startColumn
;
const
endLineNumber
=
op
.
range
.
endLineNumber
;
const
endColumn
=
op
.
range
.
endColumn
;
if
(
startLineNumber
===
endLineNumber
&&
startColumn
===
endColumn
&&
(
!
op
.
lines
||
op
.
lines
.
length
===
0
))
{
// no-op
continue
;
}
const
deletingLinesCnt
=
endLineNumber
-
startLineNumber
;
const
insertingLinesCnt
=
(
op
.
lines
?
op
.
lines
.
length
-
1
:
0
);
const
editingLinesCnt
=
Math
.
min
(
deletingLinesCnt
,
insertingLinesCnt
);
const
text
=
(
op
.
lines
?
op
.
lines
.
join
(
this
.
getEOL
())
:
''
);
if
(
text
)
{
// replacement
this
.
delete
(
op
.
rangeOffset
,
op
.
rangeLength
);
this
.
insert
(
text
,
op
.
rangeOffset
);
}
else
{
// deletion
this
.
delete
(
op
.
rangeOffset
,
op
.
rangeLength
);
}
for
(
let
j
=
startLineNumber
;
j
<=
startLineNumber
+
editingLinesCnt
;
j
++
)
{
rawContentChanges
.
push
(
new
ModelRawLineChanged
(
j
,
this
.
getLineContent
(
j
))
);
}
if
(
editingLinesCnt
<
deletingLinesCnt
)
{
rawContentChanges
.
push
(
new
ModelRawLinesDeleted
(
startLineNumber
+
editingLinesCnt
+
1
,
endLineNumber
)
);
}
if
(
editingLinesCnt
<
insertingLinesCnt
)
{
let
newLinesContent
:
string
[]
=
[];
for
(
let
j
=
editingLinesCnt
+
1
;
j
<=
insertingLinesCnt
;
j
++
)
{
newLinesContent
.
push
(
op
.
lines
[
j
]);
}
newLinesContent
[
newLinesContent
.
length
-
1
]
=
this
.
getLineContent
(
startLineNumber
+
insertingLinesCnt
-
1
);
rawContentChanges
.
push
(
new
ModelRawLinesInserted
(
startLineNumber
+
editingLinesCnt
+
1
,
startLineNumber
+
insertingLinesCnt
,
newLinesContent
.
join
(
'
\n
'
))
);
}
const
contentChangeRange
=
new
Range
(
startLineNumber
,
startColumn
,
endLineNumber
,
endColumn
);
contentChanges
.
push
({
range
:
contentChangeRange
,
rangeLength
:
op
.
rangeLength
,
text
:
text
,
lines
:
op
.
lines
,
rangeOffset
:
op
.
rangeOffset
,
forceMoveMarkers
:
op
.
forceMoveMarkers
});
}
return
[
rawContentChanges
,
contentChanges
];
}
public
getValueLengthInRange
(
range
:
Range
,
eol
:
editorCommon
.
EndOfLinePreference
=
editorCommon
.
EndOfLinePreference
.
TextDefined
):
number
{
if
(
range
.
isEmpty
())
{
return
0
;
}
if
(
range
.
startLineNumber
===
range
.
endLineNumber
)
{
return
(
range
.
endColumn
-
range
.
startColumn
);
}
let
startOffset
=
this
.
getOffsetAt
(
new
Position
(
range
.
startLineNumber
,
range
.
startColumn
));
let
endOffset
=
this
.
getOffsetAt
(
new
Position
(
range
.
endLineNumber
,
range
.
endColumn
));
return
endOffset
-
startOffset
;
}
public
getLineCharCode
(
lineNumber
:
number
,
index
:
number
):
number
{
return
this
.
getLineContent
(
lineNumber
).
charCodeAt
(
index
);
}
public
getLineLength
(
lineNumber
:
number
):
number
{
return
this
.
getLineContent
(
lineNumber
).
length
;
}
public
getLineMinColumn
(
lineNumber
:
number
):
number
{
return
1
;
}
public
getLineMaxColumn
(
lineNumber
:
number
):
number
{
return
this
.
getLineLength
(
lineNumber
)
+
1
;
}
public
getLineFirstNonWhitespaceColumn
(
lineNumber
:
number
):
number
{
const
result
=
strings
.
firstNonWhitespaceIndex
(
this
.
getLineContent
(
lineNumber
));
if
(
result
===
-
1
)
{
return
0
;
}
return
result
+
1
;
}
public
getLineLastNonWhitespaceColumn
(
lineNumber
:
number
):
number
{
const
result
=
strings
.
lastNonWhitespaceIndex
(
this
.
getLineContent
(
lineNumber
));
if
(
result
===
-
1
)
{
return
0
;
}
return
result
+
2
;
}
public
getRangeAt
(
start
:
number
,
end
:
number
):
Range
{
const
startPosition
=
this
.
getPositionAt
(
start
);
const
endPosition
=
this
.
getPositionAt
(
end
);
return
new
Range
(
startPosition
.
lineNumber
,
startPosition
.
column
,
endPosition
.
lineNumber
,
endPosition
.
column
);
}
// #endregion
// #region Piece Table
insert
(
value
:
string
,
offset
:
number
):
void
{
// todo, validate value and offset.
if
(
this
.
root
!==
SENTINEL
)
{
let
{
node
,
remainder
}
=
this
.
nodeAt
(
offset
);
let
insertPos
=
node
.
piece
.
lineStarts
.
getIndexOf
(
remainder
);
let
nodeOffsetInDocument
=
this
.
offsetOfNode
(
node
);
const
startOffset
=
this
.
_changeBuffer
.
length
;
if
(
!
node
.
piece
.
isOriginalBuffer
&&
(
node
.
piece
.
offset
+
node
.
piece
.
length
===
this
.
_changeBuffer
.
length
)
&&
(
nodeOffsetInDocument
+
node
.
piece
.
length
===
offset
))
{
// append content to this node, we don't want to keep adding node when users simply type in sequence
// unless we want to make the structure immutable
this
.
appendToNode
(
node
,
value
);
}
else
{
if
(
nodeOffsetInDocument
===
offset
)
{
// we are inserting content to the beginning of node
let
nodesToDel
=
[];
if
(
value
.
charCodeAt
(
value
.
length
-
1
)
===
13
)
{
// inserted content ends with \r
if
(
node
!==
SENTINEL
)
{
if
(
this
.
nodeCharCodeAt
(
node
,
0
)
===
10
)
{
// move `\n` forward
value
+=
'
\n
'
;
node
.
piece
.
offset
++
;
node
.
piece
.
length
--
;
node
.
piece
.
lineFeedCnt
--
;
node
.
piece
.
lineStarts
.
removeValues
(
0
,
1
);
// remove the first line, which is empty.
this
.
updateMetadata
(
node
,
-
1
,
-
1
);
if
(
node
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
node
);
}
}
}
}
this
.
_changeBuffer
+=
value
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
value
);
let
newPiece
:
Piece
=
new
Piece
(
false
,
startOffset
,
value
.
length
,
lineFeedCount
,
lineLengths
);
let
newNode
=
this
.
rbInsertLeft
(
node
,
newPiece
);
this
.
fixCRLFWithPrev
(
newNode
);
for
(
let
i
=
0
;
i
<
nodesToDel
.
length
;
i
++
)
{
this
.
rbDelete
(
nodesToDel
[
i
]);
}
}
else
if
(
nodeOffsetInDocument
+
node
.
piece
.
length
>
offset
)
{
let
nodesToDel
=
[];
// we need to split node. Create the new piece first as we are reading current node info before modifying it.
let
newRightPiece
=
new
Piece
(
node
.
piece
.
isOriginalBuffer
,
node
.
piece
.
offset
+
offset
-
nodeOffsetInDocument
,
nodeOffsetInDocument
+
node
.
piece
.
length
-
offset
,
node
.
piece
.
lineFeedCnt
-
insertPos
.
index
,
node
.
piece
.
lineStarts
.
values
);
if
(
value
.
charCodeAt
(
value
.
length
-
1
)
===
13
/** \r */
)
{
let
headOfRight
=
this
.
nodeCharCodeAt
(
node
,
offset
-
nodeOffsetInDocument
);
if
(
headOfRight
===
10
/** \n */
)
{
newRightPiece
.
offset
++
;
newRightPiece
.
length
--
;
newRightPiece
.
lineFeedCnt
--
;
newRightPiece
.
lineStarts
.
removeValues
(
0
,
insertPos
.
index
+
1
);
value
+=
'
\n
'
;
}
else
{
this
.
deletePrefixSumHead
(
newRightPiece
.
lineStarts
,
insertPos
);
}
}
else
{
this
.
deletePrefixSumHead
(
newRightPiece
.
lineStarts
,
insertPos
);
}
// reuse node
if
(
value
.
charCodeAt
(
0
)
===
10
/** \n */
)
{
let
tailOfLeft
=
this
.
nodeCharCodeAt
(
node
,
offset
-
nodeOffsetInDocument
-
1
);
if
(
tailOfLeft
===
13
/** \r */
)
{
let
previousPos
=
node
.
piece
.
lineStarts
.
getIndexOf
(
remainder
-
1
);
this
.
deleteNodeTail
(
node
,
previousPos
);
value
=
'
\r
'
+
value
;
if
(
node
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
node
);
}
}
else
{
this
.
deleteNodeTail
(
node
,
insertPos
);
}
}
else
{
this
.
deleteNodeTail
(
node
,
insertPos
);
}
this
.
_changeBuffer
+=
value
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
value
);
let
newPiece
:
Piece
=
new
Piece
(
false
,
startOffset
,
value
.
length
,
lineFeedCount
,
lineLengths
);
if
(
newRightPiece
.
length
>
0
)
{
this
.
rbInsertRight
(
node
,
newRightPiece
);
}
this
.
rbInsertRight
(
node
,
newPiece
);
for
(
let
i
=
0
;
i
<
nodesToDel
.
length
;
i
++
)
{
this
.
rbDelete
(
nodesToDel
[
i
]);
}
}
else
{
// we are inserting to the right of this node.
if
(
this
.
adjustCarriageReturnFromNext
(
value
,
node
))
{
value
+=
'
\n
'
;
}
this
.
_changeBuffer
+=
value
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
value
);
let
newPiece
:
Piece
=
new
Piece
(
false
,
startOffset
,
value
.
length
,
lineFeedCount
,
lineLengths
);
let
newNode
=
this
.
rbInsertRight
(
node
,
newPiece
);
this
.
fixCRLFWithPrev
(
newNode
);
}
}
}
else
{
// insert new node
const
startOffset
=
this
.
_changeBuffer
.
length
;
this
.
_changeBuffer
+=
value
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
value
);
let
piece
=
new
Piece
(
false
,
startOffset
,
value
.
length
,
lineFeedCount
,
lineLengths
);
this
.
rbInsertLeft
(
null
,
piece
);
}
}
delete
(
offset
:
number
,
cnt
:
number
):
void
{
if
(
cnt
<=
0
)
{
return
;
}
if
(
this
.
root
!==
SENTINEL
)
{
let
startPosition
=
this
.
nodeAt
(
offset
);
let
endPosition
=
this
.
nodeAt
(
offset
+
cnt
);
let
startNode
=
startPosition
.
node
;
let
endNode
=
endPosition
.
node
;
let
length
=
startNode
.
piece
.
length
;
let
startNodeOffsetInDocument
=
this
.
offsetOfNode
(
startNode
);
let
splitPos
=
startNode
.
piece
.
lineStarts
.
getIndexOf
(
offset
-
startNodeOffsetInDocument
);
if
(
startNode
===
endNode
)
{
// deletion falls into one node.
let
endSplitPos
=
startNode
.
piece
.
lineStarts
.
getIndexOf
(
offset
-
startNodeOffsetInDocument
+
cnt
);
if
(
startNodeOffsetInDocument
===
offset
)
{
if
(
cnt
===
length
)
{
// delete node
let
next
=
startNode
.
next
();
this
.
rbDelete
(
startNode
);
this
.
fixCRLFWithPrev
(
next
);
return
;
}
this
.
deleteNodeHead
(
startNode
,
endSplitPos
);
this
.
fixCRLFWithPrev
(
startNode
);
return
;
}
if
(
startNodeOffsetInDocument
+
length
===
offset
+
cnt
)
{
this
.
deleteNodeTail
(
startNode
,
splitPos
);
this
.
fixCRLFWithNext
(
startNode
);
return
;
}
// delete content in the middle, this node will be splitted to nodes
return
this
.
shrinkNode
(
startNode
,
splitPos
,
endSplitPos
);
}
// perform read operations before any write operation.
let
endNodeOffsetInDocument
=
this
.
offsetOfNode
(
endNode
);
// update first touched node
this
.
deleteNodeTail
(
startNode
,
splitPos
);
let
nodesToDel
=
[];
if
(
startNode
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
startNode
);
}
// update last touched node
let
endSplitPos
=
endNode
.
piece
.
lineStarts
.
getIndexOf
(
offset
-
endNodeOffsetInDocument
+
cnt
);
this
.
deleteNodeHead
(
endNode
,
endSplitPos
);
if
(
endNode
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
endNode
);
}
let
secondNode
=
startNode
.
next
();
for
(
let
node
=
secondNode
;
node
!==
SENTINEL
&&
node
!==
endNode
;
node
=
node
.
next
())
{
nodesToDel
.
push
(
node
);
}
let
prev
=
startNode
.
piece
.
length
===
0
?
startNode
.
prev
()
:
startNode
;
for
(
let
i
=
0
;
i
<
nodesToDel
.
length
;
i
++
)
{
this
.
rbDelete
(
nodesToDel
[
i
]);
}
if
(
prev
!==
SENTINEL
)
{
this
.
fixCRLFWithNext
(
prev
);
}
}
}
// #region node operations
deleteNodeHead
(
node
:
TreeNode
,
pos
?:
PrefixSumIndexOfResult
)
{
// it's okay to delete CR in CRLF.
let
cnt
=
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
pos
.
index
-
1
)
+
pos
.
remainder
;
node
.
piece
.
length
-=
cnt
;
node
.
piece
.
offset
+=
cnt
;
node
.
piece
.
lineFeedCnt
-=
pos
.
index
;
this
.
deletePrefixSumHead
(
node
.
piece
.
lineStarts
,
pos
);
this
.
updateMetadata
(
node
,
-
cnt
,
-
pos
.
index
);
}
deleteNodeTail
(
node
:
TreeNode
,
start
:
PrefixSumIndexOfResult
)
{
let
cnt
=
node
.
piece
.
length
-
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
start
.
index
-
1
)
-
start
.
remainder
;
let
hitCRLF
=
this
.
hitTestCRLF
(
node
,
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
start
.
index
-
1
)
+
start
.
remainder
,
start
);
node
.
piece
.
length
-=
cnt
;
let
lf_delta
=
start
.
index
-
node
.
piece
.
lineFeedCnt
;
node
.
piece
.
lineFeedCnt
=
start
.
index
;
this
.
deletePrefixSumTail
(
node
.
piece
.
lineStarts
,
start
);
if
(
hitCRLF
)
{
node
.
piece
.
lineFeedCnt
+=
1
;
lf_delta
+=
1
;
node
.
piece
.
lineStarts
.
insertValues
(
node
.
piece
.
lineStarts
.
values
.
length
,
new
Uint32Array
(
1
)
/*[0]*/
);
}
this
.
updateMetadata
(
node
,
-
cnt
,
lf_delta
);
}
// remove start-end from node.
shrinkNode
(
node
:
TreeNode
,
start
:
PrefixSumIndexOfResult
,
end
?:
PrefixSumIndexOfResult
)
{
// read operation first
let
oldLineLengthsVal
=
node
.
piece
.
lineStarts
.
values
;
let
offset
=
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
start
.
index
-
1
)
+
start
.
remainder
;
let
endOffset
=
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
end
.
index
-
1
)
+
end
.
remainder
;
// write.
let
startHitCRLF
=
this
.
hitTestCRLF
(
node
,
offset
,
start
);
let
nodeOldLength
=
node
.
piece
.
length
;
node
.
piece
.
length
=
offset
;
let
lf_delta
=
start
.
index
-
node
.
piece
.
lineFeedCnt
;
node
.
piece
.
lineFeedCnt
=
start
.
index
;
node
.
piece
.
lineStarts
=
new
PrefixSumComputer
(
oldLineLengthsVal
.
slice
(
0
,
start
.
index
+
1
));
node
.
piece
.
lineStarts
.
changeValue
(
start
.
index
,
start
.
remainder
);
if
(
startHitCRLF
)
{
node
.
piece
.
lineFeedCnt
+=
1
;
lf_delta
+=
1
;
node
.
piece
.
lineStarts
.
insertValues
(
node
.
piece
.
lineStarts
.
values
.
length
,
new
Uint32Array
(
1
)
/*[0]*/
);
}
this
.
updateMetadata
(
node
,
offset
-
nodeOldLength
,
lf_delta
);
let
newPieceLength
=
nodeOldLength
-
endOffset
;
if
(
newPieceLength
<=
0
)
{
return
;
}
let
newPiece
:
Piece
=
new
Piece
(
node
.
piece
.
isOriginalBuffer
,
endOffset
+
node
.
piece
.
offset
,
newPieceLength
,
oldLineLengthsVal
.
length
-
end
.
index
-
1
,
oldLineLengthsVal
.
slice
(
end
.
index
)
);
newPiece
.
lineStarts
.
changeValue
(
0
,
newPiece
.
lineStarts
.
values
[
0
]
-
end
.
remainder
);
let
newNode
=
this
.
rbInsertRight
(
node
,
newPiece
);
this
.
fixCRLFWithPrev
(
newNode
);
}
appendToNode
(
node
:
TreeNode
,
value
:
string
):
void
{
if
(
this
.
adjustCarriageReturnFromNext
(
value
,
node
))
{
value
+=
'
\n
'
;
}
let
hitCRLF
=
value
.
charCodeAt
(
0
)
===
10
&&
this
.
nodeCharCodeAt
(
node
,
node
.
piece
.
length
-
1
)
===
13
;
this
.
_changeBuffer
+=
value
;
node
.
piece
.
length
+=
value
.
length
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
value
);
let
lf_delta
=
lineFeedCount
;
if
(
hitCRLF
)
{
node
.
piece
.
lineFeedCnt
+=
lineFeedCount
-
1
;
lf_delta
--
;
let
lineStarts
=
node
.
piece
.
lineStarts
;
lineStarts
.
removeValues
(
lineStarts
.
values
.
length
-
1
,
1
);
lineStarts
.
changeValue
(
lineStarts
.
values
.
length
-
1
,
lineStarts
.
values
[
lineStarts
.
values
.
length
-
1
]
+
1
);
lineStarts
.
insertValues
(
lineStarts
.
values
.
length
,
lineLengths
.
slice
(
1
));
}
else
{
node
.
piece
.
lineFeedCnt
+=
lineFeedCount
;
let
lineStarts
=
node
.
piece
.
lineStarts
;
lineStarts
.
changeValue
(
lineStarts
.
values
.
length
-
1
,
lineStarts
.
values
[
lineStarts
.
values
.
length
-
1
]
+
lineLengths
[
0
]);
lineStarts
.
insertValues
(
lineStarts
.
values
.
length
,
lineLengths
.
slice
(
1
));
}
this
.
updateMetadata
(
node
,
value
.
length
,
lf_delta
);
}
nodeAt
(
offset
:
number
):
BufferCursor
{
let
x
=
this
.
root
;
while
(
x
!==
SENTINEL
)
{
if
(
x
.
size_left
>
offset
)
{
x
=
x
.
left
;
}
else
if
(
x
.
size_left
+
x
.
piece
.
length
>=
offset
)
{
return
{
node
:
x
,
remainder
:
offset
-
x
.
size_left
};
}
else
{
offset
-=
x
.
size_left
+
x
.
piece
.
length
;
x
=
x
.
right
;
}
}
return
null
;
}
nodeAt2
(
position
:
Position
):
BufferCursor
{
let
x
=
this
.
root
;
let
lineNumber
=
position
.
lineNumber
;
let
column
=
position
.
column
;
while
(
x
!==
SENTINEL
)
{
if
(
x
.
left
!==
SENTINEL
&&
x
.
lf_left
>=
lineNumber
-
1
)
{
x
=
x
.
left
;
}
else
if
(
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
>
lineNumber
-
1
)
{
let
prevAccumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
2
);
let
accumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
1
);
return
{
node
:
x
,
remainder
:
Math
.
min
(
prevAccumualtedValue
+
column
-
1
,
accumualtedValue
)
};
}
else
if
(
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
===
lineNumber
-
1
)
{
let
prevAccumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
lineNumber
-
x
.
lf_left
-
2
);
if
(
prevAccumualtedValue
+
column
-
1
<=
x
.
piece
.
length
)
{
return
{
node
:
x
,
remainder
:
prevAccumualtedValue
+
column
-
1
};
}
else
{
column
-=
x
.
piece
.
length
-
prevAccumualtedValue
;
break
;
}
}
else
{
lineNumber
-=
x
.
lf_left
+
x
.
piece
.
lineFeedCnt
;
x
=
x
.
right
;
}
}
// search in order, to find the node contains position.column
x
=
x
.
next
();
while
(
x
!==
SENTINEL
)
{
if
(
x
.
piece
.
lineFeedCnt
>
0
)
{
let
accumualtedValue
=
x
.
piece
.
lineStarts
.
getAccumulatedValue
(
0
);
return
{
node
:
x
,
remainder
:
Math
.
min
(
column
-
1
,
accumualtedValue
)
};
}
else
{
if
(
x
.
piece
.
length
>=
column
-
1
)
{
return
{
node
:
x
,
remainder
:
column
-
1
};
}
else
{
column
-=
x
.
piece
.
length
;
}
}
x
=
x
.
next
();
}
return
null
;
}
offsetOfNode
(
node
:
TreeNode
):
number
{
if
(
!
node
)
{
return
0
;
}
let
pos
=
node
.
size_left
;
while
(
node
!==
this
.
root
)
{
if
(
node
.
parent
.
right
===
node
)
{
pos
+=
node
.
parent
.
size_left
+
node
.
parent
.
piece
.
length
;
}
node
=
node
.
parent
;
}
return
pos
;
}
getNodeContent
(
node
:
TreeNode
):
string
{
let
buffer
=
node
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
let
currentContent
=
buffer
.
substr
(
node
.
piece
.
offset
,
node
.
piece
.
length
);
return
currentContent
;
}
deletePrefixSumTail
(
prefixSum
:
PrefixSumComputer
,
position
:
PrefixSumIndexOfResult
):
void
{
prefixSum
.
removeValues
(
position
.
index
+
1
,
prefixSum
.
values
.
length
-
position
.
index
-
1
);
prefixSum
.
changeValue
(
position
.
index
,
position
.
remainder
);
}
deletePrefixSumHead
(
prefixSum
:
PrefixSumComputer
,
position
:
PrefixSumIndexOfResult
):
void
{
prefixSum
.
changeValue
(
position
.
index
,
prefixSum
.
values
[
position
.
index
]
-
position
.
remainder
);
if
(
position
.
index
>
0
)
{
prefixSum
.
removeValues
(
0
,
position
.
index
);
}
}
// #endregion
// #region CRLF
hitTestCRLF
(
node
:
TreeNode
,
offset
:
number
,
position
:
PrefixSumIndexOfResult
)
{
if
(
node
.
piece
.
lineFeedCnt
<
1
)
{
return
false
;
}
let
currentLineLen
=
node
.
piece
.
lineStarts
.
getAccumulatedValue
(
position
.
index
);
if
(
offset
===
currentLineLen
-
1
)
{
// charCodeAt becomes slow (single or even two digits ms) when the changed buffer is long
return
this
.
nodeCharCodeAt
(
node
,
offset
-
1
)
===
13
/* \r */
&&
this
.
nodeCharCodeAt
(
node
,
offset
)
===
10
/* \n */
;
}
return
false
;
}
fixCRLFWithPrev
(
nextNode
:
TreeNode
)
{
if
(
nextNode
===
SENTINEL
||
nextNode
.
piece
.
lineFeedCnt
===
0
)
{
return
;
}
if
(
nextNode
.
piece
.
lineStarts
.
getAccumulatedValue
(
0
)
!==
1
/* if it's \n, the first line is 1 char */
)
{
return
;
}
if
(
this
.
nodeCharCodeAt
(
nextNode
,
0
)
===
10
/* \n */
)
{
let
node
=
nextNode
.
prev
();
if
(
node
===
SENTINEL
||
node
.
piece
.
lineFeedCnt
===
0
)
{
return
;
}
if
(
this
.
nodeCharCodeAt
(
node
,
node
.
piece
.
length
-
1
)
===
13
)
{
this
.
fixCRLF
(
node
,
nextNode
);
}
}
}
fixCRLFWithNext
(
node
:
TreeNode
)
{
if
(
node
===
SENTINEL
)
{
return
;
}
if
(
this
.
nodeCharCodeAt
(
node
,
node
.
piece
.
length
-
1
)
===
13
/* \r */
)
{
let
nextNode
=
node
.
next
();
if
(
nextNode
!==
SENTINEL
&&
this
.
nodeCharCodeAt
(
nextNode
,
0
)
===
10
/* \n */
)
{
this
.
fixCRLF
(
node
,
nextNode
);
}
}
}
fixCRLF
(
prev
:
TreeNode
,
next
:
TreeNode
)
{
let
nodesToDel
=
[];
// update node
prev
.
piece
.
length
-=
1
;
prev
.
piece
.
lineFeedCnt
-=
1
;
let
lineStarts
=
prev
.
piece
.
lineStarts
;
// lineStarts.values.length >= 2 due to a `\r`
lineStarts
.
removeValues
(
lineStarts
.
values
.
length
-
1
,
1
);
lineStarts
.
changeValue
(
lineStarts
.
values
.
length
-
1
,
lineStarts
.
values
[
lineStarts
.
values
.
length
-
1
]
-
1
);
this
.
updateMetadata
(
prev
,
-
1
,
-
1
);
if
(
prev
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
prev
);
}
// update nextNode
next
.
piece
.
length
-=
1
;
next
.
piece
.
offset
+=
1
;
next
.
piece
.
lineFeedCnt
-=
1
;
lineStarts
=
next
.
piece
.
lineStarts
;
lineStarts
.
removeValues
(
0
,
1
);
this
.
updateMetadata
(
next
,
-
1
,
-
1
);
if
(
next
.
piece
.
length
===
0
)
{
nodesToDel
.
push
(
next
);
}
// create new piece which contains \r\n
let
startOffset
=
this
.
_changeBuffer
.
length
;
this
.
_changeBuffer
+=
'
\r\n
'
;
const
{
lineFeedCount
,
lineLengths
}
=
this
.
calculateNewLineCount
(
'
\r\n
'
);
let
piece
=
new
Piece
(
false
,
startOffset
,
2
,
lineFeedCount
,
lineLengths
);
this
.
rbInsertRight
(
prev
,
piece
);
// delete empty nodes
for
(
let
i
=
0
;
i
<
nodesToDel
.
length
;
i
++
)
{
this
.
rbDelete
(
nodesToDel
[
i
]);
}
}
adjustCarriageReturnFromNext
(
value
:
string
,
node
:
TreeNode
):
boolean
{
if
(
value
.
charCodeAt
(
value
.
length
-
1
)
===
13
)
{
// inserted content ends with \r
let
nextNode
=
node
.
next
();
if
(
nextNode
!==
SENTINEL
)
{
if
(
this
.
nodeCharCodeAt
(
nextNode
,
0
)
===
10
)
{
// move `\n` forward
value
+=
'
\n
'
;
if
(
nextNode
.
piece
.
length
===
1
)
{
this
.
rbDelete
(
nextNode
);
}
else
{
nextNode
.
piece
.
offset
+=
1
;
nextNode
.
piece
.
length
-=
1
;
nextNode
.
piece
.
lineFeedCnt
-=
1
;
nextNode
.
piece
.
lineStarts
.
removeValues
(
0
,
1
);
// remove the first line, which is empty.
this
.
updateMetadata
(
nextNode
,
-
1
,
-
1
);
}
return
true
;
}
}
}
return
false
;
}
nodeCharCodeAt
(
node
:
TreeNode
,
offset
:
number
):
number
{
if
(
node
.
piece
.
lineFeedCnt
<
1
)
{
return
-
1
;
}
let
buffer
=
node
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
return
buffer
.
charCodeAt
(
node
.
piece
.
offset
+
offset
);
}
// #endregion
// #endregion
// #region Red Black Tree
leftRotate
(
x
:
TreeNode
)
{
let
y
=
x
.
right
;
// fix size_left
y
.
size_left
+=
x
.
size_left
+
(
x
.
piece
?
x
.
piece
.
length
:
0
);
y
.
lf_left
+=
x
.
lf_left
+
(
x
.
piece
?
x
.
piece
.
lineFeedCnt
:
0
);
x
.
right
=
y
.
left
;
if
(
y
.
left
!==
SENTINEL
)
{
y
.
left
.
parent
=
x
;
}
y
.
parent
=
x
.
parent
;
if
(
x
.
parent
===
SENTINEL
)
{
this
.
root
=
y
;
}
else
if
(
x
.
parent
.
left
===
x
)
{
x
.
parent
.
left
=
y
;
}
else
{
x
.
parent
.
right
=
y
;
}
y
.
left
=
x
;
x
.
parent
=
y
;
}
rightRotate
(
y
:
TreeNode
)
{
let
x
=
y
.
left
;
y
.
left
=
x
.
right
;
if
(
x
.
right
!==
SENTINEL
)
{
x
.
right
.
parent
=
y
;
}
x
.
parent
=
y
.
parent
;
// fix size_left
y
.
size_left
-=
x
.
size_left
+
(
x
.
piece
?
x
.
piece
.
length
:
0
);
y
.
lf_left
-=
x
.
lf_left
+
(
x
.
piece
?
x
.
piece
.
lineFeedCnt
:
0
);
if
(
y
.
parent
===
SENTINEL
)
{
this
.
root
=
x
;
}
else
if
(
y
===
y
.
parent
.
right
)
{
y
.
parent
.
right
=
x
;
}
else
{
y
.
parent
.
left
=
x
;
}
x
.
right
=
y
;
y
.
parent
=
x
;
}
/**
* node node
* / \ / \
* a b <---- a b
* /
* z
*/
rbInsertRight
(
node
:
TreeNode
,
p
:
Piece
):
TreeNode
{
let
z
=
new
TreeNode
(
p
,
NodeColor
.
Red
);
z
.
left
=
SENTINEL
;
z
.
right
=
SENTINEL
;
z
.
parent
=
SENTINEL
;
z
.
size_left
=
0
;
z
.
lf_left
=
0
;
let
x
=
this
.
root
;
if
(
x
===
SENTINEL
)
{
this
.
root
=
z
;
setNodeColor
(
z
,
NodeColor
.
Black
);
}
else
if
(
node
.
right
===
SENTINEL
)
{
node
.
right
=
z
;
z
.
parent
=
node
;
}
else
{
let
nextNode
=
leftest
(
node
.
right
);
nextNode
.
left
=
z
;
z
.
parent
=
nextNode
;
}
this
.
fixInsert
(
z
);
return
z
;
}
/**
* node node
* / \ / \
* a b ----> a b
* \
* z
*/
rbInsertLeft
(
node
:
TreeNode
,
p
:
Piece
):
TreeNode
{
let
z
=
new
TreeNode
(
p
,
NodeColor
.
Red
);
z
.
left
=
SENTINEL
;
z
.
right
=
SENTINEL
;
z
.
parent
=
SENTINEL
;
z
.
size_left
=
0
;
z
.
lf_left
=
0
;
let
x
=
this
.
root
;
if
(
x
===
SENTINEL
)
{
this
.
root
=
z
;
setNodeColor
(
z
,
NodeColor
.
Black
);
}
else
if
(
node
.
left
===
SENTINEL
)
{
node
.
left
=
z
;
z
.
parent
=
node
;
}
else
{
let
prevNode
=
righttest
(
node
.
left
);
// a
prevNode
.
right
=
z
;
z
.
parent
=
prevNode
;
}
this
.
fixInsert
(
z
);
return
z
;
}
rbDelete
(
z
:
TreeNode
)
{
let
x
:
TreeNode
;
let
y
:
TreeNode
;
if
(
z
.
left
===
SENTINEL
)
{
y
=
z
;
x
=
y
.
right
;
}
else
if
(
z
.
right
===
SENTINEL
)
{
y
=
z
;
x
=
y
.
left
;
}
else
{
y
=
leftest
(
z
.
right
);
x
=
y
.
right
;
}
if
(
y
===
this
.
root
)
{
this
.
root
=
x
;
// if x is null, we are removing the only node
setNodeColor
(
x
,
NodeColor
.
Black
);
z
.
detach
();
resetSentinel
();
this
.
root
.
parent
=
SENTINEL
;
return
;
}
let
yWasRed
=
(
getNodeColor
(
y
)
===
NodeColor
.
Red
);
if
(
y
===
y
.
parent
.
left
)
{
y
.
parent
.
left
=
x
;
}
else
{
y
.
parent
.
right
=
x
;
}
if
(
y
===
z
)
{
x
.
parent
=
y
.
parent
;
this
.
recomputeMetadata
(
x
);
}
else
{
if
(
y
.
parent
===
z
)
{
x
.
parent
=
y
;
}
else
{
x
.
parent
=
y
.
parent
;
}
// as we make changes to x's hierarchy, update size_left of subtree first
this
.
recomputeMetadata
(
x
);
y
.
left
=
z
.
left
;
y
.
right
=
z
.
right
;
y
.
parent
=
z
.
parent
;
setNodeColor
(
y
,
getNodeColor
(
z
));
if
(
z
===
this
.
root
)
{
this
.
root
=
y
;
}
else
{
if
(
z
===
z
.
parent
.
left
)
{
z
.
parent
.
left
=
y
;
}
else
{
z
.
parent
.
right
=
y
;
}
}
if
(
y
.
left
!==
SENTINEL
)
{
y
.
left
.
parent
=
y
;
}
if
(
y
.
right
!==
SENTINEL
)
{
y
.
right
.
parent
=
y
;
}
// update metadata
// we replace z with y, so in this sub tree, the length change is z.item.length
y
.
size_left
=
z
.
size_left
;
y
.
lf_left
=
z
.
lf_left
;
this
.
recomputeMetadata
(
y
);
}
z
.
detach
();
if
(
x
.
parent
.
left
===
x
)
{
let
newSizeLeft
=
calculateSize
(
x
);
let
newLFLeft
=
calculateLF
(
x
);
if
(
newSizeLeft
!==
x
.
parent
.
size_left
||
newLFLeft
!==
x
.
parent
.
lf_left
)
{
let
delta
=
newSizeLeft
-
x
.
parent
.
size_left
;
let
lf_delta
=
newLFLeft
-
x
.
parent
.
lf_left
;
x
.
parent
.
size_left
=
newSizeLeft
;
x
.
parent
.
lf_left
=
newLFLeft
;
this
.
updateMetadata
(
x
.
parent
,
delta
,
lf_delta
);
}
}
this
.
recomputeMetadata
(
x
.
parent
);
if
(
yWasRed
)
{
resetSentinel
();
return
;
}
// RB-DELETE-FIXUP
let
w
:
TreeNode
;
while
(
x
!==
this
.
root
&&
getNodeColor
(
x
)
===
NodeColor
.
Black
)
{
if
(
x
===
x
.
parent
.
left
)
{
w
=
x
.
parent
.
right
;
if
(
getNodeColor
(
w
)
===
NodeColor
.
Red
)
{
setNodeColor
(
w
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
,
NodeColor
.
Red
);
this
.
leftRotate
(
x
.
parent
);
w
=
x
.
parent
.
right
;
}
if
(
getNodeColor
(
w
.
left
)
===
NodeColor
.
Black
&&
getNodeColor
(
w
.
right
)
===
NodeColor
.
Black
)
{
setNodeColor
(
w
,
NodeColor
.
Red
);
x
=
x
.
parent
;
}
else
{
if
(
getNodeColor
(
w
.
right
)
===
NodeColor
.
Black
)
{
setNodeColor
(
w
.
left
,
NodeColor
.
Black
);
setNodeColor
(
w
,
NodeColor
.
Red
);
this
.
rightRotate
(
w
);
w
=
x
.
parent
.
right
;
}
setNodeColor
(
w
,
getNodeColor
(
x
.
parent
));
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
w
.
right
,
NodeColor
.
Black
);
this
.
leftRotate
(
x
.
parent
);
x
=
this
.
root
;
}
}
else
{
w
=
x
.
parent
.
left
;
if
(
getNodeColor
(
w
)
===
NodeColor
.
Red
)
{
setNodeColor
(
w
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
,
NodeColor
.
Red
);
this
.
rightRotate
(
x
.
parent
);
w
=
x
.
parent
.
left
;
}
if
(
getNodeColor
(
w
.
left
)
===
NodeColor
.
Black
&&
getNodeColor
(
w
.
right
)
===
NodeColor
.
Black
)
{
setNodeColor
(
w
,
NodeColor
.
Red
);
x
=
x
.
parent
;
}
else
{
if
(
getNodeColor
(
w
.
left
)
===
NodeColor
.
Black
)
{
setNodeColor
(
w
.
right
,
NodeColor
.
Black
);
setNodeColor
(
w
,
NodeColor
.
Red
);
this
.
leftRotate
(
w
);
w
=
x
.
parent
.
left
;
}
setNodeColor
(
w
,
getNodeColor
(
x
.
parent
));
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
w
.
left
,
NodeColor
.
Black
);
this
.
rightRotate
(
x
.
parent
);
x
=
this
.
root
;
}
}
}
setNodeColor
(
x
,
NodeColor
.
Black
);
resetSentinel
();
}
fixInsert
(
x
:
TreeNode
)
{
this
.
recomputeMetadata
(
x
);
while
(
x
!==
this
.
root
&&
getNodeColor
(
x
.
parent
)
===
NodeColor
.
Red
)
{
if
(
x
.
parent
===
x
.
parent
.
parent
.
left
)
{
const
y
=
x
.
parent
.
parent
.
right
;
if
(
getNodeColor
(
y
)
===
NodeColor
.
Red
)
{
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
y
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
.
parent
,
NodeColor
.
Red
);
x
=
x
.
parent
.
parent
;
}
else
{
if
(
x
===
x
.
parent
.
right
)
{
x
=
x
.
parent
;
this
.
leftRotate
(
x
);
}
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
.
parent
,
NodeColor
.
Red
);
this
.
rightRotate
(
x
.
parent
.
parent
);
}
}
else
{
const
y
=
x
.
parent
.
parent
.
left
;
if
(
getNodeColor
(
y
)
===
NodeColor
.
Red
)
{
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
y
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
.
parent
,
NodeColor
.
Red
);
x
=
x
.
parent
.
parent
;
}
else
{
if
(
x
===
x
.
parent
.
left
)
{
x
=
x
.
parent
;
this
.
rightRotate
(
x
);
}
setNodeColor
(
x
.
parent
,
NodeColor
.
Black
);
setNodeColor
(
x
.
parent
.
parent
,
NodeColor
.
Red
);
this
.
leftRotate
(
x
.
parent
.
parent
);
}
}
}
setNodeColor
(
this
.
root
,
NodeColor
.
Black
);
}
updateMetadata
(
x
:
TreeNode
,
delta
:
number
,
lineFeedCntDelta
:
number
):
void
{
// node length change, we need to update the roots of all subtrees containing this node.
while
(
x
!==
this
.
root
&&
x
!==
SENTINEL
)
{
if
(
x
.
parent
.
left
===
x
)
{
x
.
parent
.
size_left
+=
delta
;
x
.
parent
.
lf_left
+=
lineFeedCntDelta
;
}
x
=
x
.
parent
;
}
}
recomputeMetadata
(
x
:
TreeNode
)
{
let
delta
=
0
;
let
lf_delta
=
0
;
if
(
x
===
this
.
root
)
{
return
;
}
if
(
delta
===
0
)
{
// go upwards till the node whose left subtree is changed.
while
(
x
!==
this
.
root
&&
x
===
x
.
parent
.
right
)
{
x
=
x
.
parent
;
}
if
(
x
===
this
.
root
)
{
// well, it means we add a node to the end (inorder)
return
;
}
// x is the node whose right subtree is changed.
x
=
x
.
parent
;
delta
=
calculateSize
(
x
.
left
)
-
x
.
size_left
;
lf_delta
=
calculateLF
(
x
.
left
)
-
x
.
lf_left
;
x
.
size_left
+=
delta
;
x
.
lf_left
+=
lf_delta
;
}
// go upwards till root. O(logN)
while
(
x
!==
this
.
root
&&
(
delta
!==
0
||
lf_delta
!==
0
))
{
if
(
x
.
parent
.
left
===
x
)
{
x
.
parent
.
size_left
+=
delta
;
x
.
parent
.
lf_left
+=
lf_delta
;
}
x
=
x
.
parent
;
}
}
getContentOfSubTree
(
node
:
TreeNode
):
string
{
if
(
node
===
SENTINEL
)
{
return
''
;
}
let
buffer
=
node
.
piece
.
isOriginalBuffer
?
this
.
_originalBuffer
:
this
.
_changeBuffer
;
let
currentContent
=
buffer
.
substr
(
node
.
piece
.
offset
,
node
.
piece
.
length
);
return
this
.
getContentOfSubTree
(
node
.
left
)
+
currentContent
+
this
.
getContentOfSubTree
(
node
.
right
);
}
calculateNewLineCount
(
chunk
:
string
):
{
lineFeedCount
:
number
,
lineLengths
:
Uint32Array
}
{
let
lineStarts
=
[
0
];
// Reset regex to search from the beginning
this
.
_regex
.
lastIndex
=
0
;
let
prevMatchStartIndex
=
-
1
;
let
prevMatchLength
=
0
;
let
m
:
RegExpExecArray
;
do
{
if
(
prevMatchStartIndex
+
prevMatchLength
===
chunk
.
length
)
{
// Reached the end of the line
break
;
}
m
=
this
.
_regex
.
exec
(
chunk
);
if
(
!
m
)
{
break
;
}
const
matchStartIndex
=
m
.
index
;
const
matchLength
=
m
[
0
].
length
;
if
(
matchStartIndex
===
prevMatchStartIndex
&&
matchLength
===
prevMatchLength
)
{
// Exit early if the regex matches the same range twice
break
;
}
prevMatchStartIndex
=
matchStartIndex
;
prevMatchLength
=
matchLength
;
lineStarts
.
push
(
matchStartIndex
+
matchLength
);
}
while
(
m
);
const
lineLengths
=
new
Uint32Array
(
lineStarts
.
length
);
for
(
let
i
=
1
;
i
<
lineStarts
.
length
;
i
++
)
{
lineLengths
[
i
-
1
]
=
lineStarts
[
i
]
-
lineStarts
[
i
-
1
];
}
lineLengths
[
lineStarts
.
length
-
1
]
=
chunk
.
length
-
lineStarts
[
lineStarts
.
length
-
1
];
return
{
lineFeedCount
:
lineLengths
.
length
-
1
,
lineLengths
:
lineLengths
};
}
// #endregion
// #region helper
/**
* Assumes `operations` are validated and sorted ascending
*/
public
static
_getInverseEditRanges
(
operations
:
IValidatedEditOperation
[]):
Range
[]
{
let
result
:
Range
[]
=
[];
let
prevOpEndLineNumber
:
number
;
let
prevOpEndColumn
:
number
;
let
prevOp
:
IValidatedEditOperation
=
null
;
for
(
let
i
=
0
,
len
=
operations
.
length
;
i
<
len
;
i
++
)
{
let
op
=
operations
[
i
];
let
startLineNumber
:
number
;
let
startColumn
:
number
;
if
(
prevOp
)
{
if
(
prevOp
.
range
.
endLineNumber
===
op
.
range
.
startLineNumber
)
{
startLineNumber
=
prevOpEndLineNumber
;
startColumn
=
prevOpEndColumn
+
(
op
.
range
.
startColumn
-
prevOp
.
range
.
endColumn
);
}
else
{
startLineNumber
=
prevOpEndLineNumber
+
(
op
.
range
.
startLineNumber
-
prevOp
.
range
.
endLineNumber
);
startColumn
=
op
.
range
.
startColumn
;
}
}
else
{
startLineNumber
=
op
.
range
.
startLineNumber
;
startColumn
=
op
.
range
.
startColumn
;
}
let
resultRange
:
Range
;
if
(
op
.
lines
&&
op
.
lines
.
length
>
0
)
{
// the operation inserts something
let
lineCount
=
op
.
lines
.
length
;
let
firstLine
=
op
.
lines
[
0
];
let
lastLine
=
op
.
lines
[
lineCount
-
1
];
if
(
lineCount
===
1
)
{
// single line insert
resultRange
=
new
Range
(
startLineNumber
,
startColumn
,
startLineNumber
,
startColumn
+
firstLine
.
length
);
}
else
{
// multi line insert
resultRange
=
new
Range
(
startLineNumber
,
startColumn
,
startLineNumber
+
lineCount
-
1
,
lastLine
.
length
+
1
);
}
}
else
{
// There is nothing to insert
resultRange
=
new
Range
(
startLineNumber
,
startColumn
,
startLineNumber
,
startColumn
);
}
prevOpEndLineNumber
=
resultRange
.
endLineNumber
;
prevOpEndColumn
=
resultRange
.
endColumn
;
result
.
push
(
resultRange
);
prevOp
=
op
;
}
return
result
;
}
private
static
_sortOpsAscending
(
a
:
IValidatedEditOperation
,
b
:
IValidatedEditOperation
):
number
{
let
r
=
Range
.
compareRangesUsingEnds
(
a
.
range
,
b
.
range
);
if
(
r
===
0
)
{
return
a
.
sortIndex
-
b
.
sortIndex
;
}
return
r
;
}
private
static
_sortOpsDescending
(
a
:
IValidatedEditOperation
,
b
:
IValidatedEditOperation
):
number
{
let
r
=
Range
.
compareRangesUsingEnds
(
a
.
range
,
b
.
range
);
if
(
r
===
0
)
{
return
b
.
sortIndex
-
a
.
sortIndex
;
}
return
-
r
;
}
// #endregion
}
\ No newline at end of file
src/vs/editor/common/model/textModel.ts
浏览文件 @
f4c8522d
...
...
@@ -10,13 +10,14 @@ import { Position, IPosition } from 'vs/editor/common/core/position';
import
{
Range
,
IRange
}
from
'
vs/editor/common/core/range
'
;
import
{
Selection
}
from
'
vs/editor/common/core/selection
'
;
import
*
as
editorCommon
from
'
vs/editor/common/editorCommon
'
;
import
{
guessIndentation
,
IndentationGuesserTextBufferTarget
,
IndentationGuesserStringArrayTarget
}
from
'
vs/editor/common/model/indentationGuesser
'
;
import
{
guessIndentation
,
IndentationGuesserTextBufferTarget
,
IndentationGuesserStringArrayTarget
,
IndentationGuesserRawTextBufferTarget
}
from
'
vs/editor/common/model/indentationGuesser
'
;
import
{
EDITOR_MODEL_DEFAULTS
}
from
'
vs/editor/common/config/editorOptions
'
;
import
{
TextModelSearch
,
SearchParams
}
from
'
vs/editor/common/model/textModelSearch
'
;
import
{
TextSource
,
ITextSource
,
IRawTextSource
,
RawTextSource
}
from
'
vs/editor/common/model/textSource
'
;
import
{
IModelContentChangedEvent
,
ModelRawContentChangedEvent
,
ModelRawFlush
,
ModelRawEOLChanged
,
IModelOptionsChangedEvent
,
InternalModelContentChangeEvent
}
from
'
vs/editor/common/model/textModelEvents
'
;
import
{
Disposable
,
IDisposable
}
from
'
vs/base/common/lifecycle
'
;
import
{
TextBuffer
}
from
'
vs/editor/common/model/textBuffer
'
;
// import { TextBuffer } from 'vs/editor/common/model/textBuffer';
import
{
TextBuffer
}
from
'
vs/editor/common/model/textBuffer2
'
;
const
LIMIT_FIND_COUNT
=
999
;
export
const
LONG_LINE_BOUNDARY
=
10000
;
...
...
@@ -48,7 +49,13 @@ export class TextModel extends Disposable implements editorCommon.ITextModel {
let
resolvedOpts
:
editorCommon
.
TextModelResolvedOptions
;
if
(
options
.
detectIndentation
)
{
const
guessedIndentation
=
guessIndentation
(
new
IndentationGuesserStringArrayTarget
(
textSource
.
lines
),
options
.
tabSize
,
options
.
insertSpaces
);
const
guessedIndentation
=
guessIndentation
(
Array
.
isArray
(
textSource
.
lines
)
?
new
IndentationGuesserStringArrayTarget
(
textSource
.
lines
)
:
new
IndentationGuesserRawTextBufferTarget
(
textSource
.
lines
),
options
.
tabSize
,
options
.
insertSpaces
);
resolvedOpts
=
new
editorCommon
.
TextModelResolvedOptions
({
tabSize
:
guessedIndentation
.
tabSize
,
insertSpaces
:
guessedIndentation
.
insertSpaces
,
...
...
@@ -412,7 +419,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel {
public
getLinesContent
():
string
[]
{
this
.
_assertNotDisposed
();
return
this
.
_buffer
.
getLinesContent
();
return
this
.
_buffer
.
getLinesContent
()
.
split
(
/
\r\n
|
\r
|
\n
/
)
;
}
public
getEOL
():
string
{
...
...
src/vs/editor/common/model/textSource.ts
浏览文件 @
f4c8522d
...
...
@@ -7,6 +7,18 @@
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
DefaultEndOfLine
}
from
'
vs/editor/common/editorCommon
'
;
/**
* Raw text buffer for Piece Table.
*/
export
interface
IRawPTBuffer
{
text
:
string
;
lineStarts
:
number
[];
/**
* lines count
*/
length
:
number
;
}
/**
* A processed string ready to be turned into an editor model.
*/
...
...
@@ -18,7 +30,7 @@ export interface IRawTextSource {
/**
* The text split into lines.
*/
readonly
lines
:
string
[];
readonly
lines
:
string
[]
|
IRawPTBuffer
;
/**
* The BOM (leading character sequence of the file).
*/
...
...
@@ -51,18 +63,28 @@ export class RawTextSource {
const
isBasicASCII
=
(
containsRTL
?
false
:
strings
.
isBasicASCII
(
rawText
));
// Split the text into lines
const
lines
=
rawText
.
split
(
/
\r\n
|
\r
|
\n
/
);
//
const lines = rawText.split(/\r\n|\r|\n/);
// Remove the BOM (if present)
let
BOM
=
''
;
if
(
strings
.
startsWithUTF8BOM
(
lines
[
0
]
))
{
if
(
strings
.
startsWithUTF8BOM
(
rawText
))
{
BOM
=
strings
.
UTF8_BOM_CHARACTER
;
lines
[
0
]
=
lines
[
0
].
substr
(
1
);
rawText
=
rawText
.
substr
(
1
);
}
let
lastLineFeed
=
-
1
;
var
lineStarts
=
[];
while
((
lastLineFeed
=
rawText
.
indexOf
(
'
\n
'
,
lastLineFeed
+
1
))
!==
-
1
)
{
lineStarts
.
push
(
lastLineFeed
+
1
);
}
return
{
BOM
:
BOM
,
lines
:
lines
,
lines
:
{
text
:
rawText
,
lineStarts
:
lineStarts
,
length
:
lineStarts
.
length
},
length
:
rawText
.
length
,
containsRTL
:
containsRTL
,
isBasicASCII
:
isBasicASCII
,
...
...
@@ -83,7 +105,7 @@ export interface ITextSource {
/**
* The text split into lines.
*/
readonly
lines
:
string
[];
readonly
lines
:
string
[]
|
IRawPTBuffer
;
/**
* The BOM (leading character sequence of the file).
*/
...
...
src/vs/editor/common/services/modelServiceImpl.ts
浏览文件 @
f4c8522d
...
...
@@ -409,10 +409,25 @@ export class ModelServiceImpl implements IModelService {
};
const
textSourceLineSequence
=
new
class
implements
ISequence
{
public
getLength
():
number
{
return
textSource
.
lines
.
length
;
return
textSource
.
lines
.
length
+
1
;
}
public
getElementHash
(
index
:
number
):
string
{
return
textSource
.
lines
[
index
];
if
(
Array
.
isArray
(
textSource
.
lines
))
{
return
textSource
.
lines
[
index
];
}
else
{
if
(
textSource
.
lines
.
length
===
0
)
{
return
textSource
.
lines
.
text
;
}
if
(
index
===
0
)
{
return
textSource
.
lines
.
text
.
substring
(
0
,
textSource
.
lines
.
lineStarts
[
0
]);
}
if
(
index
===
textSource
.
lines
.
length
)
{
return
textSource
.
lines
.
text
.
substring
(
textSource
.
lines
.
lineStarts
[
index
-
1
]);
}
return
textSource
.
lines
.
text
.
substring
(
textSource
.
lines
.
lineStarts
[
index
-
1
],
textSource
.
lines
.
lineStarts
[
index
]);
}
}
};
...
...
src/vs/editor/common/viewModel/prefixSumComputer.ts
浏览文件 @
f4c8522d
...
...
@@ -23,7 +23,7 @@ export class PrefixSumComputer {
/**
* values[i] is the value at index i
*/
private
values
:
Uint32Array
;
values
:
Uint32Array
;
/**
* prefixSum[i] = SUM(heights[j]), 0 <= j <= i
...
...
src/vs/workbench/common/editor/textEditorModel.ts
浏览文件 @
f4c8522d
...
...
@@ -106,8 +106,10 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd
}
return
firstLineText
.
substr
(
0
,
Math
.
min
(
crIndex
,
lfIndex
));
}
else
{
}
else
if
(
Array
.
isArray
(
value
.
lines
))
{
return
value
.
lines
[
0
].
substr
(
0
,
100
);
}
else
{
return
value
.
lines
.
text
.
substr
(
0
,
100
);
}
}
...
...
src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts
浏览文件 @
f4c8522d
...
...
@@ -81,7 +81,7 @@ export class WalkThroughSnippetContentProvider implements ITextModelContentProvi
return
''
;
};
const
markdown
=
content
.
value
.
lines
.
join
(
'
\n
'
)
;
const
markdown
=
Array
.
isArray
(
content
.
value
.
lines
)
?
content
.
value
.
lines
.
join
(
'
\n
'
)
:
content
.
value
.
lines
.
text
;
marked
(
markdown
,
{
renderer
});
const
modeId
=
this
.
modeService
.
getModeIdForLanguageName
(
languageName
);
...
...
src/vs/workbench/services/backup/node/backupFileService.ts
浏览文件 @
f4c8522d
...
...
@@ -215,7 +215,7 @@ export class BackupFileService implements IBackupFileService {
public
parseBackupContent
(
rawTextSource
:
IRawTextSource
):
string
{
const
textSource
=
TextSource
.
fromRawTextSource
(
rawTextSource
,
DefaultEndOfLine
.
LF
);
return
textSource
.
lines
.
slice
(
1
).
join
(
textSource
.
EOL
);
// The first line of a backup text file is the file name
return
(
<
string
[]
>
textSource
.
lines
)
.
slice
(
1
).
join
(
textSource
.
EOL
);
// The first line of a backup text file is the file name
}
public
toBackupResource
(
resource
:
Uri
):
Uri
{
...
...
src/vs/workbench/services/textfile/electron-browser/modelBuilder.ts
浏览文件 @
f4c8522d
...
...
@@ -56,6 +56,61 @@ function optimizeStringMemory(buff: Buffer, s: string): string {
return
s
;
}
class
PTBasedBuilder
{
private
lineStarts
:
number
[];
private
text
:
string
;
private
BOM
:
string
;
private
chunkIndex
:
number
;
private
totalLength
:
number
;
constructor
()
{
this
.
text
=
''
;
this
.
BOM
=
''
;
this
.
chunkIndex
=
0
;
this
.
totalLength
=
0
;
this
.
lineStarts
=
[];
}
public
acceptChunk
(
chunk
:
string
):
void
{
if
(
this
.
chunkIndex
===
0
)
{
if
(
strings
.
startsWithUTF8BOM
(
chunk
))
{
this
.
BOM
=
strings
.
UTF8_BOM_CHARACTER
;
chunk
=
chunk
.
substr
(
1
);
}
}
let
lastLineFeed
=
-
1
;
while
((
lastLineFeed
=
chunk
.
indexOf
(
'
\n
'
,
lastLineFeed
+
1
))
!==
-
1
)
{
this
.
lineStarts
.
push
(
this
.
totalLength
+
lastLineFeed
+
1
);
}
this
.
text
+=
chunk
;
this
.
chunkIndex
++
;
}
public
finish
(
containsRTL
:
boolean
,
isBasicASCII
:
boolean
):
ModelBuilderResult
{
if
(
this
.
lineStarts
[
this
.
lineStarts
.
length
-
1
]
>
this
.
totalLength
)
{
this
.
lineStarts
.
pop
();
}
return
{
hash
:
null
,
value
:
{
length
:
this
.
totalLength
,
lines
:
{
text
:
this
.
text
,
lineStarts
:
this
.
lineStarts
,
length
:
this
.
lineStarts
.
length
},
BOM
:
this
.
BOM
,
totalCRCount
:
0
,
containsRTL
:
containsRTL
,
isBasicASCII
:
isBasicASCII
}
};
}
}
class
ModelLineBasedBuilder
{
private
computeHash
:
boolean
;
...
...
@@ -126,13 +181,15 @@ export class ModelBuilder {
private
containsRTL
:
boolean
;
private
isBasicASCII
:
boolean
;
private
ptBasedBuilder
:
PTBasedBuilder
;
public
static
fromStringStream
(
stream
:
IStringStream
):
TPromise
<
ModelBuilderResult
>
{
return
new
TPromise
<
ModelBuilderResult
>
((
c
,
e
,
p
)
=>
{
let
done
=
false
;
let
builder
=
new
ModelBuilder
(
false
);
stream
.
on
(
'
data
'
,
(
chunk
)
=>
{
builder
.
acceptChunk
(
chunk
);
builder
.
acceptChunk
2
(
chunk
);
});
stream
.
on
(
'
error
'
,
(
error
)
=>
{
...
...
@@ -145,7 +202,7 @@ export class ModelBuilder {
stream
.
on
(
'
end
'
,
()
=>
{
if
(
!
done
)
{
done
=
true
;
c
(
builder
.
finish
());
c
(
builder
.
finish
2
());
}
});
});
...
...
@@ -159,6 +216,7 @@ export class ModelBuilder {
this
.
totalLength
=
0
;
this
.
containsRTL
=
false
;
this
.
isBasicASCII
=
true
;
this
.
ptBasedBuilder
=
new
PTBasedBuilder
();
}
private
_updateCRCount
(
chunk
:
string
):
void
{
...
...
@@ -210,6 +268,22 @@ export class ModelBuilder {
this
.
leftoverPrevChunk
=
lines
[
lines
.
length
-
1
];
}
public
acceptChunk2
(
chunk
:
string
):
void
{
if
(
chunk
.
length
===
0
)
{
return
;
}
// update lineStart to offset mapping
if
(
!
this
.
containsRTL
)
{
this
.
containsRTL
=
strings
.
containsRTL
(
chunk
);
}
if
(
this
.
isBasicASCII
)
{
this
.
isBasicASCII
=
strings
.
isBasicASCII
(
chunk
);
}
this
.
ptBasedBuilder
.
acceptChunk
(
chunk
);
}
public
finish
():
ModelBuilderResult
{
let
finalLines
=
[
this
.
leftoverPrevChunk
];
if
(
this
.
leftoverEndsInCR
)
{
...
...
@@ -218,4 +292,8 @@ export class ModelBuilder {
this
.
lineBasedBuilder
.
acceptLines
(
finalLines
);
return
this
.
lineBasedBuilder
.
finish
(
this
.
totalLength
,
this
.
totalCRCount
,
this
.
containsRTL
,
this
.
isBasicASCII
);
}
public
finish2
():
ModelBuilderResult
{
return
this
.
ptBasedBuilder
.
finish
(
this
.
containsRTL
,
this
.
isBasicASCII
);
}
}
src/vs/workbench/test/workbenchTestServices.ts
浏览文件 @
f4c8522d
...
...
@@ -859,7 +859,7 @@ export class TestBackupFileService implements IBackupFileService {
}
public
parseBackupContent
(
rawText
:
IRawTextSource
):
string
{
return
rawText
.
lines
.
join
(
'
\n
'
)
;
return
Array
.
isArray
(
rawText
.
lines
)
?
rawText
.
lines
.
join
(
'
\n
'
)
:
rawText
.
lines
.
text
;
}
public
discardResourceBackup
(
resource
:
URI
):
TPromise
<
void
>
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录