@@ -70,6 +70,28 @@ export interface CodeEditorProps {
7070 jumpToRange ?: import ( '@/component-library/components/Markdown' ) . LineRange ;
7171}
7272
73+ const LARGE_FILE_SIZE_THRESHOLD_BYTES = 1 * 1024 * 1024 ; // 1MB
74+ const LARGE_FILE_MAX_LINE_LENGTH = 20000 ;
75+ const LARGE_FILE_RENDER_LINE_LIMIT = 10000 ;
76+ const LARGE_FILE_MAX_TOKENIZATION_LINE_LENGTH = 2000 ;
77+ const LARGE_FILE_EXPANSION_LABELS = [ 'show more' , '显示更多' , '展开更多' ] ;
78+
79+ function hasVeryLongLine ( content : string , maxLineLength : number ) : boolean {
80+ let currentLineLength = 0 ;
81+ for ( let i = 0 ; i < content . length ; i ++ ) {
82+ const code = content . charCodeAt ( i ) ;
83+ if ( code === 10 || code === 13 ) {
84+ currentLineLength = 0 ;
85+ continue ;
86+ }
87+ currentLineLength ++ ;
88+ if ( currentLineLength >= maxLineLength ) {
89+ return true ;
90+ }
91+ }
92+ return false ;
93+ }
94+
7395const CodeEditor : React . FC < CodeEditorProps > = ( {
7496 filePath : rawFilePath ,
7597 workspacePath,
@@ -157,6 +179,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
157179 const [ statusBarPopover , setStatusBarPopover ] = useState < null | 'position' | 'indent' | 'encoding' | 'language' > ( null ) ;
158180 const [ statusBarAnchorRect , setStatusBarAnchorRect ] = useState < AnchorRect | null > ( null ) ;
159181 const [ encoding , setEncoding ] = useState < string > ( 'UTF-8' ) ;
182+ const [ largeFileMode , setLargeFileMode ] = useState ( false ) ;
160183 const containerRef = useRef < HTMLDivElement > ( null ) ;
161184 const editorRef = useRef < monaco . editor . IStandaloneCodeEditor | null > ( null ) ;
162185 const modelRef = useRef < monaco . editor . ITextModel | null > ( null ) ;
@@ -177,6 +200,53 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
177200 const delayedFontApplyTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
178201 const userLanguageOverrideRef = useRef ( false ) ;
179202 const userIndentRef = useRef < { tab_size : number ; insert_spaces : boolean } | null > ( null ) ;
203+ const largeFileModeRef = useRef ( false ) ;
204+ const largeFileExpansionBlockedLogRef = useRef ( false ) ;
205+
206+ const detectLargeFileMode = useCallback ( ( nextContent : string , fileSizeBytes ?: number ) : boolean => {
207+ const size = typeof fileSizeBytes === 'number' && fileSizeBytes >= 0
208+ ? fileSizeBytes
209+ : new Blob ( [ nextContent ] ) . size ;
210+ if ( size >= LARGE_FILE_SIZE_THRESHOLD_BYTES ) {
211+ return true ;
212+ }
213+ return hasVeryLongLine ( nextContent , LARGE_FILE_MAX_LINE_LENGTH ) ;
214+ } , [ ] ) ;
215+
216+ const updateLargeFileMode = useCallback ( ( nextContent : string , fileSizeBytes ?: number ) => {
217+ const nextMode = detectLargeFileMode ( nextContent , fileSizeBytes ) ;
218+ if ( largeFileModeRef . current !== nextMode ) {
219+ largeFileModeRef . current = nextMode ;
220+ setLargeFileMode ( nextMode ) ;
221+ log . info ( 'Editor performance mode changed' , {
222+ filePath,
223+ largeFileMode : nextMode ,
224+ fileSizeBytes : typeof fileSizeBytes === 'number' ? fileSizeBytes : undefined
225+ } ) ;
226+ }
227+ } , [ detectLargeFileMode , filePath ] ) ;
228+
229+ const shouldBlockLargeFileExpansionClick = useCallback ( ( target : EventTarget | null ) : boolean => {
230+ if ( ! ( target instanceof HTMLElement ) ) {
231+ return false ;
232+ }
233+
234+ if ( ! target . closest ( '.monaco-editor' ) ) {
235+ return false ;
236+ }
237+
238+ const clickable = target . closest ( 'a,button,[role="button"],.monaco-button' ) as HTMLElement | null ;
239+ const text = ( clickable ?. textContent ?? target . textContent ?? '' )
240+ . toLowerCase ( )
241+ . replace ( / \s + / g, ' ' )
242+ . trim ( ) ;
243+
244+ if ( ! text ) {
245+ return false ;
246+ }
247+
248+ return LARGE_FILE_EXPANSION_LABELS . some ( ( label ) => text . includes ( label ) ) ;
249+ } , [ ] ) ;
180250
181251 useEffect ( ( ) => {
182252 filePathRef . current = filePath ;
@@ -239,18 +309,31 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
239309 wordWrap : ( config . word_wrap as any ) || 'off' ,
240310 lineNumbers : config . line_numbers as any || 'on' ,
241311 minimap : {
242- enabled : showMinimap ,
312+ enabled : showMinimap && ! largeFileMode ,
243313 side : ( config . minimap ?. side as any ) || 'right' ,
244314 size : ( config . minimap ?. size as any ) || 'proportional'
245315 } ,
246316 cursorStyle : config . cursor_style as any || 'line' ,
247317 cursorBlinking : config . cursor_blinking as any || 'blink' ,
248- smoothScrolling : config . smooth_scrolling ?? true ,
318+ smoothScrolling : largeFileMode ? false : ( config . smooth_scrolling ?? true ) ,
249319 renderWhitespace : config . render_whitespace as any || 'none' ,
250320 renderLineHighlight : config . render_line_highlight as any || 'line' ,
251- bracketPairColorization : { enabled : config . bracket_pair_colorization ?? true } ,
321+ bracketPairColorization : { enabled : largeFileMode ? false : ( config . bracket_pair_colorization ?? true ) } ,
252322 formatOnPaste : config . format_on_paste ?? false ,
253323 trimAutoWhitespace : config . trim_auto_whitespace ?? true ,
324+ inlayHints : { enabled : largeFileMode ? 'off' : 'on' } ,
325+ quickSuggestions : largeFileMode
326+ ? { other : false , comments : false , strings : false }
327+ : { other : true , comments : false , strings : false } ,
328+ 'semanticHighlighting.enabled' : ! largeFileMode ,
329+ renderValidationDecorations : largeFileMode ? 'off' : 'on' ,
330+ largeFileOptimizations : true ,
331+ maxTokenizationLineLength : largeFileMode ? LARGE_FILE_MAX_TOKENIZATION_LINE_LENGTH : LARGE_FILE_MAX_LINE_LENGTH ,
332+ occurrencesHighlight : largeFileMode ? 'off' : 'singleFile' ,
333+ selectionHighlight : ! largeFileMode ,
334+ matchBrackets : largeFileMode ? 'never' : 'always' ,
335+ disableMonospaceOptimizations : ! largeFileMode ,
336+ stopRenderingLineAfter : largeFileMode ? LARGE_FILE_RENDER_LINE_LIMIT : - 1 ,
254337 } ) ;
255338 }
256339 } ;
@@ -279,13 +362,42 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
279362 return ( ) => {
280363 globalEventBus . off ( 'editor:config:changed' , handleConfigChange ) ;
281364 } ;
282- } , [ showMinimap ] ) ;
365+ } , [ showMinimap , largeFileMode ] ) ;
366+
367+ useEffect ( ( ) => {
368+ const container = containerRef . current ;
369+ if ( ! container || ! largeFileMode ) {
370+ return ;
371+ }
372+
373+ const blockLargeFileExpansion = ( event : MouseEvent ) => {
374+ if ( ! shouldBlockLargeFileExpansionClick ( event . target ) ) {
375+ return ;
376+ }
377+
378+ event . preventDefault ( ) ;
379+ event . stopPropagation ( ) ;
380+ event . stopImmediatePropagation ( ) ;
381+
382+ if ( ! largeFileExpansionBlockedLogRef . current ) {
383+ largeFileExpansionBlockedLogRef . current = true ;
384+ log . info ( 'Blocked long-line expansion in large file mode' , { filePath } ) ;
385+ }
386+ } ;
387+
388+ container . addEventListener ( 'mousedown' , blockLargeFileExpansion , true ) ;
389+ container . addEventListener ( 'click' , blockLargeFileExpansion , true ) ;
390+ return ( ) => {
391+ container . removeEventListener ( 'mousedown' , blockLargeFileExpansion , true ) ;
392+ container . removeEventListener ( 'click' , blockLargeFileExpansion , true ) ;
393+ } ;
394+ } , [ filePath , largeFileMode , shouldBlockLargeFileExpansionClick ] ) ;
283395
284396 useMonacoLsp (
285397 editorInstance ,
286398 detectedLanguage ,
287399 filePath ,
288- enableLsp && lspReady && monacoReady ,
400+ enableLsp && lspReady && monacoReady && ! largeFileMode ,
289401 workspacePath
290402 ) ;
291403
@@ -331,6 +443,9 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
331443
332444 modelRef . current = model ;
333445 const modelContent = model . getValue ( ) ;
446+ const initialLargeFileMode = detectLargeFileMode ( modelContent ) ;
447+ largeFileModeRef . current = initialLargeFileMode ;
448+ setLargeFileMode ( initialLargeFileMode ) ;
334449
335450 const modelMetadata = monacoModelManager . getModelMetadata ( filePath ) ;
336451 if ( modelMetadata ) {
@@ -376,7 +491,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
376491 readOnly : readOnly ,
377492 lineNumbers : showLineNumbers ? 'on' : ( editorConfig . line_numbers as any ) || 'on' ,
378493 minimap : {
379- enabled : showMinimap ,
494+ enabled : showMinimap && ! initialLargeFileMode ,
380495 side : ( editorConfig . minimap ?. side as any ) || 'right' ,
381496 size : ( editorConfig . minimap ?. size as any ) || 'proportional'
382497 } ,
@@ -400,7 +515,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
400515 multiCursorModifier : 'alt' ,
401516 definitionLinkOpensInPeek : false ,
402517 inlayHints : {
403- enabled : 'on' ,
518+ enabled : initialLargeFileMode ? 'off' : 'on' ,
404519 fontSize : 12 ,
405520 fontFamily : "'Fira Code', Consolas, 'Courier New', monospace" ,
406521 padding : false
@@ -422,7 +537,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
422537 } ) ( ) ,
423538
424539 quickSuggestions : {
425- other : true ,
540+ other : ! initialLargeFileMode ,
426541 comments : false ,
427542 strings : false
428543 } ,
@@ -431,7 +546,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
431546 showSnippets : true
432547 } ,
433548
434- 'semanticHighlighting.enabled' : true ,
549+ 'semanticHighlighting.enabled' : ! initialLargeFileMode ,
435550 guides : {
436551 indentation : true ,
437552 bracketPairs : true ,
@@ -442,12 +557,17 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
442557
443558 renderLineHighlight : 'line' ,
444559 renderControlCharacters : false ,
445- renderValidationDecorations : 'on' ,
446- smoothScrolling : true ,
560+ renderValidationDecorations : initialLargeFileMode ? 'off' : 'on' ,
561+ largeFileOptimizations : true ,
562+ maxTokenizationLineLength : initialLargeFileMode ? LARGE_FILE_MAX_TOKENIZATION_LINE_LENGTH : LARGE_FILE_MAX_LINE_LENGTH ,
563+ occurrencesHighlight : initialLargeFileMode ? 'off' : 'singleFile' ,
564+ selectionHighlight : ! initialLargeFileMode ,
565+ matchBrackets : initialLargeFileMode ? 'never' : 'always' ,
566+ smoothScrolling : ! initialLargeFileMode ,
447567 roundedSelection : false ,
448- disableMonospaceOptimizations : true ,
568+ disableMonospaceOptimizations : ! initialLargeFileMode ,
449569 fontLigatures : false ,
450- stopRenderingLineAfter : - 1 ,
570+ stopRenderingLineAfter : initialLargeFileMode ? LARGE_FILE_RENDER_LINE_LIMIT : - 1 ,
451571 scrollbar : {
452572 vertical : 'auto' ,
453573 horizontal : 'auto' ,
@@ -713,7 +833,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
713833 log . error ( 'Failed to cleanup EditorReadyManager' , err ) ;
714834 } ) ;
715835 } ;
716- } , [ filePath , detectedLanguage ] ) ;
836+ } , [ filePath , detectedLanguage , detectLargeFileMode ] ) ;
717837
718838 useEffect ( ( ) => {
719839 if ( modelRef . current && monacoReady && ! loading ) {
@@ -906,6 +1026,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
9061026 try {
9071027 const { workspaceAPI } = await import ( '@/infrastructure/api' ) ;
9081028 const content = await workspaceAPI . readFileContent ( filePath , newEncoding ) ;
1029+ updateLargeFileMode ( content ) ;
9091030 setContent ( content ) ;
9101031 originalContentRef . current = content ;
9111032 if ( modelRef . current ) {
@@ -914,7 +1035,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
9141035 } catch ( err ) {
9151036 log . warn ( 'Failed to reload file with new encoding' , err ) ;
9161037 }
917- } , [ filePath ] ) ;
1038+ } , [ filePath , updateLargeFileMode ] ) ;
9181039
9191040 const handleLanguageConfirm = useCallback ( ( languageId : string ) => {
9201041 userLanguageOverrideRef . current = true ;
@@ -944,8 +1065,23 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
9441065 try {
9451066 const { workspaceAPI } = await import ( '@/infrastructure/api' ) ;
9461067 const { invoke } = await import ( '@tauri-apps/api/core' ) ;
1068+ let fileSizeBytes : number | undefined ;
1069+ try {
1070+ const fileInfo : any = await invoke ( 'get_file_metadata' , {
1071+ request : { path : filePath }
1072+ } ) ;
1073+ if ( typeof fileInfo ?. modified === 'number' ) {
1074+ lastModifiedTimeRef . current = fileInfo . modified ;
1075+ }
1076+ if ( typeof fileInfo ?. size === 'number' ) {
1077+ fileSizeBytes = fileInfo . size ;
1078+ }
1079+ } catch ( err ) {
1080+ log . warn ( 'Failed to get file metadata' , err ) ;
1081+ }
9471082
9481083 const fileContent = await workspaceAPI . readFileContent ( filePath ) ;
1084+ updateLargeFileMode ( fileContent , fileSizeBytes ) ;
9491085
9501086 setContent ( fileContent ) ;
9511087 originalContentRef . current = fileContent ;
@@ -965,15 +1101,6 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
9651101 }
9661102 } ) ;
9671103
968- // Get file's initial modification time
969- try {
970- const fileInfo : any = await invoke ( 'get_file_metadata' , {
971- request : { path : filePath }
972- } ) ;
973- lastModifiedTimeRef . current = fileInfo . modified ;
974- } catch ( err ) {
975- log . warn ( 'Failed to get file metadata' , err ) ;
976- }
9771104 } catch ( err ) {
9781105 // Simplify error message, show only core reason
9791106 const errStr = String ( err ) ;
@@ -993,7 +1120,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
9931120 isLoadingContentRef . current = false ;
9941121 } ) ;
9951122 }
996- } , [ filePath , detectedLanguage , t ] ) ;
1123+ } , [ filePath , detectedLanguage , t , updateLargeFileMode ] ) ;
9971124
9981125 // Save file content
9991126 const saveFileContent = useCallback ( async ( ) => {
@@ -1101,6 +1228,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
11011228
11021229 const { workspaceAPI } = await import ( '@/infrastructure/api' ) ;
11031230 const fileContent = await workspaceAPI . readFileContent ( filePath ) ;
1231+ updateLargeFileMode ( fileContent ) ;
11041232
11051233 if ( ! isUnmountedRef . current ) {
11061234 isLoadingContentRef . current = true ;
@@ -1128,7 +1256,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
11281256 } finally {
11291257 isCheckingFileRef . current = false ;
11301258 }
1131- } , [ filePath , hasChanges , monacoReady ] ) ;
1259+ } , [ filePath , hasChanges , monacoReady , updateLargeFileMode ] ) ;
11321260
11331261 // Initial file load - only run once when filePath changes
11341262 const loadFileContentCalledRef = useRef ( false ) ;
@@ -1334,6 +1462,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
13341462 try {
13351463 const { workspaceAPI } = await import ( '@/infrastructure/api' ) ;
13361464 const content = await workspaceAPI . readFile ( filePath ) ;
1465+ updateLargeFileMode ( content ) ;
13371466
13381467 const currentPosition = editor ?. getPosition ( ) ;
13391468
@@ -1365,7 +1494,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
13651494 return ( ) => {
13661495 unsubscribers . forEach ( unsub => unsub ( ) ) ;
13671496 } ;
1368- } , [ monacoReady , filePath ] ) ;
1497+ } , [ monacoReady , filePath , updateLargeFileMode ] ) ;
13691498
13701499 useEffect ( ( ) => {
13711500 userLanguageOverrideRef . current = false ;
@@ -1420,7 +1549,7 @@ const CodeEditor: React.FC<CodeEditorProps> = ({
14201549
14211550 return (
14221551 < div
1423- className = { `code-editor-tool ${ className } ${ loading && showLoadingOverlay ? 'is-loading' : '' } ${ error ? 'is-error' : '' } ` }
1552+ className = { `code-editor-tool ${ className } ${ loading && showLoadingOverlay ? 'is-loading' : '' } ${ error ? 'is-error' : '' } ${ largeFileMode ? 'is-large-file-mode' : '' } ` }
14241553 data-monaco-editor = "true"
14251554 data-editor-id = { `editor-${ filePath . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '-' ) } ` }
14261555 data-file-path = { filePath }
0 commit comments