@@ -335,7 +335,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
335335
336336 // Return if the target is not on the tabs container
337337 if ( e . target !== tabsContainer ) {
338- this . updateDropFeedback ( tabsContainer , false ) ; // fixes https://github.com/microsoft/vscode/issues/52093
339338 return ;
340339 }
341340
@@ -348,49 +347,31 @@ export class MultiEditorTabsControl extends EditorTabsControl {
348347 return ;
349348 }
350349
351- // Return if dragged editor is last tab because then this is a no-op
352- let isLocalDragAndDrop = false ;
353- if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
354- isLocalDragAndDrop = true ;
355-
356- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
357- if ( Array . isArray ( data ) ) {
358- const localDraggedEditor = data [ 0 ] . identifier ;
359- if ( this . groupView . id === localDraggedEditor . groupId && this . tabsModel . isLast ( localDraggedEditor . editor ) ) {
360- if ( e . dataTransfer ) {
361- e . dataTransfer . dropEffect = 'none' ;
362- }
363-
364- return ;
365- }
366- }
367- }
368-
369350 // Update the dropEffect to "copy" if there is no local data to be dragged because
370351 // in that case we can only copy the data into and not move it from its source
371- if ( ! isLocalDragAndDrop ) {
352+ if ( ! this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
372353 if ( e . dataTransfer ) {
373354 e . dataTransfer . dropEffect = 'copy' ;
374355 }
375356 }
376357
377- this . updateDropFeedback ( tabsContainer , true ) ;
358+ this . updateDropFeedback ( tabsContainer , true , e ) ;
378359 } ,
379360
380361 onDragLeave : e => {
381- this . updateDropFeedback ( tabsContainer , false ) ;
362+ this . updateDropFeedback ( tabsContainer , false , e ) ;
382363 tabsContainer . classList . remove ( 'scroll' ) ;
383364 } ,
384365
385366 onDragEnd : e => {
386- this . updateDropFeedback ( tabsContainer , false ) ;
367+ this . updateDropFeedback ( tabsContainer , false , e ) ;
387368 tabsContainer . classList . remove ( 'scroll' ) ;
388369
389370 this . onGroupDragEnd ( e , lastDragEvent , tabsContainer , isNewWindowOperation ) ;
390371 } ,
391372
392373 onDrop : e => {
393- this . updateDropFeedback ( tabsContainer , false ) ;
374+ this . updateDropFeedback ( tabsContainer , false , e ) ;
394375 tabsContainer . classList . remove ( 'scroll' ) ;
395376
396377 if ( e . target === tabsContainer ) {
@@ -1038,14 +1019,13 @@ export class MultiEditorTabsControl extends EditorTabsControl {
10381019
10391020 if ( e . dataTransfer ) {
10401021 e . dataTransfer . effectAllowed = 'copyMove' ;
1022+ e . dataTransfer . setDragImage ( tab , 0 , 0 ) ; // top left corner of dragged tab set to cursor position to make room for drop-border feedback
10411023 }
10421024
10431025 // Apply some datatransfer types to allow for dragging the element outside of the application
10441026 this . doFillResourceDataTransfers ( [ editor ] , e , isNewWindowOperation ) ;
10451027
1046- // Fixes https://github.com/microsoft/vscode/issues/18733
1047- tab . classList . add ( 'dragged' ) ;
1048- scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => tab . classList . remove ( 'dragged' ) ) ;
1028+ scheduleAtNextAnimationFrame ( getWindow ( this . parent ) , ( ) => this . updateDropFeedback ( tab , false , e , tabIndex ) ) ;
10491029 } ,
10501030
10511031 onDrag : e => {
@@ -1054,9 +1034,6 @@ export class MultiEditorTabsControl extends EditorTabsControl {
10541034
10551035 onDragEnter : e => {
10561036
1057- // Update class to signal drag operation
1058- tab . classList . add ( 'dragged-over' ) ;
1059-
10601037 // Return if transfer is unsupported
10611038 if ( ! this . isSupportedDropTransfer ( e ) ) {
10621039 if ( e . dataTransfer ) {
@@ -1066,52 +1043,30 @@ export class MultiEditorTabsControl extends EditorTabsControl {
10661043 return ;
10671044 }
10681045
1069- // Return if dragged editor is the current tab dragged over
1070- let isLocalDragAndDrop = false ;
1071- if ( this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
1072- isLocalDragAndDrop = true ;
1073-
1074- const data = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1075- if ( Array . isArray ( data ) ) {
1076- const localDraggedEditor = data [ 0 ] . identifier ;
1077- if ( localDraggedEditor . editor === this . tabsModel . getEditorByIndex ( tabIndex ) && localDraggedEditor . groupId === this . groupView . id ) {
1078- if ( e . dataTransfer ) {
1079- e . dataTransfer . dropEffect = 'none' ;
1080- }
1081-
1082- return ;
1083- }
1084- }
1085- }
1086-
10871046 // Update the dropEffect to "copy" if there is no local data to be dragged because
10881047 // in that case we can only copy the data into and not move it from its source
1089- if ( ! isLocalDragAndDrop ) {
1048+ if ( ! this . editorTransfer . hasData ( DraggedEditorIdentifier . prototype ) ) {
10901049 if ( e . dataTransfer ) {
10911050 e . dataTransfer . dropEffect = 'copy' ;
10921051 }
10931052 }
10941053
1095- this . updateDropFeedback ( tab , true , tabIndex ) ;
1054+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
10961055 } ,
10971056
1098- onDragOver : ( _ , dragDuration ) => {
1057+ onDragOver : ( e , dragDuration ) => {
10991058 if ( dragDuration >= MultiEditorTabsControl . DRAG_OVER_OPEN_TAB_THRESHOLD ) {
11001059 const draggedOverTab = this . tabsModel . getEditorByIndex ( tabIndex ) ;
11011060 if ( draggedOverTab && this . tabsModel . activeEditor !== draggedOverTab ) {
11021061 this . groupView . openEditor ( draggedOverTab , { preserveFocus : true } ) ;
11031062 }
11041063 }
1105- } ,
11061064
1107- onDragLeave : ( ) => {
1108- tab . classList . remove ( 'dragged-over' ) ;
1109- this . updateDropFeedback ( tab , false , tabIndex ) ;
1065+ this . updateDropFeedback ( tab , true , e , tabIndex ) ;
11101066 } ,
11111067
11121068 onDragEnd : async e => {
1113- tab . classList . remove ( 'dragged-over' ) ;
1114- this . updateDropFeedback ( tab , false , tabIndex ) ;
1069+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
11151070
11161071 this . editorTransfer . clearData ( DraggedEditorIdentifier . prototype ) ;
11171072
@@ -1140,10 +1095,31 @@ export class MultiEditorTabsControl extends EditorTabsControl {
11401095 } ,
11411096
11421097 onDrop : e => {
1143- tab . classList . remove ( 'dragged-over' ) ;
1144- this . updateDropFeedback ( tab , false , tabIndex ) ;
1098+ this . updateDropFeedback ( tab , false , e , tabIndex ) ;
1099+
1100+ // compute the target index
1101+ let targetIndex = tabIndex ;
1102+ if ( this . getTabDragOverLocation ( e , tab ) === 'right' ) {
1103+ targetIndex ++ ;
1104+ }
1105+
1106+ // If we are moving an editor inside the same group and it is
1107+ // located before the target index we need to reduce the index
1108+ // by one to account for the fact that the move will cause all
1109+ // subsequent tabs to move one to the left.
1110+ const editorIdentifiers = this . editorTransfer . getData ( DraggedEditorIdentifier . prototype ) ;
1111+ if ( editorIdentifiers !== undefined ) {
1112+ const draggedEditorIdentifier = editorIdentifiers [ 0 ] . identifier ;
1113+ const sourceGroup = this . editorPartsView . getGroup ( draggedEditorIdentifier . groupId ) ;
1114+ if ( sourceGroup ?. id === this . groupView . id ) {
1115+ const editorIndex = sourceGroup . getIndexOfEditor ( draggedEditorIdentifier . editor ) ;
1116+ if ( editorIndex < targetIndex ) {
1117+ targetIndex -- ;
1118+ }
1119+ }
1120+ }
11451121
1146- this . onDrop ( e , tabIndex , tabsContainer ) ;
1122+ this . onDrop ( e , targetIndex , tabsContainer ) ;
11471123 }
11481124 } ) ) ;
11491125
@@ -1174,28 +1150,73 @@ export class MultiEditorTabsControl extends EditorTabsControl {
11741150 return false ;
11751151 }
11761152
1177- private updateDropFeedback ( element : HTMLElement , isDND : boolean , tabIndex ?: number ) : void {
1153+ private updateDropFeedback ( element : HTMLElement , isDND : boolean , e : DragEvent , tabIndex ?: number ) : void {
11781154 const isTab = ( typeof tabIndex === 'number' ) ;
1179- const editor = typeof tabIndex === 'number' ? this . tabsModel . getEditorByIndex ( tabIndex ) : undefined ;
1180- const isActiveTab = isTab && ! ! editor && this . tabsModel . isActive ( editor ) ;
1181-
1182- // Background
1183- const noDNDBackgroundColor = isTab ? this . getColor ( isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND ) : '' ;
1184- element . style . backgroundColor = ( isDND ? this . getColor ( EDITOR_DRAG_AND_DROP_BACKGROUND ) : noDNDBackgroundColor ) || '' ;
1185-
1186- // Outline
1187- const activeContrastBorderColor = this . getColor ( activeContrastBorder ) ;
1188- if ( activeContrastBorderColor && isDND ) {
1189- element . style . outlineWidth = '2px' ;
1190- element . style . outlineStyle = 'dashed' ;
1191- element . style . outlineColor = activeContrastBorderColor ;
1192- element . style . outlineOffset = isTab ? '-5px' : '-3px' ;
1155+
1156+ let dropTarget ;
1157+ if ( isDND ) {
1158+ if ( isTab ) {
1159+ dropTarget = this . computeDropTarget ( e , tabIndex , element ) ;
1160+ } else {
1161+ dropTarget = { leftElement : element . lastElementChild as HTMLElement , rightElement : undefined } ;
1162+ }
11931163 } else {
1194- element . style . outlineWidth = '' ;
1195- element . style . outlineStyle = '' ;
1196- element . style . outlineColor = activeContrastBorderColor || '' ;
1197- element . style . outlineOffset = '' ;
1164+ dropTarget = undefined ;
11981165 }
1166+
1167+ this . updateDropTarget ( dropTarget ) ;
1168+ }
1169+
1170+ private dropTarget : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ;
1171+ private updateDropTarget ( newTarget : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined ) : void {
1172+ const oldTargets = this . dropTarget ;
1173+ if ( oldTargets === newTarget || oldTargets && newTarget && oldTargets . leftElement === newTarget . leftElement && oldTargets . rightElement === newTarget . rightElement ) {
1174+ return ;
1175+ }
1176+
1177+ const dropClassLeft = 'drop-target-left' ;
1178+ const dropClassRight = 'drop-target-right' ;
1179+
1180+ if ( oldTargets ) {
1181+ oldTargets . leftElement ?. classList . remove ( dropClassLeft ) ;
1182+ oldTargets . rightElement ?. classList . remove ( dropClassRight ) ;
1183+ }
1184+
1185+ if ( newTarget ) {
1186+ newTarget . leftElement ?. classList . add ( dropClassLeft ) ;
1187+ newTarget . rightElement ?. classList . add ( dropClassRight ) ;
1188+ }
1189+
1190+ this . dropTarget = newTarget ;
1191+ }
1192+
1193+ private getTabDragOverLocation ( e : DragEvent , tab : HTMLElement ) : 'left' | 'right' {
1194+ const rect = tab . getBoundingClientRect ( ) ;
1195+ const offsetXRelativeToParent = e . clientX - rect . left ;
1196+
1197+ return offsetXRelativeToParent <= rect . width / 2 ? 'left' : 'right' ;
1198+ }
1199+
1200+ private computeDropTarget ( e : DragEvent , tabIndex : number , targetTab : HTMLElement ) : { leftElement : HTMLElement | undefined ; rightElement : HTMLElement | undefined } | undefined {
1201+ const isLeftSideOfTab = this . getTabDragOverLocation ( e , targetTab ) === 'left' ;
1202+ const isLastTab = tabIndex === this . tabsModel . count - 1 ;
1203+ const isFirstTab = tabIndex === 0 ;
1204+
1205+ // Before first tab
1206+ if ( isLeftSideOfTab && isFirstTab ) {
1207+ return { leftElement : undefined , rightElement : targetTab } ;
1208+ }
1209+
1210+ // After last tab
1211+ if ( ! isLeftSideOfTab && isLastTab ) {
1212+ return { leftElement : targetTab , rightElement : undefined } ;
1213+ }
1214+
1215+ // Between two tabs
1216+ const tabBefore = isLeftSideOfTab ? targetTab . previousElementSibling : targetTab ;
1217+ const tabAfter = isLeftSideOfTab ? targetTab : targetTab . nextElementSibling ;
1218+
1219+ return { leftElement : tabBefore as HTMLElement , rightElement : tabAfter as HTMLElement } ;
11991220 }
12001221
12011222 private computeTabLabels ( ) : void {
@@ -1729,6 +1750,11 @@ export class MultiEditorTabsControl extends EditorTabsControl {
17291750 // positioned editor actions container when tabs wrap. The margin needs to
17301751 // be the width of the editor actions container to avoid screen cheese.
17311752 tabsContainer . style . setProperty ( '--last-tab-margin-right' , tabsWrapMultiLine ? `${ editorToolbarContainer . offsetWidth } px` : '0' ) ;
1753+
1754+ // Remove old css classes that are not needed anymore
1755+ for ( const tab of tabsContainer . children ) {
1756+ tab . classList . remove ( 'last-in-row' ) ;
1757+ }
17321758 }
17331759
17341760 // Setting enabled: selectively enable wrapping if possible
@@ -2044,7 +2070,7 @@ export class MultiEditorTabsControl extends EditorTabsControl {
20442070 private async onDrop ( e : DragEvent , targetTabIndex : number , tabsContainer : HTMLElement ) : Promise < void > {
20452071 EventHelper . stop ( e , true ) ;
20462072
2047- this . updateDropFeedback ( tabsContainer , false ) ;
2073+ this . updateDropFeedback ( tabsContainer , false , e , targetTabIndex ) ;
20482074 tabsContainer . classList . remove ( 'scroll' ) ;
20492075
20502076 const targetEditorIndex = this . tabsModel instanceof UnstickyEditorGroupModel ? targetTabIndex + this . groupView . stickyCount : targetTabIndex ;
0 commit comments