@@ -14,6 +14,7 @@ interface TextEditorPasteInput {
1414 pastedText : string
1515 currentText : string
1616 selections : readonly TextEditorPasteSelection [ ]
17+ multiCursorPaste ?: 'spread' | 'full'
1718}
1819
1920function normalizedSelections (
@@ -45,19 +46,58 @@ function mergedReplacementRanges(
4546 return ranges
4647}
4748
49+ function distributedPasteRanges (
50+ text : string ,
51+ selectionCount : number
52+ ) : TextEditorPasteSelection [ ] | null {
53+ if ( selectionCount <= 1 ) return null
54+
55+ let textEnd = text . length
56+ if ( text . charCodeAt ( textEnd - 1 ) === 10 ) textEnd -= 1
57+ if ( text . charCodeAt ( textEnd - 1 ) === 13 ) textEnd -= 1
58+
59+ const ranges : TextEditorPasteSelection [ ] = [ ]
60+ let lineStart = 0
61+ for ( let index = 0 ; index < textEnd ; index ++ ) {
62+ const code = text . charCodeAt ( index )
63+ if ( code !== 10 && code !== 13 ) continue
64+ ranges . push ( { start : lineStart , end : index } )
65+ if ( ranges . length >= selectionCount ) return null
66+ if ( code === 13 && text . charCodeAt ( index + 1 ) === 10 ) index += 1
67+ lineStart = index + 1
68+ }
69+ ranges . push ( { start : lineStart , end : textEnd } )
70+ return ranges . length === selectionCount ? ranges : null
71+ }
72+
4873/** Applies the workspace-file content contract to every selection in a projected Monaco paste. */
4974export function assessTextEditorPaste (
5075 input : TextEditorPasteInput ,
5176 maxBytes = PASTE_LIMITS . TEXT_EDITOR_BYTES
5277) : TextPasteAdmission {
5378 const selections = normalizedSelections ( input . selections , input . currentText . length )
5479 const replacementRanges = mergedReplacementRanges ( selections )
80+ const distributedRanges =
81+ ( input . multiCursorPaste ?? 'spread' ) === 'spread'
82+ ? distributedPasteRanges ( input . pastedText , selections . length )
83+ : null
5584 const replacedCharacters = replacementRanges . reduce (
5685 ( total , selection ) => total + selection . end - selection . start ,
5786 0
5887 )
59- const resultCharacters =
60- input . currentText . length - replacedCharacters + input . pastedText . length * selections . length
88+ const insertedCharacters = distributedRanges
89+ ? distributedRanges . reduce ( ( total , range ) => total + range . end - range . start , 0 )
90+ : input . pastedText . length * selections . length
91+ const resultCharacters = input . currentText . length - replacedCharacters + insertedCharacters
92+
93+ if ( input . pastedText . length > maxBytes ) {
94+ return {
95+ accepted : false ,
96+ reason : 'pasted-bytes' ,
97+ actual : input . pastedText . length ,
98+ limit : maxBytes ,
99+ }
100+ }
61101
62102 if ( resultCharacters <= Math . floor ( maxBytes / 3 ) ) {
63103 return { accepted : true , resultCharacters }
@@ -68,7 +108,13 @@ export function assessTextEditorPaste(
68108 return { accepted : false , reason : 'pasted-bytes' , actual : pastedBytes , limit : maxBytes }
69109 }
70110
71- const insertedBytes = pastedBytes * selections . length
111+ const insertedBytes = distributedRanges
112+ ? distributedRanges . reduce (
113+ ( total , range ) =>
114+ total + utf8ByteLengthRange ( input . pastedText , range . start , range . end , maxBytes - total ) ,
115+ 0
116+ )
117+ : pastedBytes * selections . length
72118 if ( insertedBytes > maxBytes ) {
73119 return { accepted : false , reason : 'result-bytes' , actual : insertedBytes , limit : maxBytes }
74120 }
0 commit comments