@@ -24,8 +24,23 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
2424
2525 static PREFIX = ':' ;
2626
27- constructor ( private useZeroBasedOffset : { value : boolean } = { value : false } ) {
27+ private _useZeroBasedOffset : boolean ;
28+
29+ constructor ( private useZeroBasedOffsetSetting ?: { value : boolean } ) {
2830 super ( { canAcceptInBackground : true } ) ;
31+ this . _useZeroBasedOffset = useZeroBasedOffsetSetting ?. value === true ;
32+ }
33+
34+ private get useZeroBasedOffset ( ) {
35+ return this . _useZeroBasedOffset ;
36+ }
37+
38+ private set useZeroBasedOffset ( value : boolean ) {
39+ this . _useZeroBasedOffset = value ;
40+ if ( this . useZeroBasedOffsetSetting ) {
41+ // Asynchronously persist the setting change
42+ this . useZeroBasedOffsetSetting . value = value ;
43+ }
2944 }
3045
3146 protected provideWithoutTextEditor ( picker : IQuickPick < IGotoLineQuickPickItem , { useSeparators : true } > ) : IDisposable {
@@ -45,7 +60,7 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
4560 disposables . add ( picker . onDidAccept ( event => {
4661 const [ item ] = picker . selectedItems ;
4762 if ( item ) {
48- if ( ! this . isValidLineNumber ( editor , item . lineNumber ) ) {
63+ if ( ! item . lineNumber ) {
4964 return ;
5065 }
5166
@@ -60,34 +75,29 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
6075 // React to picker changes
6176 const updatePickerAndEditor = ( ) => {
6277 const inputText = picker . value . trim ( ) . substring ( AbstractGotoLineQuickAccessProvider . PREFIX . length ) ;
63- const inOffsetMode = inputText . startsWith ( ':' ) ;
64- const position = this . parsePosition ( editor , inputText ) ;
65- const label = this . getPickLabel ( editor , position . lineNumber , position . column , inOffsetMode ) ;
78+ const { inOffsetMode, lineNumber, column, label } = this . parsePosition ( editor , inputText ) ;
6679
6780 // Show toggle only when input text starts with '::'.
68- toggle . visible = inOffsetMode ;
81+ toggle . visible = ! ! inOffsetMode ;
6982
7083 // Picker
7184 picker . items = [ {
72- lineNumber : position . lineNumber ,
73- column : position . column ,
85+ lineNumber,
86+ column,
7487 label,
75- detail : inputText . length ?
76- undefined : // Don't show hint once the user has started typing.
77- localize ( 'gotoLineQuickAccessDescription' , "Use :line[:column] or ::offset to go to a position. Negative values are counted from the end." )
7888 } ] ;
7989
8090 // ARIA Label
8191 picker . ariaLabel = label ;
8292
8393 // Clear decorations for invalid range
84- if ( ! this . isValidLineNumber ( editor , position . lineNumber ) ) {
94+ if ( ! lineNumber ) {
8595 this . clearDecorations ( editor ) ;
8696 return ;
8797 }
8898
8999 // Reveal
90- const range = this . toRange ( position . lineNumber , position . column ) ;
100+ const range = this . toRange ( lineNumber , column ) ;
91101 editor . revealRangeInCenter ( range , ScrollType . Smooth ) ;
92102
93103 // Decorate
@@ -96,17 +106,17 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
96106
97107 // Add a toggle to switch between 1- and 0-based offsets.
98108 const toggle = new Toggle ( {
99- title : localize ( 'gotoLineToggle' , "Use zero-based offset " ) ,
109+ title : localize ( 'gotoLineToggle' , "Use Zero-Based Offset " ) ,
100110 icon : Codicon . indexZero ,
101- isChecked : this . useZeroBasedOffset . value ,
111+ isChecked : this . useZeroBasedOffset ,
102112 inputActiveOptionBorder : asCssVariable ( inputActiveOptionBorder ) ,
103113 inputActiveOptionForeground : asCssVariable ( inputActiveOptionForeground ) ,
104114 inputActiveOptionBackground : asCssVariable ( inputActiveOptionBackground )
105115 } ) ;
106116
107117 disposables . add (
108118 toggle . onChange ( ( ) => {
109- this . useZeroBasedOffset . value = ! this . useZeroBasedOffset . value ;
119+ this . useZeroBasedOffset = ! this . useZeroBasedOffset ;
110120 updatePickerAndEditor ( ) ;
111121 } ) ) ;
112122
@@ -139,95 +149,78 @@ export abstract class AbstractGotoLineQuickAccessProvider extends AbstractEditor
139149 } ;
140150 }
141151
142- protected parsePosition ( editor : IEditor , value : string ) : IPosition {
152+ protected parsePosition ( editor : IEditor , value : string ) : Partial < IPosition > & { inOffsetMode ?: boolean ; label : string } {
143153 const model = this . getModel ( editor ) ;
154+ if ( ! model ) {
155+ return {
156+ label : localize ( 'gotoLine.noEditor' , "Open a text editor first to go to a line." )
157+ } ;
158+ }
144159
145160 // Support ::<offset> notation to navigate to a specific offset in the model.
146161 if ( value . startsWith ( ':' ) ) {
147162 let offset = parseInt ( value . substring ( 1 ) , 10 ) ;
148- if ( ! isNaN ( offset ) && model ) {
163+ const maxOffset = model . getValueLength ( ) ;
164+ if ( isNaN ( offset ) ) {
165+ // No valid offset specified.
166+ return {
167+ inOffsetMode : true ,
168+ label : localize ( 'gotoLine.offsetPrompt' , "Type a character number in the file from 1 to {0} to go to." , maxOffset )
169+ } ;
170+ } else {
149171 const reverse = offset < 0 ;
150- if ( ! this . useZeroBasedOffset . value ) {
172+ if ( ! this . useZeroBasedOffset ) {
151173 // Convert 1-based offset to model's 0-based.
152174 offset -= Math . sign ( offset ) ;
153175 }
154176 if ( reverse ) {
155177 // Offset from the end of the buffer
156- offset += model . getValueLength ( ) ;
178+ offset += maxOffset ;
157179 }
158- return model . getPositionAt ( offset ) ;
180+ const pos = model . getPositionAt ( offset ) ;
181+ return {
182+ ...pos ,
183+ inOffsetMode : true ,
184+ label : localize ( 'gotoLine.goToPosition' , "Press Enter to go to line {0} and column {1}." , pos . lineNumber , pos . column )
185+ } ;
186+ }
187+ } else {
188+ // Support line-col formats of `line,col`, `line:col`, `line#col`
189+ const parts = value . split ( / , | : | # / ) ;
190+
191+ const maxLine = model . getLineCount ( ) ;
192+ let lineNumber = parseInt ( parts [ 0 ] ?. trim ( ) , 10 ) ;
193+ if ( parts . length < 1 || isNaN ( lineNumber ) ) {
194+ return {
195+ label : localize ( 'gotoLine.linePrompt' , "Type a line number from 1 to {0} to go to." , maxLine )
196+ } ;
159197 }
160- }
161-
162- // Support line-col formats of `line,col`, `line:col`, `line#col`
163- let [ lineNumber , column ] = value . split ( / , | : | # / ) . map ( part => parseInt ( part , 10 ) ) . filter ( part => ! isNaN ( part ) ) ;
164198
165- // Handle negative line numbers and clip to valid range.
166- const maxLine = ( model ?. getLineCount ( ) ?? 0 ) + 1 ;
167- lineNumber = lineNumber >= 0 ? lineNumber : maxLine + lineNumber ;
168- lineNumber = Math . min ( Math . max ( 1 , lineNumber ) , maxLine ) ;
199+ // Handle negative line numbers and clip to valid range.
200+ lineNumber = lineNumber >= 0 ? lineNumber : ( maxLine + 1 ) + lineNumber ;
201+ lineNumber = Math . min ( Math . max ( 1 , lineNumber ) , maxLine ) ;
169202
170- // Handle negative column numbers and clip to valid range.
171- if ( column !== undefined && model ) {
172203 const maxColumn = model . getLineMaxColumn ( lineNumber ) ;
173- column = column >= 0 ? column : maxColumn + column ;
174- column = Math . min ( Math . max ( 1 , column ) , maxColumn ) ;
175- }
176-
177- return { lineNumber, column } ;
178- }
179-
180- private getPickLabel ( editor : IEditor , lineNumber : number , column : number | undefined , inOffsetMode : boolean ) : string {
181-
182- // Location valid: indicate this as picker label
183- if ( this . isValidLineNumber ( editor , lineNumber ) ) {
184- if ( this . isValidColumn ( editor , lineNumber , column ) ) {
185- return localize ( 'gotoLineColumnLabel' , "Go to line {0} and character {1}." , lineNumber , column ) ;
204+ let column = parseInt ( parts [ 1 ] ?. trim ( ) , 10 ) ;
205+ if ( parts . length < 2 || isNaN ( column ) ) {
206+ return {
207+ lineNumber,
208+ column : 1 ,
209+ label : parts . length < 2 ?
210+ localize ( 'gotoLine.lineColumnPrompt' , "Press Enter to go to line {0}. Type : to enter column number." , lineNumber ) :
211+ localize ( 'gotoLine.columnPrompt' , "Press Enter to go to line {0} or enter column number from 1 to {1}." , lineNumber , maxColumn )
212+ } ;
186213 }
187214
188- return localize ( 'gotoLineLabel' , "Go to line {0}." , lineNumber ) ;
189- }
190-
191- // Location invalid: show generic label
192- const position = editor . getPosition ( ) || { lineNumber : 1 , column : 1 } ;
193-
194- // When in offset mode, prompt for an offset.
195- if ( inOffsetMode ) {
196- return localize ( 'gotoLineOffsetLabel' , "Current Line: {0}, Character: {1}. Type a character offset to navigate to." , position . lineNumber , position . column ) ;
197- }
198-
199- const lineCount = this . lineCount ( editor ) ;
200- if ( lineCount > 1 ) {
201- return localize ( 'gotoLineLabelEmptyWithLimit' , "Current Line: {0}, Character: {1}. Type a line number between 1 and {2} to navigate to." , position . lineNumber , position . column , lineCount ) ;
202- }
203-
204- return localize ( 'gotoLineLabelEmpty' , "Current Line: {0}, Character: {1}. Type a line number to navigate to." , position . lineNumber , position . column ) ;
205- }
206-
207- private isValidLineNumber ( editor : IEditor , lineNumber : number | undefined ) : boolean {
208- if ( ! lineNumber || typeof lineNumber !== 'number' ) {
209- return false ;
210- }
211-
212- return lineNumber > 0 && lineNumber <= this . lineCount ( editor ) ;
213- }
214-
215- private isValidColumn ( editor : IEditor , lineNumber : number , column : number | undefined ) : boolean {
216- if ( ! column || typeof column !== 'number' ) {
217- return false ;
218- }
215+ // Handle negative column numbers and clip to valid range.
216+ column = column >= 0 ? column : maxColumn + column ;
217+ column = Math . min ( Math . max ( 1 , column ) , maxColumn ) ;
219218
220- const model = this . getModel ( editor ) ;
221- if ( ! model ) {
222- return false ;
219+ return {
220+ lineNumber,
221+ column,
222+ label : localize ( 'gotoLine.goToPosition' , "Press Enter to go to line {0} and column {1}." , lineNumber , column )
223+ } ;
223224 }
224-
225- const positionCandidate = { lineNumber, column } ;
226-
227- return model . validatePosition ( positionCandidate ) . equals ( positionCandidate ) ;
228- }
229-
230- private lineCount ( editor : IEditor ) : number {
231- return this . getModel ( editor ) ?. getLineCount ( ) ?? 0 ;
232225 }
233226}
0 commit comments