@@ -8,35 +8,42 @@ import {
88 showDialog ,
99 showErrorMessage
1010} from '@jupyterlab/apputils' ;
11- import { CodeEditor } from '@jupyterlab/codeeditor' ;
11+ import {
12+ CodeEditor ,
13+ CodeEditorWrapper ,
14+ IEditorFactoryService
15+ } from '@jupyterlab/codeeditor' ;
16+ import { IEditorLanguageRegistry } from '@jupyterlab/codemirror' ;
1217import { PathExt , URLExt } from '@jupyterlab/coreutils' ;
1318import { FileBrowser , FileBrowserModel } from '@jupyterlab/filebrowser' ;
1419import { Contents } from '@jupyterlab/services' ;
1520import { ISettingRegistry } from '@jupyterlab/settingregistry' ;
1621import { ITerminal } from '@jupyterlab/terminal' ;
1722import { ITranslator , TranslationBundle } from '@jupyterlab/translation' ;
1823import {
19- closeIcon ,
2024 ContextMenuSvg ,
2125 Toolbar ,
22- ToolbarButton
26+ ToolbarButton ,
27+ closeIcon ,
28+ saveIcon
2329} from '@jupyterlab/ui-components' ;
24- import { ArrayExt } from '@lumino/algorithm' ;
30+ import { ArrayExt , find } from '@lumino/algorithm' ;
2531import { CommandRegistry } from '@lumino/commands' ;
2632import { PromiseDelegate } from '@lumino/coreutils' ;
2733import { Message } from '@lumino/messaging' ;
2834import { ContextMenu , DockPanel , Menu , Panel , Widget } from '@lumino/widgets' ;
2935import * as React from 'react' ;
3036import { CancelledError } from './cancelledError' ;
3137import { BranchPicker } from './components/BranchPicker' ;
38+ import { CONTEXT_COMMANDS } from './components/FileList' ;
39+ import { ManageRemoteDialogue } from './components/ManageRemoteDialogue' ;
3240import { NewTagDialogBox } from './components/NewTagDialog' ;
33- import { DiffModel } from './components/diff/model' ;
3441import { createPlainTextDiff } from './components/diff/PlainTextDiff' ;
3542import { PreviewMainAreaWidget } from './components/diff/PreviewMainAreaWidget' ;
36- import { CONTEXT_COMMANDS } from './components/FileList' ;
37- import { ManageRemoteDialogue } from './components/ManageRemoteDialogue' ;
43+ import { DiffModel } from './components/diff/model' ;
3844import { AUTH_ERROR_MESSAGES , requestAPI } from './git' ;
39- import { getDiffProvider , GitExtension } from './model' ;
45+ import { GitExtension , getDiffProvider } from './model' ;
46+ import { showDetails , showError } from './notifications' ;
4047import {
4148 addIcon ,
4249 diffIcon ,
@@ -50,10 +57,8 @@ import {
5057import { CommandIDs , ContextCommandIDs , Git , IGitExtension } from './tokens' ;
5158import { AdvancedPushForm } from './widgets/AdvancedPushForm' ;
5259import { GitCredentialsForm } from './widgets/CredentialsBox' ;
53- import { discardAllChanges } from './widgets/discardAllChanges' ;
5460import { CheckboxForm } from './widgets/GitResetToRemoteForm' ;
55- import { IEditorLanguageRegistry } from '@jupyterlab/codemirror' ;
56- import { showDetails , showError } from './notifications' ;
61+ import { discardAllChanges } from './widgets/discardAllChanges' ;
5762
5863export interface IGitCloneArgs {
5964 /**
@@ -126,7 +131,7 @@ function pluralizedContextLabel(singular: string, plural: string) {
126131export function addCommands (
127132 app : JupyterFrontEnd ,
128133 gitModel : GitExtension ,
129- editorFactory : CodeEditor . Factory ,
134+ editorFactory : IEditorFactoryService ,
130135 languageRegistry : IEditorLanguageRegistry ,
131136 fileBrowserModel : FileBrowserModel ,
132137 settings : ISettingRegistry . ISettings ,
@@ -314,13 +319,129 @@ export function addCommands(
314319 }
315320 } ) ;
316321
322+ async function showGitignore ( error : any ) {
323+ const model = new CodeEditor . Model ( { } ) ;
324+ const repoPath = gitModel . getRelativeFilePath ( ) ;
325+ const id = repoPath + '/.git-ignore' ;
326+ const contentData = await gitModel . readGitIgnore ( ) ;
327+
328+ const gitIgnoreWidget = find (
329+ shell . widgets ( ) ,
330+ shellWidget => shellWidget . id === id
331+ ) ;
332+ if ( gitIgnoreWidget ) {
333+ shell . activateById ( id ) ;
334+ return ;
335+ }
336+ model . sharedModel . setSource ( contentData ? contentData : '' ) ;
337+ const editor = new CodeEditorWrapper ( {
338+ factory : editorFactory . newDocumentEditor . bind ( editorFactory ) ,
339+ model : model
340+ } ) ;
341+ const modelChangedSignal = model . sharedModel . changed ;
342+ editor . disposed . connect ( ( ) => {
343+ model . dispose ( ) ;
344+ } ) ;
345+ const preview = new MainAreaWidget ( {
346+ content : editor
347+ } ) ;
348+
349+ preview . title . label = '.gitignore' ;
350+ preview . id = id ;
351+ preview . title . icon = gitIcon ;
352+ preview . title . closable = true ;
353+ preview . title . caption = repoPath + '/.gitignore' ;
354+ const saveButton = new ToolbarButton ( {
355+ icon : saveIcon ,
356+ onClick : async ( ) => {
357+ if ( saved ) {
358+ return ;
359+ }
360+ const newContent = model . sharedModel . getSource ( ) ;
361+ try {
362+ await gitModel . writeGitIgnore ( newContent ) ;
363+ preview . title . className = '' ;
364+ saved = true ;
365+ } catch ( error ) {
366+ console . log ( 'Could not save .gitignore' ) ;
367+ }
368+ } ,
369+ tooltip : trans . __ ( 'Saves .gitignore' )
370+ } ) ;
371+ let saved = true ;
372+ preview . toolbar . addItem ( 'save' , saveButton ) ;
373+ shell . add ( preview ) ;
374+ modelChangedSignal . connect ( ( ) => {
375+ if ( saved ) {
376+ saved = false ;
377+ preview . title . className = 'not-saved' ;
378+ }
379+ } ) ;
380+ }
381+
382+ /* Helper: Show gitignore hidden file */
383+ async function showGitignoreHiddenFile ( error : any , hidePrompt : boolean ) {
384+ if ( hidePrompt ) {
385+ return showGitignore ( error ) ;
386+ }
387+ const result = await showDialog ( {
388+ title : trans . __ ( 'Warning: The .gitignore file is a hidden file.' ) ,
389+ body : (
390+ < div >
391+ { trans . __ (
392+ 'Hidden files by default cannot be accessed with the regular code editor. In order to open the .gitignore file you must:'
393+ ) }
394+ < ol >
395+ < li >
396+ { trans . __ (
397+ 'Print the command below to create a jupyter_server_config.py file with defaults commented out. If you already have the file located in .jupyter, skip this step.'
398+ ) }
399+ < div style = { { padding : '0.5rem' } } >
400+ { 'jupyter server --generate-config' }
401+ </ div >
402+ </ li >
403+ < li >
404+ { trans . __ (
405+ 'Open jupyter_server_config.py, uncomment out the following line and set it to True:'
406+ ) }
407+ < div style = { { padding : '0.5rem' } } >
408+ { 'c.ContentsManager.allow_hidden = False' }
409+ </ div >
410+ </ li >
411+ </ ol >
412+ </ div >
413+ ) ,
414+ buttons : [
415+ Dialog . cancelButton ( { label : trans . __ ( 'Cancel' ) } ) ,
416+ Dialog . okButton ( { label : trans . __ ( 'Show .gitignore file anyways' ) } )
417+ ] ,
418+ checkbox : {
419+ label : trans . __ ( 'Do not show this warning again' ) ,
420+ checked : false
421+ }
422+ } ) ;
423+ if ( result . button . accept ) {
424+ settings . set ( 'hideHiddenFileWarning' , result . isChecked ) ;
425+ showGitignore ( error ) ;
426+ }
427+ }
428+
317429 /** Add git open gitignore command */
318430 commands . addCommand ( CommandIDs . gitOpenGitignore , {
319431 label : trans . __ ( 'Open .gitignore' ) ,
320432 caption : trans . __ ( 'Open .gitignore' ) ,
321433 isEnabled : ( ) => gitModel . pathRepository !== null ,
322434 execute : async ( ) => {
323- await gitModel . ensureGitignore ( ) ;
435+ try {
436+ await gitModel . ensureGitignore ( ) ;
437+ } catch ( error : any ) {
438+ if ( error ?. name === 'hiddenFile' ) {
439+ await showGitignoreHiddenFile (
440+ error ,
441+ settings . composite [ 'hideHiddenFileWarning' ] as boolean
442+ ) ;
443+ }
444+ }
324445 }
325446 } ) ;
326447
@@ -586,7 +707,7 @@ export function addCommands(
586707 ( options =>
587708 createPlainTextDiff ( {
588709 ...options ,
589- editorFactory,
710+ editorFactory : editorFactory . newInlineEditor . bind ( editorFactory ) ,
590711 languageRegistry
591712 } ) ) ) ;
592713
@@ -1523,7 +1644,16 @@ export function addCommands(
15231644 const { files } = args as any as CommandArguments . IGitContextAction ;
15241645 for ( const file of files ) {
15251646 if ( file ) {
1526- await gitModel . ignore ( file . to , false ) ;
1647+ try {
1648+ await gitModel . ignore ( file . to , false ) ;
1649+ } catch ( error : any ) {
1650+ if ( error ?. name === 'hiddenFile' ) {
1651+ await showGitignoreHiddenFile (
1652+ error ,
1653+ settings . composite [ 'hideHiddenFileWarning' ] as boolean
1654+ ) ;
1655+ }
1656+ }
15271657 }
15281658 }
15291659 }
0 commit comments