Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
xxadev
vscode
提交
805d0785
V
vscode
项目概览
xxadev
/
vscode
与 Fork 源项目一致
从无法访问的项目Fork
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
V
vscode
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
805d0785
编写于
3月 28, 2018
作者:
A
Alex Dima
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Remove ChunksTextBuffer
上级
ef7763a2
变更
6
显示空白变更内容
内联
并排
Showing
6 changed file
with
2 addition
and
2104 deletion
+2
-2104
src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts
src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts
+0
-323
src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts
.../editor/common/model/chunksTextBuffer/chunksTextBuffer.ts
+0
-1526
src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts
.../common/model/chunksTextBuffer/chunksTextBufferBuilder.ts
+0
-186
src/vs/editor/common/model/textModel.ts
src/vs/editor/common/model/textModel.ts
+1
-6
src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts
src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts
+1
-2
src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts
...or/test/common/model/chunksTextBuffer/bufferPiece.test.ts
+0
-61
未找到文件。
src/vs/editor/common/model/chunksTextBuffer/bufferPiece.ts
已删除
100644 → 0
浏览文件 @
ef7763a2
/*---------------------------------------------------------------------------------------------
* 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
{
CharCode
}
from
'
vs/base/common/charCode
'
;
export
class
LeafOffsetLenEdit
{
constructor
(
public
readonly
start
:
number
,
public
readonly
length
:
number
,
public
readonly
text
:
string
)
{
}
}
export
class
BufferPiece
{
private
readonly
_str
:
string
;
public
get
text
():
string
{
return
this
.
_str
;
}
private
readonly
_lineStarts
:
Uint32Array
;
constructor
(
str
:
string
,
lineStarts
:
Uint32Array
=
null
)
{
this
.
_str
=
str
;
if
(
lineStarts
===
null
)
{
this
.
_lineStarts
=
createLineStartsFast
(
str
);
}
else
{
this
.
_lineStarts
=
lineStarts
;
}
}
public
length
():
number
{
return
this
.
_str
.
length
;
}
public
newLineCount
():
number
{
return
this
.
_lineStarts
.
length
;
}
public
lineStartFor
(
relativeLineIndex
:
number
):
number
{
return
this
.
_lineStarts
[
relativeLineIndex
];
}
public
charCodeAt
(
index
:
number
):
number
{
return
this
.
_str
.
charCodeAt
(
index
);
}
public
substr
(
from
:
number
,
length
:
number
):
string
{
return
this
.
_str
.
substr
(
from
,
length
);
}
public
findLineStartBeforeOffset
(
offset
:
number
):
number
{
if
(
this
.
_lineStarts
.
length
===
0
||
offset
<
this
.
_lineStarts
[
0
])
{
return
-
1
;
}
let
low
=
0
,
high
=
this
.
_lineStarts
.
length
-
1
;
while
(
low
<
high
)
{
let
mid
=
low
+
Math
.
ceil
((
high
-
low
)
/
2
);
let
lineStart
=
this
.
_lineStarts
[
mid
];
if
(
offset
===
lineStart
)
{
return
mid
;
}
else
if
(
offset
<
lineStart
)
{
high
=
mid
-
1
;
}
else
{
low
=
mid
;
}
}
return
low
;
}
public
findLineFirstNonWhitespaceIndex
(
searchStartOffset
:
number
):
number
{
for
(
let
i
=
searchStartOffset
,
len
=
this
.
_str
.
length
;
i
<
len
;
i
++
)
{
const
chCode
=
this
.
_str
.
charCodeAt
(
i
);
if
(
chCode
===
CharCode
.
CarriageReturn
||
chCode
===
CharCode
.
LineFeed
)
{
// Reached EOL
return
-
2
;
}
if
(
chCode
!==
CharCode
.
Space
&&
chCode
!==
CharCode
.
Tab
)
{
return
i
;
}
}
return
-
1
;
}
public
findLineLastNonWhitespaceIndex
(
searchStartOffset
:
number
):
number
{
for
(
let
i
=
searchStartOffset
-
1
;
i
>=
0
;
i
--
)
{
const
chCode
=
this
.
_str
.
charCodeAt
(
i
);
if
(
chCode
===
CharCode
.
CarriageReturn
||
chCode
===
CharCode
.
LineFeed
)
{
// Reached EOL
return
-
2
;
}
if
(
chCode
!==
CharCode
.
Space
&&
chCode
!==
CharCode
.
Tab
)
{
return
i
;
}
}
return
-
1
;
}
public
static
normalizeEOL
(
target
:
BufferPiece
,
eol
:
'
\r\n
'
|
'
\n
'
):
BufferPiece
{
return
new
BufferPiece
(
target
.
_str
.
replace
(
/
\r\n
|
\r
|
\n
/g
,
eol
));
}
public
static
deleteLastChar
(
target
:
BufferPiece
):
BufferPiece
{
const
targetCharsLength
=
target
.
length
();
const
targetLineStartsLength
=
target
.
newLineCount
();
const
targetLineStarts
=
target
.
_lineStarts
;
let
newLineStartsLength
;
if
(
targetLineStartsLength
>
0
&&
targetLineStarts
[
targetLineStartsLength
-
1
]
===
targetCharsLength
)
{
newLineStartsLength
=
targetLineStartsLength
-
1
;
}
else
{
newLineStartsLength
=
targetLineStartsLength
;
}
let
newLineStarts
=
new
Uint32Array
(
newLineStartsLength
);
newLineStarts
.
set
(
targetLineStarts
);
return
new
BufferPiece
(
target
.
_str
.
substr
(
0
,
targetCharsLength
-
1
),
newLineStarts
);
}
public
static
insertFirstChar
(
target
:
BufferPiece
,
character
:
number
):
BufferPiece
{
const
targetLineStartsLength
=
target
.
newLineCount
();
const
targetLineStarts
=
target
.
_lineStarts
;
const
insertLineStart
=
((
character
===
CharCode
.
CarriageReturn
&&
(
targetLineStartsLength
===
0
||
targetLineStarts
[
0
]
!==
1
||
target
.
charCodeAt
(
0
)
!==
CharCode
.
LineFeed
))
||
(
character
===
CharCode
.
LineFeed
));
const
newLineStartsLength
=
(
insertLineStart
?
targetLineStartsLength
+
1
:
targetLineStartsLength
);
let
newLineStarts
=
new
Uint32Array
(
newLineStartsLength
);
if
(
insertLineStart
)
{
newLineStarts
[
0
]
=
1
;
for
(
let
i
=
0
;
i
<
targetLineStartsLength
;
i
++
)
{
newLineStarts
[
i
+
1
]
=
targetLineStarts
[
i
]
+
1
;
}
}
else
{
for
(
let
i
=
0
;
i
<
targetLineStartsLength
;
i
++
)
{
newLineStarts
[
i
]
=
targetLineStarts
[
i
]
+
1
;
}
}
return
new
BufferPiece
(
String
.
fromCharCode
(
character
)
+
target
.
_str
,
newLineStarts
);
}
public
static
join
(
first
:
BufferPiece
,
second
:
BufferPiece
):
BufferPiece
{
const
firstCharsLength
=
first
.
_str
.
length
;
const
firstLineStartsLength
=
first
.
_lineStarts
.
length
;
const
secondLineStartsLength
=
second
.
_lineStarts
.
length
;
const
firstLineStarts
=
first
.
_lineStarts
;
const
secondLineStarts
=
second
.
_lineStarts
;
const
newLineStartsLength
=
firstLineStartsLength
+
secondLineStartsLength
;
let
newLineStarts
=
new
Uint32Array
(
newLineStartsLength
);
newLineStarts
.
set
(
firstLineStarts
,
0
);
for
(
let
i
=
0
;
i
<
secondLineStartsLength
;
i
++
)
{
newLineStarts
[
i
+
firstLineStartsLength
]
=
secondLineStarts
[
i
]
+
firstCharsLength
;
}
return
new
BufferPiece
(
first
.
_str
+
second
.
_str
,
newLineStarts
);
}
public
static
replaceOffsetLen
(
target
:
BufferPiece
,
edits
:
LeafOffsetLenEdit
[],
idealLeafLength
:
number
,
maxLeafLength
:
number
,
result
:
BufferPiece
[]):
void
{
const
editsSize
=
edits
.
length
;
const
originalCharsLength
=
target
.
length
();
if
(
editsSize
===
1
&&
edits
[
0
].
text
.
length
===
0
&&
edits
[
0
].
start
===
0
&&
edits
[
0
].
length
===
originalCharsLength
)
{
// special case => deleting everything
return
;
}
let
pieces
:
string
[]
=
new
Array
<
string
>
(
2
*
editsSize
+
1
);
let
originalFromIndex
=
0
;
let
piecesTextLength
=
0
;
for
(
let
i
=
0
;
i
<
editsSize
;
i
++
)
{
const
edit
=
edits
[
i
];
const
originalText
=
target
.
_str
.
substr
(
originalFromIndex
,
edit
.
start
-
originalFromIndex
);
pieces
[
2
*
i
]
=
originalText
;
piecesTextLength
+=
originalText
.
length
;
originalFromIndex
=
edit
.
start
+
edit
.
length
;
pieces
[
2
*
i
+
1
]
=
edit
.
text
;
piecesTextLength
+=
edit
.
text
.
length
;
}
// maintain the chars that survive to the right of the last edit
let
text
=
target
.
_str
.
substr
(
originalFromIndex
,
originalCharsLength
-
originalFromIndex
);
pieces
[
2
*
editsSize
]
=
text
;
piecesTextLength
+=
text
.
length
;
let
targetDataLength
=
piecesTextLength
>
maxLeafLength
?
idealLeafLength
:
piecesTextLength
;
let
targetDataOffset
=
0
;
let
data
:
string
=
''
;
for
(
let
pieceIndex
=
0
,
pieceCount
=
pieces
.
length
;
pieceIndex
<
pieceCount
;
pieceIndex
++
)
{
const
pieceText
=
pieces
[
pieceIndex
];
const
pieceLength
=
pieceText
.
length
;
if
(
pieceLength
===
0
)
{
continue
;
}
let
pieceOffset
=
0
;
while
(
pieceOffset
<
pieceLength
)
{
if
(
targetDataOffset
>=
targetDataLength
)
{
result
.
push
(
new
BufferPiece
(
data
));
targetDataLength
=
piecesTextLength
>
maxLeafLength
?
idealLeafLength
:
piecesTextLength
;
targetDataOffset
=
0
;
data
=
''
;
}
let
writingCnt
=
min
(
pieceLength
-
pieceOffset
,
targetDataLength
-
targetDataOffset
);
data
+=
pieceText
.
substr
(
pieceOffset
,
writingCnt
);
pieceOffset
+=
writingCnt
;
targetDataOffset
+=
writingCnt
;
piecesTextLength
-=
writingCnt
;
// check that the buffer piece does not end in a \r or high surrogate
if
(
targetDataOffset
===
targetDataLength
&&
piecesTextLength
>
0
)
{
const
lastChar
=
data
.
charCodeAt
(
targetDataLength
-
1
);
if
(
lastChar
===
CharCode
.
CarriageReturn
||
(
0xD800
<=
lastChar
&&
lastChar
<=
0xDBFF
))
{
// move lastChar over to next buffer piece
targetDataLength
-=
1
;
pieceOffset
-=
1
;
targetDataOffset
-=
1
;
piecesTextLength
+=
1
;
data
=
data
.
substr
(
0
,
data
.
length
-
1
);
}
}
}
}
result
.
push
(
new
BufferPiece
(
data
));
}
}
function
min
(
a
:
number
,
b
:
number
):
number
{
return
(
a
<
b
?
a
:
b
);
}
export
function
createUint32Array
(
arr
:
number
[]):
Uint32Array
{
let
r
=
new
Uint32Array
(
arr
.
length
);
r
.
set
(
arr
,
0
);
return
r
;
}
export
class
LineStarts
{
constructor
(
public
readonly
lineStarts
:
Uint32Array
,
public
readonly
cr
:
number
,
public
readonly
lf
:
number
,
public
readonly
crlf
:
number
,
public
readonly
isBasicASCII
:
boolean
)
{
}
}
export
function
createLineStartsFast
(
str
:
string
):
Uint32Array
{
let
r
:
number
[]
=
[],
rLength
=
0
;
for
(
let
i
=
0
,
len
=
str
.
length
;
i
<
len
;
i
++
)
{
const
chr
=
str
.
charCodeAt
(
i
);
if
(
chr
===
CharCode
.
CarriageReturn
)
{
if
(
i
+
1
<
len
&&
str
.
charCodeAt
(
i
+
1
)
===
CharCode
.
LineFeed
)
{
// \r\n... case
r
[
rLength
++
]
=
i
+
2
;
i
++
;
// skip \n
}
else
{
// \r... case
r
[
rLength
++
]
=
i
+
1
;
}
}
else
if
(
chr
===
CharCode
.
LineFeed
)
{
r
[
rLength
++
]
=
i
+
1
;
}
}
return
createUint32Array
(
r
);
}
export
function
createLineStarts
(
r
:
number
[],
str
:
string
):
LineStarts
{
r
.
length
=
0
;
let
rLength
=
0
;
let
cr
=
0
,
lf
=
0
,
crlf
=
0
;
let
isBasicASCII
=
true
;
for
(
let
i
=
0
,
len
=
str
.
length
;
i
<
len
;
i
++
)
{
const
chr
=
str
.
charCodeAt
(
i
);
if
(
chr
===
CharCode
.
CarriageReturn
)
{
if
(
i
+
1
<
len
&&
str
.
charCodeAt
(
i
+
1
)
===
CharCode
.
LineFeed
)
{
// \r\n... case
crlf
++
;
r
[
rLength
++
]
=
i
+
2
;
i
++
;
// skip \n
}
else
{
cr
++
;
// \r... case
r
[
rLength
++
]
=
i
+
1
;
}
}
else
if
(
chr
===
CharCode
.
LineFeed
)
{
lf
++
;
r
[
rLength
++
]
=
i
+
1
;
}
else
{
if
(
isBasicASCII
)
{
if
(
chr
!==
CharCode
.
Tab
&&
(
chr
<
32
||
chr
>
126
))
{
isBasicASCII
=
false
;
}
}
}
}
const
result
=
new
LineStarts
(
createUint32Array
(
r
),
cr
,
lf
,
crlf
,
isBasicASCII
);
r
.
length
=
0
;
return
result
;
}
src/vs/editor/common/model/chunksTextBuffer/chunksTextBuffer.ts
已删除
100644 → 0
浏览文件 @
ef7763a2
/*---------------------------------------------------------------------------------------------
* 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
{
CharCode
}
from
'
vs/base/common/charCode
'
;
import
{
ITextBuffer
,
EndOfLinePreference
,
IIdentifiedSingleEditOperation
,
ApplyEditsResult
,
ISingleEditOperationIdentifier
,
IInternalModelContentChange
}
from
'
vs/editor/common/model
'
;
import
{
BufferPiece
,
LeafOffsetLenEdit
}
from
'
vs/editor/common/model/chunksTextBuffer/bufferPiece
'
;
import
{
Position
}
from
'
vs/editor/common/core/position
'
;
import
{
Range
}
from
'
vs/editor/common/core/range
'
;
import
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
ITextSnapshot
}
from
'
vs/platform/files/common/files
'
;
export
interface
IValidatedEditOperation
{
sortIndex
:
number
;
identifier
:
ISingleEditOperationIdentifier
;
range
:
Range
;
rangeOffset
:
number
;
rangeLength
:
number
;
lines
:
string
[];
forceMoveMarkers
:
boolean
;
isAutoWhitespaceEdit
:
boolean
;
}
export
class
ChunksTextBuffer
implements
ITextBuffer
{
private
_BOM
:
string
;
private
_actual
:
Buffer
;
private
_mightContainRTL
:
boolean
;
private
_mightContainNonBasicASCII
:
boolean
;
constructor
(
pieces
:
BufferPiece
[],
_averageChunkSize
:
number
,
BOM
:
string
,
eol
:
'
\r\n
'
|
'
\n
'
,
containsRTL
:
boolean
,
isBasicASCII
:
boolean
)
{
this
.
_BOM
=
BOM
;
const
averageChunkSize
=
Math
.
floor
(
Math
.
min
(
65536.0
,
Math
.
max
(
128.0
,
_averageChunkSize
)));
const
delta
=
Math
.
floor
(
averageChunkSize
/
3
);
const
min
=
averageChunkSize
-
delta
;
const
max
=
2
*
min
;
this
.
_actual
=
new
Buffer
(
pieces
,
min
,
max
,
eol
);
this
.
_mightContainRTL
=
containsRTL
;
this
.
_mightContainNonBasicASCII
=
!
isBasicASCII
;
}
equals
(
other
:
ITextBuffer
):
boolean
{
if
(
!
(
other
instanceof
ChunksTextBuffer
))
{
return
false
;
}
return
this
.
_actual
.
equals
(
other
.
_actual
);
}
mightContainRTL
():
boolean
{
return
this
.
_mightContainRTL
;
}
mightContainNonBasicASCII
():
boolean
{
return
this
.
_mightContainNonBasicASCII
;
}
getBOM
():
string
{
return
this
.
_BOM
;
}
getEOL
():
string
{
return
this
.
_actual
.
getEOL
();
}
getOffsetAt
(
lineNumber
:
number
,
column
:
number
):
number
{
return
this
.
_actual
.
convertPositionToOffset
(
lineNumber
,
column
);
}
getPositionAt
(
offset
:
number
):
Position
{
return
this
.
_actual
.
convertOffsetToPosition
(
offset
);
}
getRangeAt
(
offset
:
number
,
length
:
number
):
Range
{
return
this
.
_actual
.
convertOffsetLenToRange
(
offset
,
length
);
}
getValueInRange
(
range
:
Range
,
eol
:
EndOfLinePreference
):
string
{
if
(
range
.
isEmpty
())
{
return
''
;
}
const
text
=
this
.
_actual
.
getValueInRange
(
range
);
switch
(
eol
)
{
case
EndOfLinePreference
.
TextDefined
:
return
text
;
case
EndOfLinePreference
.
LF
:
if
(
this
.
getEOL
()
===
'
\n
'
)
{
return
text
;
}
else
{
return
text
.
replace
(
/
\r\n
/g
,
'
\n
'
);
}
case
EndOfLinePreference
.
CRLF
:
if
(
this
.
getEOL
()
===
'
\r\n
'
)
{
return
text
;
}
else
{
return
text
.
replace
(
/
\n
/g
,
'
\r\n
'
);
}
}
return
null
;
}
public
createSnapshot
(
preserveBOM
:
boolean
):
ITextSnapshot
{
return
this
.
_actual
.
createSnapshot
(
preserveBOM
?
this
.
_BOM
:
''
);
}
getValueLengthInRange
(
range
:
Range
,
eol
:
EndOfLinePreference
):
number
{
if
(
range
.
isEmpty
())
{
return
0
;
}
const
eolCount
=
range
.
endLineNumber
-
range
.
startLineNumber
;
const
result
=
this
.
_actual
.
getValueLengthInRange
(
range
);
switch
(
eol
)
{
case
EndOfLinePreference
.
TextDefined
:
return
result
;
case
EndOfLinePreference
.
LF
:
if
(
this
.
getEOL
()
===
'
\n
'
)
{
return
result
;
}
else
{
return
result
-
eolCount
;
// \r\n => \n
}
case
EndOfLinePreference
.
CRLF
:
if
(
this
.
getEOL
()
===
'
\r\n
'
)
{
return
result
;
}
else
{
return
result
+
eolCount
;
// \n => \r\n
}
}
return
0
;
}
public
getLength
():
number
{
return
this
.
_actual
.
getLength
();
}
getLineCount
():
number
{
return
this
.
_actual
.
getLineCount
();
}
getLinesContent
():
string
[]
{
return
this
.
_actual
.
getLinesContent
();
}
getLineContent
(
lineNumber
:
number
):
string
{
return
this
.
_actual
.
getLineContent
(
lineNumber
);
}
getLineCharCode
(
lineNumber
:
number
,
index
:
number
):
number
{
return
this
.
_actual
.
getLineCharCode
(
lineNumber
,
index
);
}
getLineLength
(
lineNumber
:
number
):
number
{
return
this
.
_actual
.
getLineLength
(
lineNumber
);
}
getLineFirstNonWhitespaceColumn
(
lineNumber
:
number
):
number
{
const
result
=
this
.
_actual
.
getLineFirstNonWhitespaceIndex
(
lineNumber
);
if
(
result
===
-
1
)
{
return
0
;
}
return
result
+
1
;
}
getLineLastNonWhitespaceColumn
(
lineNumber
:
number
):
number
{
const
result
=
this
.
_actual
.
getLineLastNonWhitespaceIndex
(
lineNumber
);
if
(
result
===
-
1
)
{
return
0
;
}
return
result
+
1
;
}
setEOL
(
newEOL
:
'
\r\n
'
|
'
\n
'
):
void
{
if
(
this
.
getEOL
()
===
newEOL
)
{
// nothing to do...
return
;
}
this
.
_actual
.
setEOL
(
newEOL
);
}
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
;
}
applyEdits
(
rawOperations
:
IIdentifiedSingleEditOperation
[],
recordTrimAutoWhitespace
:
boolean
):
ApplyEditsResult
{
if
(
rawOperations
.
length
===
0
)
{
return
new
ApplyEditsResult
([],
[],
[]);
}
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
||
null
,
range
:
validatedRange
,
rangeOffset
:
this
.
getOffsetAt
(
validatedRange
.
startLineNumber
,
validatedRange
.
startColumn
),
rangeLength
:
this
.
getValueLengthInRange
(
validatedRange
,
EndOfLinePreference
.
TextDefined
),
lines
:
op
.
text
?
op
.
text
.
split
(
/
\r\n
|
\r
|
\n
/
)
:
null
,
forceMoveMarkers
:
op
.
forceMoveMarkers
||
false
,
isAutoWhitespaceEdit
:
op
.
isAutoWhitespaceEdit
||
false
};
}
// Sort operations ascending
operations
.
sort
(
ChunksTextBuffer
.
_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
=
ChunksTextBuffer
.
_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
:
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
,
EndOfLinePreference
.
TextDefined
),
forceMoveMarkers
:
op
.
forceMoveMarkers
};
}
this
.
_mightContainRTL
=
mightContainRTL
;
this
.
_mightContainNonBasicASCII
=
mightContainNonBasicASCII
;
const
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
ApplyEditsResult
(
reverseOperations
,
contentChanges
,
trimAutoWhitespaceLineNumbers
);
}
/**
* Transform operations such that they represent the same logic edit,
* but that they also do not cause OOM crashes.
*/
private
_reduceOperations
(
operations
:
IValidatedEditOperation
[]):
IValidatedEditOperation
[]
{
if
(
operations
.
length
<
1000
)
{
// We know from empirical testing that a thousand edits work fine regardless of their shape.
return
operations
;
}
// At one point, due to how events are emitted and how each operation is handled,
// some operations can trigger a high ammount of temporary string allocations,
// that will immediately get edited again.
// e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line
// Therefore, the strategy is to collapse all the operations into a huge single edit operation
return
[
this
.
_toSingleEditOperation
(
operations
)];
}
_toSingleEditOperation
(
operations
:
IValidatedEditOperation
[]):
IValidatedEditOperation
{
let
forceMoveMarkers
=
false
,
firstEditRange
=
operations
[
0
].
range
,
lastEditRange
=
operations
[
operations
.
length
-
1
].
range
,
entireEditRange
=
new
Range
(
firstEditRange
.
startLineNumber
,
firstEditRange
.
startColumn
,
lastEditRange
.
endLineNumber
,
lastEditRange
.
endColumn
),
lastEndLineNumber
=
firstEditRange
.
startLineNumber
,
lastEndColumn
=
firstEditRange
.
startColumn
,
result
:
string
[]
=
[];
for
(
let
i
=
0
,
len
=
operations
.
length
;
i
<
len
;
i
++
)
{
let
operation
=
operations
[
i
],
range
=
operation
.
range
;
forceMoveMarkers
=
forceMoveMarkers
||
operation
.
forceMoveMarkers
;
// (1) -- Push old text
for
(
let
lineNumber
=
lastEndLineNumber
;
lineNumber
<
range
.
startLineNumber
;
lineNumber
++
)
{
if
(
lineNumber
===
lastEndLineNumber
)
{
result
.
push
(
this
.
getLineContent
(
lineNumber
).
substring
(
lastEndColumn
-
1
));
}
else
{
result
.
push
(
'
\n
'
);
result
.
push
(
this
.
getLineContent
(
lineNumber
));
}
}
if
(
range
.
startLineNumber
===
lastEndLineNumber
)
{
result
.
push
(
this
.
getLineContent
(
range
.
startLineNumber
).
substring
(
lastEndColumn
-
1
,
range
.
startColumn
-
1
));
}
else
{
result
.
push
(
'
\n
'
);
result
.
push
(
this
.
getLineContent
(
range
.
startLineNumber
).
substring
(
0
,
range
.
startColumn
-
1
));
}
// (2) -- Push new text
if
(
operation
.
lines
)
{
for
(
let
j
=
0
,
lenJ
=
operation
.
lines
.
length
;
j
<
lenJ
;
j
++
)
{
if
(
j
!==
0
)
{
result
.
push
(
'
\n
'
);
}
result
.
push
(
operation
.
lines
[
j
]);
}
}
lastEndLineNumber
=
operation
.
range
.
endLineNumber
;
lastEndColumn
=
operation
.
range
.
endColumn
;
}
return
{
sortIndex
:
0
,
identifier
:
operations
[
0
].
identifier
,
range
:
entireEditRange
,
rangeOffset
:
this
.
getOffsetAt
(
entireEditRange
.
startLineNumber
,
entireEditRange
.
startColumn
),
rangeLength
:
this
.
getValueLengthInRange
(
entireEditRange
,
EndOfLinePreference
.
TextDefined
),
lines
:
result
.
join
(
''
).
split
(
'
\n
'
),
forceMoveMarkers
:
forceMoveMarkers
,
isAutoWhitespaceEdit
:
false
};
}
private
_doApplyEdits
(
operations
:
IValidatedEditOperation
[]):
IInternalModelContentChange
[]
{
// Sort operations descending
operations
.
sort
(
ChunksTextBuffer
.
_sortOpsDescending
);
let
contentChanges
:
IInternalModelContentChange
[]
=
[];
let
edits
:
OffsetLenEdit
[]
=
[];
for
(
let
i
=
0
,
len
=
operations
.
length
;
i
<
len
;
i
++
)
{
const
op
=
operations
[
i
];
const
text
=
(
op
.
lines
?
op
.
lines
.
join
(
this
.
getEOL
())
:
''
);
edits
[
i
]
=
new
OffsetLenEdit
(
op
.
sortIndex
,
op
.
rangeOffset
,
op
.
rangeLength
,
text
);
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
;
}
contentChanges
.
push
({
range
:
op
.
range
,
rangeLength
:
op
.
rangeLength
,
text
:
text
,
rangeOffset
:
op
.
rangeOffset
,
forceMoveMarkers
:
op
.
forceMoveMarkers
});
}
this
.
_actual
.
replaceOffsetLen
(
edits
);
return
contentChanges
;
}
/**
* 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
;
}
}
class
BufferNodes
{
public
length
:
Uint32Array
;
public
newLineCount
:
Uint32Array
;
constructor
(
count
:
number
)
{
this
.
length
=
new
Uint32Array
(
count
);
this
.
newLineCount
=
new
Uint32Array
(
count
);
}
}
class
BufferCursor
{
constructor
(
public
offset
:
number
,
public
leafIndex
:
number
,
public
leafStartOffset
:
number
,
public
leafStartNewLineCount
:
number
)
{
}
public
set
(
offset
:
number
,
leafIndex
:
number
,
leafStartOffset
:
number
,
leafStartNewLineCount
:
number
)
{
this
.
offset
=
offset
;
this
.
leafIndex
=
leafIndex
;
this
.
leafStartOffset
=
leafStartOffset
;
this
.
leafStartNewLineCount
=
leafStartNewLineCount
;
}
}
class
OffsetLenEdit
{
constructor
(
public
readonly
initialIndex
:
number
,
public
readonly
offset
:
number
,
public
length
:
number
,
public
text
:
string
)
{
}
}
class
InternalOffsetLenEdit
{
constructor
(
public
readonly
startLeafIndex
:
number
,
public
readonly
startInnerOffset
:
number
,
public
readonly
endLeafIndex
:
number
,
public
readonly
endInnerOffset
:
number
,
public
text
:
string
)
{
}
}
class
LeafReplacement
{
constructor
(
public
readonly
startLeafIndex
:
number
,
public
readonly
endLeafIndex
:
number
,
public
readonly
replacements
:
BufferPiece
[]
)
{
}
}
const
BUFFER_CURSOR_POOL_SIZE
=
10
;
const
BufferCursorPool
=
new
class
{
private
_pool
:
BufferCursor
[];
private
_len
:
number
;
constructor
()
{
this
.
_pool
=
[];
for
(
let
i
=
0
;
i
<
BUFFER_CURSOR_POOL_SIZE
;
i
++
)
{
this
.
_pool
[
i
]
=
new
BufferCursor
(
0
,
0
,
0
,
0
);
}
this
.
_len
=
this
.
_pool
.
length
;
}
public
put
(
cursor
:
BufferCursor
):
void
{
if
(
this
.
_len
>
this
.
_pool
.
length
)
{
// oh, well
return
;
}
this
.
_pool
[
this
.
_len
++
]
=
cursor
;
}
public
take
():
BufferCursor
{
if
(
this
.
_len
===
0
)
{
// oh, well
console
.
log
(
`insufficient BufferCursor pool`
);
return
new
BufferCursor
(
0
,
0
,
0
,
0
);
}
const
result
=
this
.
_pool
[
this
.
_len
-
1
];
this
.
_pool
[
this
.
_len
--
]
=
null
;
return
result
;
}
};
class
BufferSnapshot
implements
ITextSnapshot
{
private
readonly
_pieces
:
BufferPiece
[];
private
readonly
_piecesLength
:
number
;
private
readonly
_BOM
:
string
;
private
_piecesIndex
:
number
;
constructor
(
pieces
:
BufferPiece
[],
BOM
:
string
)
{
this
.
_pieces
=
pieces
;
this
.
_piecesLength
=
this
.
_pieces
.
length
;
this
.
_BOM
=
BOM
;
this
.
_piecesIndex
=
0
;
}
public
read
():
string
{
if
(
this
.
_piecesIndex
>=
this
.
_piecesLength
)
{
return
null
;
}
let
result
:
string
=
null
;
if
(
this
.
_piecesIndex
===
0
)
{
result
=
this
.
_BOM
+
this
.
_pieces
[
this
.
_piecesIndex
].
text
;
}
else
{
result
=
this
.
_pieces
[
this
.
_piecesIndex
].
text
;
}
this
.
_piecesIndex
++
;
return
result
;
}
}
class
Buffer
{
private
_minLeafLength
:
number
;
private
_maxLeafLength
:
number
;
private
_idealLeafLength
:
number
;
private
_eol
:
'
\r\n
'
|
'
\n
'
;
private
_eolLength
:
number
;
private
_leafs
:
BufferPiece
[];
private
_nodes
:
BufferNodes
;
private
_nodesCount
:
number
;
private
_leafsStart
:
number
;
private
_leafsEnd
:
number
;
constructor
(
pieces
:
BufferPiece
[],
minLeafLength
:
number
,
maxLeafLength
:
number
,
eol
:
'
\r\n
'
|
'
\n
'
)
{
if
(
!
(
2
*
minLeafLength
>=
maxLeafLength
))
{
throw
new
Error
(
`assertion violation`
);
}
this
.
_minLeafLength
=
minLeafLength
;
this
.
_maxLeafLength
=
maxLeafLength
;
this
.
_idealLeafLength
=
(
minLeafLength
+
maxLeafLength
)
>>>
1
;
this
.
_eol
=
eol
;
this
.
_eolLength
=
this
.
_eol
.
length
;
this
.
_leafs
=
pieces
;
this
.
_nodes
=
null
;
this
.
_nodesCount
=
0
;
this
.
_leafsStart
=
0
;
this
.
_leafsEnd
=
0
;
this
.
_rebuildNodes
();
}
equals
(
other
:
Buffer
):
boolean
{
return
Buffer
.
equals
(
this
,
other
);
}
private
static
equals
(
a
:
Buffer
,
b
:
Buffer
):
boolean
{
const
aLength
=
a
.
getLength
();
const
bLength
=
b
.
getLength
();
if
(
aLength
!==
bLength
)
{
return
false
;
}
if
(
a
.
getLineCount
()
!==
b
.
getLineCount
())
{
return
false
;
}
let
remaining
=
aLength
;
let
aLeafIndex
=
-
1
,
aLeaf
=
null
,
aLeafLength
=
0
,
aLeafRemaining
=
0
;
let
bLeafIndex
=
-
1
,
bLeaf
=
null
,
bLeafLength
=
0
,
bLeafRemaining
=
0
;
while
(
remaining
>
0
)
{
if
(
aLeafRemaining
===
0
)
{
aLeafIndex
++
;
aLeaf
=
a
.
_leafs
[
aLeafIndex
];
aLeafLength
=
aLeaf
.
length
();
aLeafRemaining
=
aLeafLength
;
}
if
(
bLeafRemaining
===
0
)
{
bLeafIndex
++
;
bLeaf
=
b
.
_leafs
[
bLeafIndex
];
bLeafLength
=
bLeaf
.
length
();
bLeafRemaining
=
bLeafLength
;
}
let
consuming
=
Math
.
min
(
aLeafRemaining
,
bLeafRemaining
);
let
aStr
=
aLeaf
.
substr
(
aLeafLength
-
aLeafRemaining
,
consuming
);
let
bStr
=
bLeaf
.
substr
(
bLeafLength
-
bLeafRemaining
,
consuming
);
if
(
aStr
!==
bStr
)
{
return
false
;
}
remaining
-=
consuming
;
aLeafRemaining
-=
consuming
;
bLeafRemaining
-=
consuming
;
}
return
true
;
}
public
getEOL
():
string
{
return
this
.
_eol
;
}
private
_rebuildNodes
()
{
const
leafsCount
=
this
.
_leafs
.
length
;
this
.
_nodesCount
=
(
1
<<
log2
(
leafsCount
));
this
.
_leafsStart
=
this
.
_nodesCount
;
this
.
_leafsEnd
=
this
.
_leafsStart
+
leafsCount
;
this
.
_nodes
=
new
BufferNodes
(
this
.
_nodesCount
);
for
(
let
i
=
this
.
_nodesCount
-
1
;
i
>=
1
;
i
--
)
{
this
.
_updateSingleNode
(
i
);
}
}
private
_updateSingleNode
(
nodeIndex
:
number
):
void
{
const
left
=
LEFT_CHILD
(
nodeIndex
);
const
right
=
RIGHT_CHILD
(
nodeIndex
);
let
length
=
0
;
let
newLineCount
=
0
;
if
(
this
.
IS_NODE
(
left
))
{
length
+=
this
.
_nodes
.
length
[
left
];
newLineCount
+=
this
.
_nodes
.
newLineCount
[
left
];
}
else
if
(
this
.
IS_LEAF
(
left
))
{
const
leaf
=
this
.
_leafs
[
this
.
NODE_TO_LEAF_INDEX
(
left
)];
length
+=
leaf
.
length
();
newLineCount
+=
leaf
.
newLineCount
();
}
if
(
this
.
IS_NODE
(
right
))
{
length
+=
this
.
_nodes
.
length
[
right
];
newLineCount
+=
this
.
_nodes
.
newLineCount
[
right
];
}
else
if
(
this
.
IS_LEAF
(
right
))
{
const
leaf
=
this
.
_leafs
[
this
.
NODE_TO_LEAF_INDEX
(
right
)];
length
+=
leaf
.
length
();
newLineCount
+=
leaf
.
newLineCount
();
}
this
.
_nodes
.
length
[
nodeIndex
]
=
length
;
this
.
_nodes
.
newLineCount
[
nodeIndex
]
=
newLineCount
;
}
private
_findOffset
(
offset
:
number
,
result
:
BufferCursor
):
boolean
{
if
(
offset
>
this
.
_nodes
.
length
[
1
])
{
return
false
;
}
let
it
=
1
;
let
searchOffset
=
offset
;
let
leafStartOffset
=
0
;
let
leafStartNewLineCount
=
0
;
while
(
!
this
.
IS_LEAF
(
it
))
{
const
left
=
LEFT_CHILD
(
it
);
const
right
=
RIGHT_CHILD
(
it
);
let
leftNewLineCount
=
0
;
let
leftLength
=
0
;
if
(
this
.
IS_NODE
(
left
))
{
leftNewLineCount
=
this
.
_nodes
.
newLineCount
[
left
];
leftLength
=
this
.
_nodes
.
length
[
left
];
}
else
if
(
this
.
IS_LEAF
(
left
))
{
const
leaf
=
this
.
_leafs
[
this
.
NODE_TO_LEAF_INDEX
(
left
)];
leftNewLineCount
=
leaf
.
newLineCount
();
leftLength
=
leaf
.
length
();
}
let
rightLength
=
0
;
if
(
this
.
IS_NODE
(
right
))
{
rightLength
+=
this
.
_nodes
.
length
[
right
];
}
else
if
(
this
.
IS_LEAF
(
right
))
{
rightLength
+=
this
.
_leafs
[
this
.
NODE_TO_LEAF_INDEX
(
right
)].
length
();
}
if
(
searchOffset
<
leftLength
||
rightLength
===
0
)
{
// go left
it
=
left
;
}
else
{
// go right
searchOffset
-=
leftLength
;
leafStartOffset
+=
leftLength
;
leafStartNewLineCount
+=
leftNewLineCount
;
it
=
right
;
}
}
it
=
this
.
NODE_TO_LEAF_INDEX
(
it
);
result
.
set
(
offset
,
it
,
leafStartOffset
,
leafStartNewLineCount
);
return
true
;
}
private
_findOffsetCloseAfter
(
offset
:
number
,
start
:
BufferCursor
,
result
:
BufferCursor
):
boolean
{
if
(
offset
>
this
.
_nodes
.
length
[
1
])
{
return
false
;
}
let
innerOffset
=
offset
-
start
.
leafStartOffset
;
const
leafsCount
=
this
.
_leafs
.
length
;
let
leafIndex
=
start
.
leafIndex
;
let
leafStartOffset
=
start
.
leafStartOffset
;
let
leafStartNewLineCount
=
start
.
leafStartNewLineCount
;
while
(
true
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
if
(
innerOffset
<
leaf
.
length
()
||
(
innerOffset
===
leaf
.
length
()
&&
leafIndex
+
1
===
leafsCount
))
{
result
.
set
(
offset
,
leafIndex
,
leafStartOffset
,
leafStartNewLineCount
);
return
true
;
}
leafIndex
++
;
if
(
leafIndex
>=
leafsCount
)
{
result
.
set
(
offset
,
leafIndex
,
leafStartOffset
,
leafStartNewLineCount
);
return
true
;
}
leafStartOffset
+=
leaf
.
length
();
leafStartNewLineCount
+=
leaf
.
newLineCount
();
innerOffset
-=
leaf
.
length
();
}
}
private
_findLineStart
(
lineNumber
:
number
,
result
:
BufferCursor
):
boolean
{
let
lineIndex
=
lineNumber
-
1
;
if
(
lineIndex
<
0
||
lineIndex
>
this
.
_nodes
.
newLineCount
[
1
])
{
result
.
set
(
0
,
0
,
0
,
0
);
return
false
;
}
let
it
=
1
;
let
leafStartOffset
=
0
;
let
leafStartNewLineCount
=
0
;
while
(
!
this
.
IS_LEAF
(
it
))
{
const
left
=
LEFT_CHILD
(
it
);
const
right
=
RIGHT_CHILD
(
it
);
let
leftNewLineCount
=
0
;
let
leftLength
=
0
;
if
(
this
.
IS_NODE
(
left
))
{
leftNewLineCount
=
this
.
_nodes
.
newLineCount
[
left
];
leftLength
=
this
.
_nodes
.
length
[
left
];
}
else
if
(
this
.
IS_LEAF
(
left
))
{
const
leaf
=
this
.
_leafs
[
this
.
NODE_TO_LEAF_INDEX
(
left
)];
leftNewLineCount
=
leaf
.
newLineCount
();
leftLength
=
leaf
.
length
();
}
if
(
lineIndex
<=
leftNewLineCount
)
{
// go left
it
=
left
;
continue
;
}
// go right
lineIndex
-=
leftNewLineCount
;
leafStartOffset
+=
leftLength
;
leafStartNewLineCount
+=
leftNewLineCount
;
it
=
right
;
}
it
=
this
.
NODE_TO_LEAF_INDEX
(
it
);
const
innerLineStartOffset
=
(
lineIndex
===
0
?
0
:
this
.
_leafs
[
it
].
lineStartFor
(
lineIndex
-
1
));
result
.
set
(
leafStartOffset
+
innerLineStartOffset
,
it
,
leafStartOffset
,
leafStartNewLineCount
);
return
true
;
}
private
_findLineEnd
(
start
:
BufferCursor
,
lineNumber
:
number
,
result
:
BufferCursor
):
void
{
let
innerLineIndex
=
lineNumber
-
1
-
start
.
leafStartNewLineCount
;
const
leafsCount
=
this
.
_leafs
.
length
;
let
leafIndex
=
start
.
leafIndex
;
let
leafStartOffset
=
start
.
leafStartOffset
;
let
leafStartNewLineCount
=
start
.
leafStartNewLineCount
;
while
(
true
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
if
(
innerLineIndex
<
leaf
.
newLineCount
())
{
const
lineEndOffset
=
this
.
_leafs
[
leafIndex
].
lineStartFor
(
innerLineIndex
);
result
.
set
(
leafStartOffset
+
lineEndOffset
,
leafIndex
,
leafStartOffset
,
leafStartNewLineCount
);
return
;
}
leafIndex
++
;
if
(
leafIndex
>=
leafsCount
)
{
result
.
set
(
leafStartOffset
+
leaf
.
length
(),
leafIndex
-
1
,
leafStartOffset
,
leafStartNewLineCount
);
return
;
}
leafStartOffset
+=
leaf
.
length
();
leafStartNewLineCount
+=
leaf
.
newLineCount
();
innerLineIndex
=
0
;
}
}
private
_findLine
(
lineNumber
:
number
,
start
:
BufferCursor
,
end
:
BufferCursor
):
boolean
{
if
(
!
this
.
_findLineStart
(
lineNumber
,
start
))
{
return
false
;
}
this
.
_findLineEnd
(
start
,
lineNumber
,
end
);
return
true
;
}
public
getLength
():
number
{
return
this
.
_nodes
.
length
[
1
];
}
public
getLineCount
():
number
{
return
this
.
_nodes
.
newLineCount
[
1
]
+
1
;
}
public
getLineContent
(
lineNumber
:
number
):
string
{
const
start
=
BufferCursorPool
.
take
();
const
end
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLine
(
lineNumber
,
start
,
end
))
{
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
throw
new
Error
(
`Line not found`
);
}
let
result
:
string
;
if
(
lineNumber
===
this
.
getLineCount
())
{
// last line is not trailed by an eol
result
=
this
.
extractString
(
start
,
end
.
offset
-
start
.
offset
);
}
else
{
result
=
this
.
extractString
(
start
,
end
.
offset
-
start
.
offset
-
this
.
_eolLength
);
}
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
return
result
;
}
public
getLineCharCode
(
lineNumber
:
number
,
index
:
number
):
number
{
const
start
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLineStart
(
lineNumber
,
start
))
{
BufferCursorPool
.
put
(
start
);
throw
new
Error
(
`Line not found`
);
}
const
tmp
=
BufferCursorPool
.
take
();
this
.
_findOffsetCloseAfter
(
start
.
offset
+
index
,
start
,
tmp
);
const
result
=
this
.
_leafs
[
tmp
.
leafIndex
].
charCodeAt
(
tmp
.
offset
-
tmp
.
leafStartNewLineCount
);
BufferCursorPool
.
put
(
tmp
);
BufferCursorPool
.
put
(
start
);
return
result
;
}
public
getLineLength
(
lineNumber
:
number
):
number
{
const
start
=
BufferCursorPool
.
take
();
const
end
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLine
(
lineNumber
,
start
,
end
))
{
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
throw
new
Error
(
`Line not found`
);
}
let
result
:
number
;
if
(
lineNumber
===
this
.
getLineCount
())
{
// last line is not trailed by an eol
result
=
end
.
offset
-
start
.
offset
;
}
else
{
result
=
end
.
offset
-
start
.
offset
-
this
.
_eolLength
;
}
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
return
result
;
}
public
getLineFirstNonWhitespaceIndex
(
lineNumber
:
number
):
number
{
const
start
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLineStart
(
lineNumber
,
start
))
{
BufferCursorPool
.
put
(
start
);
throw
new
Error
(
`Line not found`
);
}
let
leafIndex
=
start
.
leafIndex
;
let
searchStartOffset
=
start
.
offset
-
start
.
leafStartOffset
;
BufferCursorPool
.
put
(
start
);
const
leafsCount
=
this
.
_leafs
.
length
;
let
totalDelta
=
0
;
while
(
true
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
const
leafResult
=
leaf
.
findLineFirstNonWhitespaceIndex
(
searchStartOffset
);
if
(
leafResult
===
-
2
)
{
// reached EOL
return
-
1
;
}
if
(
leafResult
!==
-
1
)
{
return
(
leafResult
-
searchStartOffset
)
+
totalDelta
;
}
leafIndex
++
;
if
(
leafIndex
>=
leafsCount
)
{
return
-
1
;
}
totalDelta
+=
(
leaf
.
length
()
-
searchStartOffset
);
searchStartOffset
=
0
;
}
}
public
getLineLastNonWhitespaceIndex
(
lineNumber
:
number
):
number
{
const
start
=
BufferCursorPool
.
take
();
const
end
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLineStart
(
lineNumber
,
start
))
{
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
throw
new
Error
(
`Line not found`
);
}
this
.
_findLineEnd
(
start
,
lineNumber
,
end
);
const
startOffset
=
start
.
offset
;
const
endOffset
=
end
.
offset
;
let
leafIndex
=
end
.
leafIndex
;
let
searchStartOffset
=
end
.
offset
-
end
.
leafStartOffset
-
this
.
_eolLength
;
BufferCursorPool
.
put
(
start
);
BufferCursorPool
.
put
(
end
);
let
totalDelta
=
0
;
while
(
true
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
const
leafResult
=
leaf
.
findLineLastNonWhitespaceIndex
(
searchStartOffset
);
if
(
leafResult
===
-
2
)
{
// reached EOL
return
-
1
;
}
if
(
leafResult
!==
-
1
)
{
const
delta
=
(
searchStartOffset
-
1
-
leafResult
);
const
absoluteOffset
=
(
endOffset
-
this
.
_eolLength
)
-
delta
-
totalDelta
;
return
absoluteOffset
-
startOffset
;
}
leafIndex
--
;
if
(
leafIndex
<
0
)
{
return
-
1
;
}
totalDelta
+=
searchStartOffset
;
searchStartOffset
=
leaf
.
length
();
}
}
public
getLinesContent
():
string
[]
{
let
result
:
string
[]
=
new
Array
<
string
>
(
this
.
getLineCount
());
let
resultIndex
=
0
;
let
currentLine
=
''
;
for
(
let
leafIndex
=
0
,
leafsCount
=
this
.
_leafs
.
length
;
leafIndex
<
leafsCount
;
leafIndex
++
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
const
leafNewLineCount
=
leaf
.
newLineCount
();
if
(
leafNewLineCount
===
0
)
{
// special case => push entire leaf text
currentLine
+=
leaf
.
text
;
continue
;
}
let
leafSubstrOffset
=
0
;
for
(
let
newLineIndex
=
0
;
newLineIndex
<
leafNewLineCount
;
newLineIndex
++
)
{
const
newLineStart
=
leaf
.
lineStartFor
(
newLineIndex
);
currentLine
+=
leaf
.
substr
(
leafSubstrOffset
,
newLineStart
-
leafSubstrOffset
-
this
.
_eolLength
);
result
[
resultIndex
++
]
=
currentLine
;
currentLine
=
''
;
leafSubstrOffset
=
newLineStart
;
}
currentLine
+=
leaf
.
substr
(
leafSubstrOffset
,
leaf
.
length
());
}
result
[
resultIndex
++
]
=
currentLine
;
return
result
;
}
public
extractString
(
start
:
BufferCursor
,
len
:
number
):
string
{
if
(
!
(
start
.
offset
+
len
<=
this
.
_nodes
.
length
[
1
]))
{
throw
new
Error
(
`assertion violation`
);
}
let
innerLeafOffset
=
start
.
offset
-
start
.
leafStartOffset
;
let
leafIndex
=
start
.
leafIndex
;
let
res
=
''
;
while
(
len
>
0
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
const
cnt
=
Math
.
min
(
len
,
leaf
.
length
()
-
innerLeafOffset
);
res
+=
leaf
.
substr
(
innerLeafOffset
,
cnt
);
len
-=
cnt
;
innerLeafOffset
=
0
;
if
(
len
===
0
)
{
break
;
}
leafIndex
++
;
}
return
res
;
}
private
_getOffsetAt
(
lineNumber
:
number
,
column
:
number
,
result
:
BufferCursor
):
boolean
{
const
lineStart
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLineStart
(
lineNumber
,
lineStart
))
{
BufferCursorPool
.
put
(
lineStart
);
return
false
;
}
const
startOffset
=
lineStart
.
offset
+
column
-
1
;
if
(
!
this
.
_findOffsetCloseAfter
(
startOffset
,
lineStart
,
result
))
{
BufferCursorPool
.
put
(
lineStart
);
return
false
;
}
BufferCursorPool
.
put
(
lineStart
);
return
true
;
}
public
convertPositionToOffset
(
lineNumber
:
number
,
column
:
number
):
number
{
const
r
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findLineStart
(
lineNumber
,
r
))
{
BufferCursorPool
.
put
(
r
);
throw
new
Error
(
`Position not found`
);
}
const
result
=
r
.
offset
+
column
-
1
;
BufferCursorPool
.
put
(
r
);
return
result
;
}
/**
* returns `lineNumber`
*/
private
_findLineStartBeforeOffsetInLeaf
(
offset
:
number
,
leafIndex
:
number
,
leafStartOffset
:
number
,
leafStartNewLineCount
:
number
,
result
:
BufferCursor
):
number
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
const
lineStartIndex
=
leaf
.
findLineStartBeforeOffset
(
offset
-
leafStartOffset
);
const
lineStartOffset
=
leafStartOffset
+
leaf
.
lineStartFor
(
lineStartIndex
);
result
.
set
(
lineStartOffset
,
leafIndex
,
leafStartOffset
,
leafStartNewLineCount
);
return
leafStartNewLineCount
+
lineStartIndex
+
2
;
}
/**
* returns `lineNumber`.
*/
private
_findLineStartBeforeOffset
(
offset
:
number
,
location
:
BufferCursor
,
result
:
BufferCursor
):
number
{
let
leafIndex
=
location
.
leafIndex
;
let
leafStartOffset
=
location
.
leafStartOffset
;
let
leafStartNewLineCount
=
location
.
leafStartNewLineCount
;
while
(
true
)
{
const
leaf
=
this
.
_leafs
[
leafIndex
];
if
(
leaf
.
newLineCount
()
>=
1
&&
leaf
.
lineStartFor
(
0
)
+
leafStartOffset
<=
offset
)
{
// must be in this leaf
return
this
.
_findLineStartBeforeOffsetInLeaf
(
offset
,
leafIndex
,
leafStartOffset
,
leafStartNewLineCount
,
result
);
}
// continue looking in previous leaf
leafIndex
--
;
if
(
leafIndex
<
0
)
{
result
.
set
(
0
,
0
,
0
,
0
);
return
1
;
}
leafStartOffset
-=
this
.
_leafs
[
leafIndex
].
length
();
leafStartNewLineCount
-=
this
.
_leafs
[
leafIndex
].
newLineCount
();
}
}
public
convertOffsetToPosition
(
offset
:
number
):
Position
{
const
r
=
BufferCursorPool
.
take
();
const
lineStart
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findOffset
(
offset
,
r
))
{
BufferCursorPool
.
put
(
r
);
BufferCursorPool
.
put
(
lineStart
);
throw
new
Error
(
`Offset not found`
);
}
const
lineNumber
=
this
.
_findLineStartBeforeOffset
(
offset
,
r
,
lineStart
);
const
column
=
offset
-
lineStart
.
offset
+
1
;
BufferCursorPool
.
put
(
r
);
BufferCursorPool
.
put
(
lineStart
);
return
new
Position
(
lineNumber
,
column
);
}
public
convertOffsetLenToRange
(
offset
:
number
,
len
:
number
):
Range
{
const
r
=
BufferCursorPool
.
take
();
const
lineStart
=
BufferCursorPool
.
take
();
if
(
!
this
.
_findOffset
(
offset
,
r
))
{
BufferCursorPool
.
put
(
r
);
BufferCursorPool
.
put
(
lineStart
);
throw
new
Error
(
`Offset not found`
);
}
const
startLineNumber
=
this
.
_findLineStartBeforeOffset
(
offset
,
r
,
lineStart
);
const
startColumn
=
offset
-
lineStart
.
offset
+
1
;
if
(
!
this
.
_findOffset
(
offset
+
len
,
r
))
{
BufferCursorPool
.
put
(
r
);
BufferCursorPool
.
put
(
lineStart
);
throw
new
Error
(
`Offset not found`
);
}
const
endLineNumber
=
this
.
_findLineStartBeforeOffset
(
offset
+
len
,
r
,
lineStart
);
const
endColumn
=
offset
+
len
-
lineStart
.
offset
+
1
;
BufferCursorPool
.
put
(
r
);
BufferCursorPool
.
put
(
lineStart
);
return
new
Range
(
startLineNumber
,
startColumn
,
endLineNumber
,
endColumn
);
}
public
getValueInRange
(
range
:
Range
):
string
{
const
start
=
BufferCursorPool
.
take
();
if
(
!
this
.
_getOffsetAt
(
range
.
startLineNumber
,
range
.
startColumn
,
start
))
{
BufferCursorPool
.
put
(
start
);
throw
new
Error
(
`Line not found`
);
}
const
endOffset
=
this
.
convertPositionToOffset
(
range
.
endLineNumber
,
range
.
endColumn
);
const
result
=
this
.
extractString
(
start
,
endOffset
-
start
.
offset
);
BufferCursorPool
.
put
(
start
);
return
result
;
}
public
createSnapshot
(
BOM
:
string
):
ITextSnapshot
{
return
new
BufferSnapshot
(
this
.
_leafs
,
BOM
);
}
public
getValueLengthInRange
(
range
:
Range
):
number
{
const
startOffset
=
this
.
convertPositionToOffset
(
range
.
startLineNumber
,
range
.
startColumn
);
const
endOffset
=
this
.
convertPositionToOffset
(
range
.
endLineNumber
,
range
.
endColumn
);
return
endOffset
-
startOffset
;
}
//#region Editing
private
_mergeAdjacentEdits
(
edits
:
OffsetLenEdit
[]):
OffsetLenEdit
[]
{
// Check if we must merge adjacent edits
let
merged
:
OffsetLenEdit
[]
=
[],
mergedLength
=
0
;
let
prev
=
edits
[
0
];
for
(
let
i
=
1
,
len
=
edits
.
length
;
i
<
len
;
i
++
)
{
const
curr
=
edits
[
i
];
if
(
prev
.
offset
+
prev
.
length
===
curr
.
offset
)
{
// merge into `prev`
prev
.
length
=
prev
.
length
+
curr
.
length
;
prev
.
text
=
prev
.
text
+
curr
.
text
;
}
else
{
merged
[
mergedLength
++
]
=
prev
;
prev
=
curr
;
}
}
merged
[
mergedLength
++
]
=
prev
;
return
merged
;
}
private
_resolveEdits
(
edits
:
OffsetLenEdit
[]):
InternalOffsetLenEdit
[]
{
edits
=
this
.
_mergeAdjacentEdits
(
edits
);
let
result
:
InternalOffsetLenEdit
[]
=
[];
let
tmp
=
new
BufferCursor
(
0
,
0
,
0
,
0
);
let
tmp2
=
new
BufferCursor
(
0
,
0
,
0
,
0
);
for
(
let
i
=
0
,
len
=
edits
.
length
;
i
<
len
;
i
++
)
{
const
edit
=
edits
[
i
];
let
text
=
edit
.
text
;
this
.
_findOffset
(
edit
.
offset
,
tmp
);
let
startLeafIndex
=
tmp
.
leafIndex
;
let
startInnerOffset
=
tmp
.
offset
-
tmp
.
leafStartOffset
;
if
(
startInnerOffset
>
0
)
{
const
startLeaf
=
this
.
_leafs
[
startLeafIndex
];
const
charBefore
=
startLeaf
.
charCodeAt
(
startInnerOffset
-
1
);
if
(
charBefore
===
CharCode
.
CarriageReturn
)
{
// include the replacement of \r in the edit
text
=
'
\r
'
+
text
;
this
.
_findOffsetCloseAfter
(
edit
.
offset
-
1
,
tmp
,
tmp2
);
startLeafIndex
=
tmp2
.
leafIndex
;
startInnerOffset
=
tmp2
.
offset
-
tmp2
.
leafStartOffset
;
// this._findOffset(edit.offset - 1, tmp);
// startLeafIndex = tmp.leafIndex;
// startInnerOffset = tmp.offset - tmp.leafStartOffset;
}
}
this
.
_findOffset
(
edit
.
offset
+
edit
.
length
,
tmp
);
let
endLeafIndex
=
tmp
.
leafIndex
;
let
endInnerOffset
=
tmp
.
offset
-
tmp
.
leafStartOffset
;
const
endLeaf
=
this
.
_leafs
[
endLeafIndex
];
if
(
endInnerOffset
<
endLeaf
.
length
())
{
const
charAfter
=
endLeaf
.
charCodeAt
(
endInnerOffset
);
if
(
charAfter
===
CharCode
.
LineFeed
)
{
// include the replacement of \n in the edit
text
=
text
+
'
\n
'
;
this
.
_findOffsetCloseAfter
(
edit
.
offset
+
edit
.
length
+
1
,
tmp
,
tmp2
);
endLeafIndex
=
tmp2
.
leafIndex
;
endInnerOffset
=
tmp2
.
offset
-
tmp2
.
leafStartOffset
;
// this._findOffset(edit.offset + edit.length + 1, tmp);
// endLeafIndex = tmp.leafIndex;
// endInnerOffset = tmp.offset - tmp.leafStartOffset;
}
}
result
[
i
]
=
new
InternalOffsetLenEdit
(
startLeafIndex
,
startInnerOffset
,
endLeafIndex
,
endInnerOffset
,
text
);
}
return
result
;
}
private
_pushLeafReplacement
(
startLeafIndex
:
number
,
endLeafIndex
:
number
,
replacements
:
LeafReplacement
[]):
LeafReplacement
{
const
res
=
new
LeafReplacement
(
startLeafIndex
,
endLeafIndex
,
[]);
replacements
.
push
(
res
);
return
res
;
}
private
_flushLeafEdits
(
accumulatedLeafIndex
:
number
,
accumulatedLeafEdits
:
LeafOffsetLenEdit
[],
replacements
:
LeafReplacement
[]):
void
{
if
(
accumulatedLeafEdits
.
length
>
0
)
{
const
rep
=
this
.
_pushLeafReplacement
(
accumulatedLeafIndex
,
accumulatedLeafIndex
,
replacements
);
BufferPiece
.
replaceOffsetLen
(
this
.
_leafs
[
accumulatedLeafIndex
],
accumulatedLeafEdits
,
this
.
_idealLeafLength
,
this
.
_maxLeafLength
,
rep
.
replacements
);
}
accumulatedLeafEdits
.
length
=
0
;
}
private
_pushLeafEdits
(
start
:
number
,
length
:
number
,
text
:
string
,
accumulatedLeafEdits
:
LeafOffsetLenEdit
[]):
void
{
if
(
length
!==
0
||
text
.
length
!==
0
)
{
accumulatedLeafEdits
.
push
(
new
LeafOffsetLenEdit
(
start
,
length
,
text
));
}
}
private
_appendLeaf
(
leaf
:
BufferPiece
,
leafs
:
BufferPiece
[],
prevLeaf
:
BufferPiece
):
BufferPiece
{
if
(
prevLeaf
===
null
)
{
leafs
.
push
(
leaf
);
prevLeaf
=
leaf
;
return
prevLeaf
;
}
let
prevLeafLength
=
prevLeaf
.
length
();
let
currLeafLength
=
leaf
.
length
();
if
((
prevLeafLength
<
this
.
_minLeafLength
||
currLeafLength
<
this
.
_minLeafLength
)
&&
prevLeafLength
+
currLeafLength
<=
this
.
_maxLeafLength
)
{
const
joinedLeaf
=
BufferPiece
.
join
(
prevLeaf
,
leaf
);
leafs
[
leafs
.
length
-
1
]
=
joinedLeaf
;
prevLeaf
=
joinedLeaf
;
return
prevLeaf
;
}
const
lastChar
=
prevLeaf
.
charCodeAt
(
prevLeafLength
-
1
);
const
firstChar
=
leaf
.
charCodeAt
(
0
);
if
(
(
lastChar
>=
0xd800
&&
lastChar
<=
0xdbff
)
||
(
lastChar
===
CharCode
.
CarriageReturn
&&
firstChar
===
CharCode
.
LineFeed
)
)
{
const
modifiedPrevLeaf
=
BufferPiece
.
deleteLastChar
(
prevLeaf
);
leafs
[
leafs
.
length
-
1
]
=
modifiedPrevLeaf
;
const
modifiedLeaf
=
BufferPiece
.
insertFirstChar
(
leaf
,
lastChar
);
leaf
=
modifiedLeaf
;
}
leafs
.
push
(
leaf
);
prevLeaf
=
leaf
;
return
prevLeaf
;
}
private
static
_compareEdits
(
a
:
OffsetLenEdit
,
b
:
OffsetLenEdit
):
number
{
if
(
a
.
offset
===
b
.
offset
)
{
if
(
a
.
length
===
b
.
length
)
{
return
(
a
.
initialIndex
-
b
.
initialIndex
);
}
return
(
a
.
length
-
b
.
length
);
}
return
a
.
offset
-
b
.
offset
;
}
public
replaceOffsetLen
(
_edits
:
OffsetLenEdit
[]):
void
{
_edits
.
sort
(
Buffer
.
_compareEdits
);
const
initialLeafLength
=
this
.
_leafs
.
length
;
const
edits
=
this
.
_resolveEdits
(
_edits
);
let
accumulatedLeafIndex
=
0
;
let
accumulatedLeafEdits
:
LeafOffsetLenEdit
[]
=
[];
let
replacements
:
LeafReplacement
[]
=
[];
for
(
let
i
=
0
,
len
=
edits
.
length
;
i
<
len
;
i
++
)
{
const
edit
=
edits
[
i
];
const
startLeafIndex
=
edit
.
startLeafIndex
;
const
endLeafIndex
=
edit
.
endLeafIndex
;
if
(
startLeafIndex
!==
accumulatedLeafIndex
)
{
this
.
_flushLeafEdits
(
accumulatedLeafIndex
,
accumulatedLeafEdits
,
replacements
);
accumulatedLeafIndex
=
startLeafIndex
;
}
const
leafEditStart
=
edit
.
startInnerOffset
;
const
leafEditEnd
=
(
startLeafIndex
===
endLeafIndex
?
edit
.
endInnerOffset
:
this
.
_leafs
[
startLeafIndex
].
length
());
this
.
_pushLeafEdits
(
leafEditStart
,
leafEditEnd
-
leafEditStart
,
edit
.
text
,
accumulatedLeafEdits
);
if
(
startLeafIndex
<
endLeafIndex
)
{
this
.
_flushLeafEdits
(
accumulatedLeafIndex
,
accumulatedLeafEdits
,
replacements
);
accumulatedLeafIndex
=
endLeafIndex
;
// delete leafs in the middle
if
(
startLeafIndex
+
1
<
endLeafIndex
)
{
this
.
_pushLeafReplacement
(
startLeafIndex
+
1
,
endLeafIndex
-
1
,
replacements
);
}
// delete on last leaf
const
leafEditStart
=
0
;
const
leafEditEnd
=
edit
.
endInnerOffset
;
this
.
_pushLeafEdits
(
leafEditStart
,
leafEditEnd
-
leafEditStart
,
''
,
accumulatedLeafEdits
);
}
}
this
.
_flushLeafEdits
(
accumulatedLeafIndex
,
accumulatedLeafEdits
,
replacements
);
let
leafs
:
BufferPiece
[]
=
[];
let
leafIndex
=
0
;
let
prevLeaf
:
BufferPiece
=
null
;
for
(
let
i
=
0
,
len
=
replacements
.
length
;
i
<
len
;
i
++
)
{
const
replaceStartLeafIndex
=
replacements
[
i
].
startLeafIndex
;
const
replaceEndLeafIndex
=
replacements
[
i
].
endLeafIndex
;
const
innerLeafs
=
replacements
[
i
].
replacements
;
// add leafs to the left of this replace op.
while
(
leafIndex
<
replaceStartLeafIndex
)
{
prevLeaf
=
this
.
_appendLeaf
(
this
.
_leafs
[
leafIndex
],
leafs
,
prevLeaf
);
leafIndex
++
;
}
// delete leafs that get replaced.
while
(
leafIndex
<=
replaceEndLeafIndex
)
{
leafIndex
++
;
}
// add new leafs.
for
(
let
j
=
0
,
lenJ
=
innerLeafs
.
length
;
j
<
lenJ
;
j
++
)
{
prevLeaf
=
this
.
_appendLeaf
(
innerLeafs
[
j
],
leafs
,
prevLeaf
);
}
}
// add remaining leafs to the right of the last replacement.
while
(
leafIndex
<
initialLeafLength
)
{
prevLeaf
=
this
.
_appendLeaf
(
this
.
_leafs
[
leafIndex
],
leafs
,
prevLeaf
);
leafIndex
++
;
}
if
(
leafs
.
length
===
0
)
{
// don't leave behind an empty leafs array
leafs
.
push
(
new
BufferPiece
(
''
));
}
this
.
_leafs
=
leafs
;
this
.
_rebuildNodes
();
}
public
setEOL
(
newEOL
:
'
\r\n
'
|
'
\n
'
):
void
{
let
leafs
:
BufferPiece
[]
=
[];
for
(
let
i
=
0
,
len
=
this
.
_leafs
.
length
;
i
<
len
;
i
++
)
{
leafs
[
i
]
=
BufferPiece
.
normalizeEOL
(
this
.
_leafs
[
i
],
newEOL
);
}
this
.
_leafs
=
leafs
;
this
.
_rebuildNodes
();
this
.
_eol
=
newEOL
;
this
.
_eolLength
=
this
.
_eol
.
length
;
}
//#endregion
private
IS_NODE
(
i
:
number
):
boolean
{
return
(
i
<
this
.
_nodesCount
);
}
private
IS_LEAF
(
i
:
number
):
boolean
{
return
(
i
>=
this
.
_leafsStart
&&
i
<
this
.
_leafsEnd
);
}
private
NODE_TO_LEAF_INDEX
(
i
:
number
):
number
{
return
(
i
-
this
.
_leafsStart
);
}
// private LEAF_TO_NODE_INDEX(i: number): number {
// return (i + this._leafsStart);
// }
}
function
log2
(
n
:
number
):
number
{
let
v
=
1
;
for
(
let
pow
=
1
;
;
pow
++
)
{
v
=
v
<<
1
;
if
(
v
>=
n
)
{
return
pow
;
}
}
// return -1;
}
function
LEFT_CHILD
(
i
:
number
):
number
{
return
(
i
<<
1
);
}
function
RIGHT_CHILD
(
i
:
number
):
number
{
return
(
i
<<
1
)
+
1
;
}
src/vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder.ts
已删除
100644 → 0
浏览文件 @
ef7763a2
/*---------------------------------------------------------------------------------------------
* 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
*
as
strings
from
'
vs/base/common/strings
'
;
import
{
ITextBufferBuilder
,
ITextBufferFactory
,
ITextBuffer
,
DefaultEndOfLine
}
from
'
vs/editor/common/model
'
;
import
{
BufferPiece
,
createLineStarts
}
from
'
vs/editor/common/model/chunksTextBuffer/bufferPiece
'
;
import
{
ChunksTextBuffer
}
from
'
vs/editor/common/model/chunksTextBuffer/chunksTextBuffer
'
;
import
{
CharCode
}
from
'
vs/base/common/charCode
'
;
export
class
TextBufferFactory
implements
ITextBufferFactory
{
constructor
(
private
readonly
_pieces
:
BufferPiece
[],
private
readonly
_averageChunkSize
:
number
,
private
readonly
_BOM
:
string
,
private
readonly
_cr
:
number
,
private
readonly
_lf
:
number
,
private
readonly
_crlf
:
number
,
private
readonly
_containsRTL
:
boolean
,
private
readonly
_isBasicASCII
:
boolean
,
)
{
}
/**
* if text source is empty or with precisely one line, returns null. No end of line is detected.
* if text source contains more lines ending with '\r\n', returns '\r\n'.
* Otherwise returns '\n'. More lines end with '\n'.
*/
private
_getEOL
(
defaultEOL
:
DefaultEndOfLine
):
'
\r\n
'
|
'
\n
'
{
const
totalEOLCount
=
this
.
_cr
+
this
.
_lf
+
this
.
_crlf
;
const
totalCRCount
=
this
.
_cr
+
this
.
_crlf
;
if
(
totalEOLCount
===
0
)
{
// This is an empty file or a file with precisely one line
return
(
defaultEOL
===
DefaultEndOfLine
.
LF
?
'
\n
'
:
'
\r\n
'
);
}
if
(
totalCRCount
>
totalEOLCount
/
2
)
{
// More than half of the file contains \r\n ending lines
return
'
\r\n
'
;
}
// At least one line more ends in \n
return
'
\n
'
;
}
public
create
(
defaultEOL
:
DefaultEndOfLine
):
ITextBuffer
{
const
eol
=
this
.
_getEOL
(
defaultEOL
);
let
pieces
=
this
.
_pieces
;
if
(
(
eol
===
'
\r\n
'
&&
(
this
.
_cr
>
0
||
this
.
_lf
>
0
))
||
(
eol
===
'
\n
'
&&
(
this
.
_cr
>
0
||
this
.
_crlf
>
0
))
)
{
// Normalize pieces
for
(
let
i
=
0
,
len
=
pieces
.
length
;
i
<
len
;
i
++
)
{
pieces
[
i
]
=
BufferPiece
.
normalizeEOL
(
pieces
[
i
],
eol
);
}
}
return
new
ChunksTextBuffer
(
pieces
,
this
.
_averageChunkSize
,
this
.
_BOM
,
eol
,
this
.
_containsRTL
,
this
.
_isBasicASCII
);
}
public
getFirstLineText
(
lengthLimit
:
number
):
string
{
const
firstPiece
=
this
.
_pieces
[
0
];
if
(
firstPiece
.
newLineCount
()
===
0
)
{
return
firstPiece
.
substr
(
0
,
lengthLimit
);
}
const
firstEOLOffset
=
firstPiece
.
lineStartFor
(
0
);
return
firstPiece
.
substr
(
0
,
Math
.
min
(
lengthLimit
,
firstEOLOffset
));
}
}
export
class
ChunksTextBufferBuilder
implements
ITextBufferBuilder
{
private
_rawPieces
:
BufferPiece
[];
private
_hasPreviousChar
:
boolean
;
private
_previousChar
:
number
;
private
_averageChunkSize
:
number
;
private
_tmpLineStarts
:
number
[];
private
BOM
:
string
;
private
cr
:
number
;
private
lf
:
number
;
private
crlf
:
number
;
private
containsRTL
:
boolean
;
private
isBasicASCII
:
boolean
;
constructor
()
{
this
.
_rawPieces
=
[];
this
.
_hasPreviousChar
=
false
;
this
.
_previousChar
=
0
;
this
.
_averageChunkSize
=
0
;
this
.
_tmpLineStarts
=
[];
this
.
BOM
=
''
;
this
.
cr
=
0
;
this
.
lf
=
0
;
this
.
crlf
=
0
;
this
.
containsRTL
=
false
;
this
.
isBasicASCII
=
true
;
}
public
acceptChunk
(
chunk
:
string
):
void
{
if
(
chunk
.
length
===
0
)
{
return
;
}
if
(
this
.
_rawPieces
.
length
===
0
)
{
if
(
strings
.
startsWithUTF8BOM
(
chunk
))
{
this
.
BOM
=
strings
.
UTF8_BOM_CHARACTER
;
chunk
=
chunk
.
substr
(
1
);
}
}
this
.
_averageChunkSize
=
(
this
.
_averageChunkSize
*
this
.
_rawPieces
.
length
+
chunk
.
length
)
/
(
this
.
_rawPieces
.
length
+
1
);
const
lastChar
=
chunk
.
charCodeAt
(
chunk
.
length
-
1
);
if
(
lastChar
===
CharCode
.
CarriageReturn
||
(
lastChar
>=
0xd800
&&
lastChar
<=
0xdbff
))
{
// last character is \r or a high surrogate => keep it back
this
.
_acceptChunk1
(
chunk
.
substr
(
0
,
chunk
.
length
-
1
),
false
);
this
.
_hasPreviousChar
=
true
;
this
.
_previousChar
=
lastChar
;
}
else
{
this
.
_acceptChunk1
(
chunk
,
false
);
this
.
_hasPreviousChar
=
false
;
this
.
_previousChar
=
lastChar
;
}
}
private
_acceptChunk1
(
chunk
:
string
,
allowEmptyStrings
:
boolean
):
void
{
if
(
!
allowEmptyStrings
&&
chunk
.
length
===
0
)
{
// Nothing to do
return
;
}
if
(
this
.
_hasPreviousChar
)
{
this
.
_acceptChunk2
(
chunk
+
String
.
fromCharCode
(
this
.
_previousChar
));
}
else
{
this
.
_acceptChunk2
(
chunk
);
}
}
private
_acceptChunk2
(
chunk
:
string
):
void
{
const
lineStarts
=
createLineStarts
(
this
.
_tmpLineStarts
,
chunk
);
this
.
_rawPieces
.
push
(
new
BufferPiece
(
chunk
,
lineStarts
.
lineStarts
));
this
.
cr
+=
lineStarts
.
cr
;
this
.
lf
+=
lineStarts
.
lf
;
this
.
crlf
+=
lineStarts
.
crlf
;
if
(
this
.
isBasicASCII
)
{
this
.
isBasicASCII
=
lineStarts
.
isBasicASCII
;
}
if
(
!
this
.
isBasicASCII
&&
!
this
.
containsRTL
)
{
// No need to check if is basic ASCII
this
.
containsRTL
=
strings
.
containsRTL
(
chunk
);
}
}
public
finish
():
TextBufferFactory
{
this
.
_finish
();
return
new
TextBufferFactory
(
this
.
_rawPieces
,
this
.
_averageChunkSize
,
this
.
BOM
,
this
.
cr
,
this
.
lf
,
this
.
crlf
,
this
.
containsRTL
,
this
.
isBasicASCII
);
}
private
_finish
():
void
{
if
(
this
.
_rawPieces
.
length
===
0
)
{
// no chunks => forcefully go through accept chunk
this
.
_acceptChunk1
(
''
,
true
);
return
;
}
if
(
this
.
_hasPreviousChar
)
{
this
.
_hasPreviousChar
=
false
;
// recreate last chunk
const
lastPiece
=
this
.
_rawPieces
[
this
.
_rawPieces
.
length
-
1
];
const
tmp
=
new
BufferPiece
(
String
.
fromCharCode
(
this
.
_previousChar
));
const
newLastPiece
=
BufferPiece
.
join
(
lastPiece
,
tmp
);
this
.
_rawPieces
[
this
.
_rawPieces
.
length
-
1
]
=
newLastPiece
;
if
(
this
.
_previousChar
===
CharCode
.
CarriageReturn
)
{
this
.
cr
++
;
}
}
}
}
src/vs/editor/common/model/textModel.ts
浏览文件 @
805d0785
...
...
@@ -35,12 +35,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
import
{
IStringStream
,
ITextSnapshot
}
from
'
vs/platform/files/common/files
'
;
import
{
LinesTextBufferBuilder
}
from
'
vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder
'
;
import
{
PieceTreeTextBufferBuilder
}
from
'
vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder
'
;
import
{
ChunksTextBufferBuilder
}
from
'
vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder
'
;
export
enum
TextBufferType
{
LinesArray
,
PieceTree
,
Chunks
PieceTree
}
// Here is the master switch for the text buffer implementation:
export
const
OPTIONS
=
{
...
...
@@ -51,9 +49,6 @@ function createTextBufferBuilder() {
if
(
OPTIONS
.
TEXT_BUFFER_IMPLEMENTATION
===
TextBufferType
.
PieceTree
)
{
return
new
PieceTreeTextBufferBuilder
();
}
if
(
OPTIONS
.
TEXT_BUFFER_IMPLEMENTATION
===
TextBufferType
.
Chunks
)
{
return
new
ChunksTextBufferBuilder
();
}
return
new
LinesTextBufferBuilder
();
}
...
...
src/vs/editor/test/common/model/benchmark/benchmarkUtils.ts
浏览文件 @
805d0785
...
...
@@ -6,7 +6,6 @@
import
{
ITextBufferBuilder
,
ITextBufferFactory
,
ITextBuffer
,
DefaultEndOfLine
}
from
'
vs/editor/common/model
'
;
import
{
LinesTextBufferBuilder
}
from
'
vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder
'
;
import
{
PieceTreeTextBufferBuilder
}
from
'
vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder
'
;
import
{
ChunksTextBufferBuilder
}
from
'
vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder
'
;
export
function
doBenchmark
<
T
>
(
id
:
string
,
ts
:
T
[],
fn
:
(
t
:
T
)
=>
void
)
{
let
columns
:
string
[]
=
[
id
];
...
...
@@ -57,7 +56,7 @@ export class BenchmarkSuite {
for
(
let
i
=
0
;
i
<
this
.
benchmarks
.
length
;
i
++
)
{
let
benchmark
=
this
.
benchmarks
[
i
];
let
columns
:
string
[]
=
[
benchmark
.
name
];
[
new
LinesTextBufferBuilder
(),
new
PieceTreeTextBufferBuilder
()
,
new
ChunksTextBufferBuilder
()
].
forEach
((
builder
:
ITextBufferBuilder
)
=>
{
[
new
LinesTextBufferBuilder
(),
new
PieceTreeTextBufferBuilder
()].
forEach
((
builder
:
ITextBufferBuilder
)
=>
{
let
timeDiffTotal
=
0.0
;
for
(
let
j
=
0
;
j
<
this
.
iterations
;
j
++
)
{
let
factory
=
benchmark
.
buildBuffer
(
builder
);
...
...
src/vs/editor/test/common/model/chunksTextBuffer/bufferPiece.test.ts
已删除
100644 → 0
浏览文件 @
ef7763a2
/*---------------------------------------------------------------------------------------------
* 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
*
as
assert
from
'
assert
'
;
import
{
BufferPiece
}
from
'
vs/editor/common/model/chunksTextBuffer/bufferPiece
'
;
suite
(
'
BufferPiece
'
,
()
=>
{
test
(
'
findLineStartBeforeOffset
'
,
()
=>
{
let
piece
=
new
BufferPiece
([
'
Line1
\r\n
'
,
'
l2
\n
'
,
'
another
\r
'
,
'
and
\r\n
'
,
'
finally
\n
'
,
'
last
'
].
join
(
''
));
assert
.
equal
(
piece
.
length
(),
35
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
0
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
1
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
2
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
3
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
4
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
5
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
6
),
-
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
7
),
0
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
8
),
0
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
9
),
0
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
10
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
11
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
12
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
13
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
14
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
15
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
16
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
17
),
1
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
18
),
2
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
19
),
2
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
20
),
2
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
21
),
2
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
22
),
2
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
23
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
24
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
25
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
26
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
27
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
28
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
29
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
30
),
3
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
31
),
4
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
32
),
4
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
33
),
4
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
34
),
4
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
35
),
4
);
assert
.
deepEqual
(
piece
.
findLineStartBeforeOffset
(
36
),
4
);
});
});
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录