@@ -169,53 +169,50 @@ define(function (require, exports, module) {
169169 * @param {Array.<{line: number, ch: number}> } posArray
170170 */
171171 function _renderMarks ( editor , posArray ) {
172- const cm = editor . _codeMirror ;
173- const markerState = _getMarkerState ( editor ) ;
174- const $track = $ ( ".tickmark-track" , editor . getRootElement ( ) ) ;
175- const editorHt = cm . getScrollerElement ( ) . scrollHeight ;
172+ const cm = editor . _codeMirror ;
173+ const markerState = _getMarkerState ( editor ) ;
174+ const $track = $ ( ".tickmark-track" , editor . getRootElement ( ) ) ;
175+ const editorHt = cm . getScrollerElement ( ) . scrollHeight ;
176+ const wrapping = cm . getOption ( "lineWrapping" ) ;
176177
177- const wrapping = cm . getOption ( "lineWrapping" ) ;
178- const singleLineH = wrapping && cm . defaultTextHeight ( ) * 1.5 ;
179-
180- // For performance, precompute top for each mark
178+ // We'll collect all the normalized (top, bottom) positions here
181179 const markPositions = [ ] ;
182- let curLine = null , curLineObj = null ;
183-
184- function getY ( pos ) {
185- if ( curLine !== pos . line ) {
186- curLine = pos . line ;
187- curLineObj = cm . getLineHandle ( curLine ) ;
188- }
189- if ( wrapping && curLineObj && curLineObj . height > singleLineH ) {
190- return cm . charCoords ( pos , "local" ) . top ;
191- }
192- return cm . heightAtLine ( curLineObj , "local" ) ;
193- }
194180
195181 posArray . forEach ( function ( pos ) {
196- const y = getY ( pos ) ;
197- const ratio = editorHt ? ( y / editorHt ) : 0 ;
198- const top = Math . round ( ratio * markerState . trackHt ) + markerState . trackOffset - 1 ;
199-
200- let markerHeight = MARKER_HEIGHT_LINE ;
201- let isLine = true ;
202- if ( pos . options . trackStyle === TRACK_STYLES . ON_LEFT ) {
203- markerHeight = MARKER_HEIGHT_LEFT ;
204- isLine = false ;
205- }
182+ // Extract the style info
183+ const trackStyle = pos . options . trackStyle || TRACK_STYLES . LINE ;
184+ const cssColorClass = pos . options . cssColorClass || "" ;
185+
186+ // Decide which marker height to use
187+ const isLineMarker = ( trackStyle === TRACK_STYLES . LINE ) ;
188+ const markerHeight = isLineMarker ? MARKER_HEIGHT_LINE : MARKER_HEIGHT_LEFT ;
189+
190+ // We'll measure the 'start' of the range and the 'end' of the range
191+ const startPos = pos . start || pos ; // Fallback, in case it's single
192+ const endPos = pos . end || pos ; // Fallback, in case it's single
193+
194+ // Compute the top offset for the start
195+ const startY = _computeY ( startPos ) ;
196+ // Compute the top offset for the end
197+ const endY = _computeY ( endPos ) ;
198+
199+ // Put them in ascending order
200+ const topY = Math . min ( startY , endY ) ;
201+ const bottomY = Math . max ( startY , endY ) + markerHeight ;
206202
207203 markPositions . push ( {
208- top : top ,
209- bottom : top + markerHeight ,
210- isLine : isLine ,
211- cssColorClass : pos . options . cssColorClass || ""
204+ top : topY ,
205+ bottom : bottomY ,
206+ isLine : isLineMarker ,
207+ cssColorClass
212208 } ) ;
213209 } ) ;
214210
215- // Sort them by top coordinate
216- markPositions . sort ( function ( a , b ) { return a . top - b . top ; } ) ;
211+ // Merge/condense overlapping or adjacent segments, same as before
212+ markPositions . sort ( function ( a , b ) {
213+ return a . top - b . top ;
214+ } ) ;
217215
218- // Merge nearby or overlapping segments
219216 const mergedLineMarks = [ ] ;
220217 const mergedLeftMarks = [ ] ;
221218
@@ -234,20 +231,38 @@ define(function (require, exports, module) {
234231 mergedMarks . push ( mark ) ;
235232 } ) ;
236233
237- // Build HTML for horizontal marks
234+ // Now render them into the DOM
235+ // (1) For the "line" style
238236 let html = mergedLineMarks . map ( function ( m ) {
239- return `<div class='tickmark ${ m . cssColorClass } ' style='top: ${ m . top } px; height: ${ m . height } px;'></div>` ;
237+ return `<div class='tickmark ${ m . cssColorClass } '
238+ style='top: ${ m . top } px; height: ${ m . height } px;'></div>` ;
240239 } ) . join ( "" ) ;
241240 $track . append ( $ ( html ) ) ;
242241
243- // Build HTML for vertical marks
242+ // (2) For the "left" style
244243 html = mergedLeftMarks . map ( function ( m ) {
245- return `<div class='tickmark tickmark-side ${
246- m . cssColorClass } ' style='top: ${ m . top } px; height: ${ m . height } px;'></div>`;
244+ return `<div class='tickmark tickmark-side ${ m . cssColorClass } '
245+ style='top: ${ m . top } px; height: ${ m . height } px;'></div>` ;
247246 } ) . join ( "" ) ;
248247 $track . append ( $ ( html ) ) ;
248+
249+ /**
250+ * Helper function to compute Y offset for a given {line, ch} position
251+ */
252+ function _computeY ( cmPos ) {
253+ if ( wrapping ) {
254+ // For wrapped lines, measure the exact Y-position in the editor
255+ return cm . charCoords ( cmPos , "local" ) . top / editorHt * markerState . trackHt
256+ + markerState . trackOffset - 1 ;
257+ }
258+ // For unwrapped lines, we can do a simpler approach
259+ const cursorTop = cm . heightAtLine ( cmPos . line , "local" ) ;
260+ const ratio = editorHt ? ( cursorTop / editorHt ) : 0 ;
261+ return Math . round ( ratio * markerState . trackHt ) + markerState . trackOffset - 1 ;
262+ }
249263 }
250264
265+
251266 /**
252267 * Private helper: Show the track if it's not already visible.
253268 * @param {!Editor } editor
@@ -381,58 +396,143 @@ define(function (require, exports, module) {
381396 }
382397
383398 /**
384- * Adds tickmarks for the given positions into the editor's tickmark track.
399+ * Merges an array of tickmark ranges if they are adjacent or overlapping in lines.
400+ * All items are assumed to be in the shape:
401+ * {
402+ * start: { line: number, ch: number },
403+ * end: { line: number, ch: number },
404+ * options: Object
405+ * }
406+ *
407+ * @param {Array } markArray
408+ * @return {Array } A new array with merged ranges.
409+ */
410+ function _mergeMarks ( markArray ) {
411+ // 1) Sort by starting line (and ch if you want a finer sort)
412+ markArray . sort ( ( a , b ) => {
413+ if ( a . start . line !== b . start . line ) {
414+ return a . start . line - b . start . line ;
415+ }
416+ return a . start . ch - b . start . ch ;
417+ } ) ;
418+
419+ const merged = [ ] ;
420+ let current = null ;
421+
422+ for ( const mark of markArray ) {
423+ // If we're not currently building a merged range, start one
424+ if ( ! current ) {
425+ current = {
426+ start : { ...mark . start } ,
427+ end : { ...mark . end } ,
428+ options : mark . options
429+ } ;
430+ } else {
431+ // Check if the new mark is adjacent or overlaps the current range
432+ // i.e. if mark's start is <= current's end.line + 1
433+ if ( mark . start . line <= current . end . line + 1 ) {
434+ // Merge them by extending current.end if needed
435+ if ( mark . end . line > current . end . line ) {
436+ current . end . line = mark . end . line ;
437+ current . end . ch = mark . end . ch ;
438+ } else if ( mark . end . line === current . end . line && mark . end . ch > current . end . ch ) {
439+ current . end . ch = mark . end . ch ;
440+ }
441+ // If you need to unify other fields (like color classes),
442+ // decide how to handle current.options vs. mark.options here.
443+ } else {
444+ // Not adjacent => push the old range and start a fresh one
445+ merged . push ( current ) ;
446+ current = {
447+ start : { ...mark . start } ,
448+ end : { ...mark . end } ,
449+ options : mark . options
450+ } ;
451+ }
452+ }
453+ }
454+
455+ // Flush any final in-progress range
456+ if ( current ) {
457+ merged . push ( current ) ;
458+ }
459+
460+ return merged ;
461+ }
462+
463+ /**
464+ * Adds tickmarks or range-markers for the given positions (or ranges) into the editor's tickmark track.
385465 * If the track was not visible and new marks are added, it is automatically shown.
466+ *
386467 * @param {!Editor } editor
387- * @param {Array.<{line: number, ch: number}> } posArray
468+ * @param {Array.<{line: number, ch: number} | {start: {line, ch}, end: {line, ch}}> } posArray
469+ * Each element can be:
470+ * (A) a single point: `{ line: number, ch: number }`, or
471+ * (B) a range: `{ start: { line, ch }, end: { line, ch } }`
388472 * @param {Object } [options]
389- * @param {string } [options.name] you can assign a name to marks and then use this name to selectively
390- * clear these marks .
391- * @param {string } [options.trackStyle] one of TRACK_STYLES.*
392- * @param {string } [options.cssColorClass] a css class that should override the --mark-color css var .
473+ * @param {string } [options.name] Optionally assign a name to these marks and later selectively clear them.
474+ * @param { string } [options.trackStyle] one of TRACK_STYLES.* (e.g., "line" or "left") .
475+ * @param {string } [options.cssColorClass] A CSS class that can override or extend styling.
476+ * @param {string } [options.dontMerge] If set to true, will not merge nearby lines in posArray to single mark .
393477 */
394478 function addTickmarks ( editor , posArray , options = { } ) {
395479 const markerState = _getMarkerState ( editor ) ;
396480 if ( ! markerState ) {
397481 return ;
398482 }
399483
400- // Make sure we have a valid editor instance
401- if ( ! editor ) {
402- console . error ( "Calling ScrollTrackMarkers.addTickmarks without an editor is deprecated." ) ;
403- editor = EditorManager . getActiveEditor ( ) ;
404- }
405-
406- // If track was empty before, note it
484+ // Keep track of whether the track was empty before adding marks
407485 const wasEmpty = ( markerState . marks . length === 0 ) ;
408486
409- // Normalize the new positions to include the same options object
410- const newPosArray = posArray . map ( pos => ( { ...pos , options } ) ) ;
487+ // Normalize each incoming item so that every mark has both {start, end} internally
488+ const newMarks = posArray . map ( pos => {
489+ // If this looks like { start: {...}, end: {...} }, use it directly
490+ if ( pos . start && pos . end ) {
491+ return {
492+ start : pos . start ,
493+ end : pos . end ,
494+ options
495+ } ;
496+ }
411497
412- // Concat the new positions
413- markerState . marks = markerState . marks . concat ( newPosArray ) ;
498+ // Otherwise assume it's a single point { line, ch }
499+ // Treat it as a zero-length range
500+ return {
501+ start : pos ,
502+ end : pos ,
503+ options
504+ } ;
505+ } ) ;
414506
415- // If we were empty and now have tickmarks, show track
507+ const mergedMarks = options . dontMerge ? newMarks : _mergeMarks ( newMarks ) ;
508+
509+ // Concat the new marks onto the existing marks
510+ markerState . marks = markerState . marks . concat ( mergedMarks ) ;
511+
512+ // If we were empty and now have marks, show the scroll track
416513 if ( wasEmpty && markerState . marks . length > 0 ) {
417514 _showTrack ( editor ) ;
418515 }
419516
420- // If track is visible, re-render
517+ // If the track is visible, re-render everything
421518 if ( markerState . visible ) {
422519 $ ( ".tickmark-track" , editor . getRootElement ( ) ) . empty ( ) ;
423520 _renderMarks ( editor , markerState . marks ) ;
424521 }
425522 }
426523
524+
427525 /**
428526 * Highlights the "current" tickmark at the given index (into the marks array provided to addTickmarks),
429527 * or clears if `index === -1`.
430528 * @param {number } index
431529 * @param {!Editor } editor
530+ * @private
432531 */
433- function markCurrent ( index , editor ) {
532+ function _markCurrent ( index , editor ) {
434533 if ( ! editor ) {
435- throw new Error ( "Calling ScrollTrackMarkers.markCurrent without editor instance is deprecated." ) ;
534+ throw new Error (
535+ "Calling private API ScrollTrackMarkers._markCurrent without editor instance is deprecated." ) ;
436536 }
437537 const markerState = _getMarkerState ( editor ) ;
438538
@@ -446,7 +546,7 @@ define(function (require, exports, module) {
446546 }
447547
448548 // Position the highlight
449- const top = _getTop ( editor , markerState . marks [ index ] ) ;
549+ const top = _getTop ( editor , markerState . marks [ index ] . start ) ;
450550 const $currentTick = $ (
451551 `<div class='tickmark tickmark-current' style='top: ${ top } px; height: ${ MARKER_HEIGHT_LINE } px;'></div>`
452552 ) ;
@@ -468,11 +568,15 @@ define(function (require, exports, module) {
468568 // For unit tests
469569 exports . _getTickmarks = _getTickmarks ;
470570
571+ // private API
572+ exports . _markCurrent = _markCurrent ;
573+
574+ // deprecated public API
575+ exports . setVisible = setVisible ; // Deprecated
576+
471577 // Public API
578+ exports . addTickmarks = addTickmarks ;
472579 exports . clear = clear ;
473580 exports . clearAll = clearAll ;
474- exports . setVisible = setVisible ; // Deprecated
475- exports . addTickmarks = addTickmarks ;
476- exports . markCurrent = markCurrent ;
477581 exports . TRACK_STYLES = TRACK_STYLES ;
478582} ) ;
0 commit comments