Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/keenwrite.git

Add source parser for injecting logging

AuthorDaveJarvis <email>
Date2020-09-16 00:09:59 GMT-0700
Commit83ae24d16e4fd0110306108fb93ae7e6dca45ad4
Parentabc8de4
Delta652 lines added, 599 lines removed, 53-line increase
src/main/java/com/scrivenvar/MainWindow.java
private DefinitionSource mDefinitionSource = createDefaultDefinitionSource();
- private final DefinitionPane mDefinitionPane = new DefinitionPane();
- private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
- private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
- mCaretPositionListener,
- mCaretParagraphListener );
-
- /**
- * Listens on the definition pane for double-click events.
- */
- private final DefinitionNameInjector mDefinitionNameInjector
- = new DefinitionNameInjector( mDefinitionPane );
-
- public MainWindow() {
- mStatusBar = createStatusBar();
- mLineNumberText = createLineNumberText();
- mFindTextField = createFindTextField();
- mScene = createScene();
- mSpellChecker = createSpellChecker();
-
- // Add the close request listener before the window is shown.
- initLayout();
- StatusBarNotifier.setStatusBar( mStatusBar );
- }
-
- /**
- * Called after the stage is shown.
- */
- public void init() {
- initFindInput();
- initSnitch();
- initDefinitionListener();
- initTabAddedListener();
- initTabChangedListener();
- initPreferences();
- initVariableNameInjector();
- }
-
- private void initLayout() {
- final var scene = getScene();
-
- scene.getStylesheets().add( STYLESHEET_SCENE );
- scene.windowProperty().addListener(
- ( unused, oldWindow, newWindow ) ->
- newWindow.setOnCloseRequest(
- e -> {
- if( !getFileEditorPane().closeAllEditors() ) {
- e.consume();
- }
- }
- )
- );
- }
-
- /**
- * Initialize the find input text field to listen on F3, ENTER, and
- * ESCAPE key presses.
- */
- private void initFindInput() {
- final TextField input = getFindTextField();
-
- input.setOnKeyPressed( ( KeyEvent event ) -> {
- switch( event.getCode() ) {
- case F3:
- case ENTER:
- editFindNext();
- break;
- case F:
- if( !event.isControlDown() ) {
- break;
- }
- case ESCAPE:
- getStatusBar().setGraphic( null );
- getActiveFileEditorTab().getEditorPane().requestFocus();
- break;
- }
- } );
-
- // Remove when the input field loses focus.
- input.focusedProperty().addListener(
- ( focused, oldFocus, newFocus ) -> {
- if( !newFocus ) {
- getStatusBar().setGraphic( null );
- }
- }
- );
- }
-
- /**
- * Watch for changes to external files. In particular, this awaits
- * modifications to any XSL files associated with XML files being edited.
- * When
- * an XSL file is modified (external to the application), the snitch's ears
- * perk up and the file is reloaded. This keeps the XSL transformation up to
- * date with what's on the file system.
- */
- private void initSnitch() {
- SNITCH.addObserver( this );
- }
-
- /**
- * Listen for {@link FileEditorTabPane} to receive open definition file
- * event.
- */
- private void initDefinitionListener() {
- getFileEditorPane().onOpenDefinitionFileProperty().addListener(
- ( final ObservableValue<? extends Path> file,
- final Path oldPath, final Path newPath ) -> {
- openDefinitions( newPath );
- rerender();
- }
- );
- }
-
- /**
- * Re-instantiates all processors then re-renders the active tab. This
- * will refresh the resolved map, force R to re-initialize, and brute-force
- * XSLT file reloads.
- */
- private void rerender() {
- runLater(
- () -> {
- resetProcessors();
- renderActiveTab();
- }
- );
- }
-
- /**
- * When tabs are added, hook the various change listeners onto the new
- * tab sothat the preview pane refreshes as necessary.
- */
- private void initTabAddedListener() {
- final FileEditorTabPane editorPane = getFileEditorPane();
-
- // Make sure the text processor kicks off when new files are opened.
- final ObservableList<Tab> tabs = editorPane.getTabs();
-
- // Update the preview pane on tab changes.
- tabs.addListener(
- ( final Change<? extends Tab> change ) -> {
- while( change.next() ) {
- if( change.wasAdded() ) {
- // Multiple tabs can be added simultaneously.
- for( final Tab newTab : change.getAddedSubList() ) {
- final FileEditorTab tab = (FileEditorTab) newTab;
-
- initTextChangeListener( tab );
- initScrollEventListener( tab );
- initSpellCheckListener( tab );
-// initSyntaxListener( tab );
- }
- }
- }
- }
- );
- }
-
- private void initTextChangeListener( final FileEditorTab tab ) {
- tab.addTextChangeListener(
- ( __, ov, nv ) -> {
- process( tab );
- scrollToParagraph( getCurrentParagraphIndex() );
- }
- );
- }
-
- private void initScrollEventListener( final FileEditorTab tab ) {
- final var scrollPane = tab.getScrollPane();
- final var scrollBar = getPreviewPane().getVerticalScrollBar();
-
- addShowListener( scrollPane, ( __ ) -> {
- final var handler = new ScrollEventHandler( scrollPane, scrollBar );
- handler.enabledProperty().bind( tab.selectedProperty() );
- } );
- }
-
- /**
- * Listen for changes to the any particular paragraph and perform a quick
- * spell check upon it. The style classes in the editor will be changed to
- * mark any spelling mistakes in the paragraph. The user may then interact
- * with any misspelled word (i.e., any piece of text that is marked) to
- * revise the spelling.
- *
- * @param tab The tab to spellcheck.
- */
- private void initSpellCheckListener( final FileEditorTab tab ) {
- final var editor = tab.getEditorPane().getEditor();
-
- // When the editor first appears, run a full spell check. This allows
- // spell checking while typing to be restricted to the active paragraph,
- // which is usually substantially smaller than the whole document.
- addShowListener(
- editor, ( __ ) -> spellcheck( editor, editor.getText() )
- );
-
- // Use the plain text changes so that notifications of style changes
- // are suppressed. Checking against the identity ensures that only
- // new text additions or deletions trigger proofreading.
- editor.plainTextChanges()
- .filter( p -> !p.isIdentity() ).subscribe( change -> {
-
- // Only perform a spell check on the current paragraph. The
- // entire document is processed once, when opened.
- final var offset = change.getPosition();
- final var position = editor.offsetToPosition( offset, Forward );
- final var paraId = position.getMajor();
- final var paragraph = editor.getParagraph( paraId );
- final var text = paragraph.getText();
-
- // Ensure that styles aren't doubled-up.
- editor.clearStyle( paraId );
-
- spellcheck( editor, text, paraId );
- } );
- }
-
- /**
- * Listen for new tab selection events.
- */
- private void initTabChangedListener() {
- final FileEditorTabPane editorPane = getFileEditorPane();
-
- // Update the preview pane changing tabs.
- editorPane.addTabSelectionListener(
- ( tabPane, oldTab, newTab ) -> {
- if( newTab == null ) {
- // Clear the preview pane when closing an editor. When the last
- // tab is closed, this ensures that the preview pane is empty.
- getPreviewPane().clear();
- }
- else {
- final var tab = (FileEditorTab) newTab;
- updateVariableNameInjector( tab );
- process( tab );
- }
- }
- );
- }
-
- /**
- * Reloads the preferences from the previous session.
- */
- private void initPreferences() {
- initDefinitionPane();
- getFileEditorPane().initPreferences();
- getUserPreferences().addSaveEventHandler( mRPreferencesListener );
- }
-
- private void initVariableNameInjector() {
- updateVariableNameInjector( getActiveFileEditorTab() );
- }
-
- /**
- * Calls the listener when the given node is shown for the first time. The
- * visible property is not the same as the initial showing event; visibility
- * can be triggered numerous times (such as going off screen).
- * <p>
- * This is called, for example, before the drag handler can be attached,
- * because the scrollbar for the text editor pane must be visible.
- * </p>
- *
- * @param node The node to watch for showing.
- * @param consumer The consumer to invoke when the event fires.
- */
- private void addShowListener(
- final Node node, final Consumer<Void> consumer ) {
- final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
- runLater( () -> {
- if( newShow ) {
- try {
- consumer.accept( null );
- } catch( final Exception ex ) {
- alert( ex );
- }
- }
- } );
-
- Val.flatMap( node.sceneProperty(), Scene::windowProperty )
- .flatMap( Window::showingProperty )
- .addListener( listener );
- }
-
- private void scrollToParagraph( final int id ) {
- scrollToParagraph( id, false );
- }
-
- /**
- * @param id The paragraph to scroll to, will be approximated if it doesn't
- * exist.
- * @param force {@code true} means to force scrolling immediately, which
- * should only be attempted when it is known that the document
- * has been fully rendered. Otherwise the internal map of ID
- * attributes will be incomplete and scrolling will flounder.
- */
- private void scrollToParagraph( final int id, final boolean force ) {
- synchronized( mMutex ) {
- final var previewPane = getPreviewPane();
- final var scrollPane = previewPane.getScrollPane();
- final int approxId = getActiveEditorPane().approximateParagraphId( id );
-
- if( force ) {
- previewPane.scrollTo( approxId );
- }
- else {
- previewPane.tryScrollTo( approxId );
- }
-
- scrollPane.repaint();
- }
- }
-
- private void updateVariableNameInjector( final FileEditorTab tab ) {
- getDefinitionNameInjector().addListener( tab );
- }
-
- /**
- * Called whenever the preview pane becomes out of sync with the file editor
- * tab. This can be called when the text changes, the caret paragraph
- * changes, or the file tab changes.
- *
- * @param tab The file editor tab that has been changed in some fashion.
- */
- private void process( final FileEditorTab tab ) {
- if( tab != null ) {
- getPreviewPane().setPath( tab.getPath() );
-
- final Processor<String> processor = getProcessors().computeIfAbsent(
- tab, p -> createProcessors( tab )
- );
-
- try {
- processChain( processor, tab.getEditorText() );
- } catch( final Exception ex ) {
- alert( ex );
- }
- }
- }
-
- /**
- * Executes the processing chain, operating on the given string.
- *
- * @param handler The first processor in the chain to call.
- * @param text The initial value of the text to process.
- * @return The final value of the text that was processed by the chain.
- */
- private String processChain( Processor<String> handler, String text ) {
- while( handler != null && text != null ) {
- text = handler.apply( text );
- handler = handler.next();
- }
-
- return text;
- }
-
- private void renderActiveTab() {
- process( getActiveFileEditorTab() );
- }
-
- /**
- * Called when a definition source is opened.
- *
- * @param path Path to the definition source that was opened.
- */
- private void openDefinitions( final Path path ) {
- try {
- final var ds = createDefinitionSource( path );
- setDefinitionSource( ds );
-
- final var prefs = getUserPreferences();
- prefs.definitionPathProperty().setValue( path.toFile() );
- prefs.save();
-
- final var tooltipPath = new Tooltip( path.toString() );
- tooltipPath.setShowDelay( Duration.millis( 200 ) );
-
- final var pane = getDefinitionPane();
- pane.update( ds );
- pane.addTreeChangeHandler( mTreeHandler );
- pane.addKeyEventHandler( mDefinitionKeyHandler );
- pane.filenameProperty().setValue( path.getFileName().toString() );
- pane.setTooltip( tooltipPath );
-
- interpolateResolvedMap();
- } catch( final Exception ex ) {
- alert( ex );
- }
- }
-
- private void exportDefinitions( final Path path ) {
- try {
- final var pane = getDefinitionPane();
- final var root = pane.getTreeView().getRoot();
- final var problemChild = pane.isTreeWellFormed();
-
- if( problemChild == null ) {
- getDefinitionSource().getTreeAdapter().export( root, path );
- }
- else {
- alert( "yaml.error.tree.form", problemChild.getValue() );
- }
- } catch( final Exception ex ) {
- alert( ex );
- }
- }
-
- private void interpolateResolvedMap() {
- final var treeMap = getDefinitionPane().toMap();
- final var map = new HashMap<>( treeMap );
- MapInterpolator.interpolate( map );
-
- getResolvedMap().clear();
- getResolvedMap().putAll( map );
- }
-
- private void initDefinitionPane() {
- openDefinitions( getDefinitionPath() );
- }
-
- //---- File actions -------------------------------------------------------
-
- /**
- * Called when an {@link Observable} instance has changed. This is called
- * by both the {@link Snitch} service and the notify service. The @link
- * Snitch} service can be called for different file types, including
- * {@link DefinitionSource} instances.
- *
- * @param observable The observed instance.
- * @param value The noteworthy item.
- */
- @Override
- public void update( final Observable observable, final Object value ) {
- if( value instanceof Path && observable instanceof Snitch ) {
- updateSelectedTab();
- }
- }
-
- /**
- * Called when a file has been modified.
- */
- private void updateSelectedTab() {
- rerender();
- }
-
- /**
- * After resetting the processors, they will refresh anew to be up-to-date
- * with the files (text and definition) currently loaded into the editor.
- */
- private void resetProcessors() {
- getProcessors().clear();
- }
-
- //---- File actions -------------------------------------------------------
-
- private void fileNew() {
- getFileEditorPane().newEditor();
- }
-
- private void fileOpen() {
- getFileEditorPane().openFileDialog();
- }
-
- private void fileClose() {
- getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
- }
-
- /**
- * TODO: Upon closing, first remove the tab change listeners. (There's no
- * need to re-render each tab when all are being closed.)
- */
- private void fileCloseAll() {
- getFileEditorPane().closeAllEditors();
- }
-
- private void fileSave() {
- getFileEditorPane().saveEditor( getActiveFileEditorTab() );
- }
-
- private void fileSaveAs() {
- final FileEditorTab editor = getActiveFileEditorTab();
- getFileEditorPane().saveEditorAs( editor );
- getProcessors().remove( editor );
-
- try {
- process( editor );
- } catch( final Exception ex ) {
- alert( ex );
- }
- }
-
- private void fileSaveAll() {
- getFileEditorPane().saveAllEditors();
- }
-
- private void fileExit() {
- final Window window = getWindow();
- fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
- }
-
- //---- Edit actions -------------------------------------------------------
-
- /**
- * Transform the Markdown into HTML then copy that HTML into the copy
- * buffer.
- */
- private void copyHtml() {
- final var markdown = getActiveEditorPane().getText();
- final var processors = createProcessorFactory().createProcessors(
- getActiveFileEditorTab()
- );
-
- final var chain = processors.remove( HtmlPreviewProcessor.class );
-
- final String html = processChain( chain, markdown );
-
- final Clipboard clipboard = Clipboard.getSystemClipboard();
- final ClipboardContent content = new ClipboardContent();
- content.putString( html );
- clipboard.setContent( content );
- }
-
- /**
- * Used to find text in the active file editor window.
- */
- private void editFind() {
- final TextField input = getFindTextField();
- getStatusBar().setGraphic( input );
- input.requestFocus();
- }
-
- public void editFindNext() {
- getActiveFileEditorTab().searchNext( getFindTextField().getText() );
- }
-
- public void editPreferences() {
- getUserPreferences().show();
- }
-
- //---- Insert actions -----------------------------------------------------
-
- /**
- * Delegates to the active editor to handle wrapping the current text
- * selection with leading and trailing strings.
- *
- * @param leading The string to put before the selection.
- * @param trailing The string to put after the selection.
- */
- private void insertMarkdown(
- final String leading, final String trailing ) {
- getActiveEditorPane().surroundSelection( leading, trailing );
- }
-
- private void insertMarkdown(
- final String leading, final String trailing, final String hint ) {
- getActiveEditorPane().surroundSelection( leading, trailing, hint );
- }
-
- //---- View actions -------------------------------------------------------
-
- private void viewRefresh() {
- rerender();
- }
-
- //---- Help actions -------------------------------------------------------
-
- private void helpAbout() {
- final Alert alert = new Alert( AlertType.INFORMATION );
- alert.setTitle( get( "Dialog.about.title" ) );
- alert.setHeaderText( get( "Dialog.about.header" ) );
- alert.setContentText( get( "Dialog.about.content" ) );
- alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
- alert.initOwner( getWindow() );
-
- alert.showAndWait();
- }
-
- //---- Member creators ----------------------------------------------------
-
- private SpellChecker createSpellChecker() {
- try {
- final Collection<String> lexicon = readLexicon( "en.txt" );
- return SymSpellSpeller.forLexicon( lexicon );
- } catch( final Exception ex ) {
- alert( ex );
- return new PermissiveSpeller();
- }
- }
-
- /**
- * Factory to create processors that are suited to different file types.
- *
- * @param tab The tab that is subjected to processing.
- * @return A processor suited to the file type specified by the tab's path.
- */
- private Processor<String> createProcessors( final FileEditorTab tab ) {
- return createProcessorFactory().createProcessors( tab );
- }
-
- private ProcessorFactory createProcessorFactory() {
- return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
+ private final DefinitionPane mDefinitionPane = createDefinitionPane();
+ private final HTMLPreviewPane mPreviewPane = createHTMLPreviewPane();
+ private final FileEditorTabPane mFileEditorPane = new FileEditorTabPane(
+ mCaretPositionListener,
+ mCaretParagraphListener );
+
+ /**
+ * Listens on the definition pane for double-click events.
+ */
+ private final DefinitionNameInjector mDefinitionNameInjector
+ = new DefinitionNameInjector( mDefinitionPane );
+
+ public MainWindow() {
+ mStatusBar = createStatusBar();
+ mLineNumberText = createLineNumberText();
+ mFindTextField = createFindTextField();
+ mScene = createScene();
+ mSpellChecker = createSpellChecker();
+
+ // Add the close request listener before the window is shown.
+ initLayout();
+ StatusBarNotifier.setStatusBar( mStatusBar );
+ }
+
+ /**
+ * Called after the stage is shown.
+ */
+ public void init() {
+ initFindInput();
+ initSnitch();
+ initDefinitionListener();
+ initTabAddedListener();
+ initTabChangedListener();
+ initPreferences();
+ initVariableNameInjector();
+ }
+
+ private void initLayout() {
+ final var scene = getScene();
+
+ scene.getStylesheets().add( STYLESHEET_SCENE );
+ scene.windowProperty().addListener(
+ ( unused, oldWindow, newWindow ) ->
+ newWindow.setOnCloseRequest(
+ e -> {
+ if( !getFileEditorPane().closeAllEditors() ) {
+ e.consume();
+ }
+ }
+ )
+ );
+ }
+
+ /**
+ * Initialize the find input text field to listen on F3, ENTER, and
+ * ESCAPE key presses.
+ */
+ private void initFindInput() {
+ final TextField input = getFindTextField();
+
+ input.setOnKeyPressed( ( KeyEvent event ) -> {
+ switch( event.getCode() ) {
+ case F3:
+ case ENTER:
+ editFindNext();
+ break;
+ case F:
+ if( !event.isControlDown() ) {
+ break;
+ }
+ case ESCAPE:
+ getStatusBar().setGraphic( null );
+ getActiveFileEditorTab().getEditorPane().requestFocus();
+ break;
+ }
+ } );
+
+ // Remove when the input field loses focus.
+ input.focusedProperty().addListener(
+ ( focused, oldFocus, newFocus ) -> {
+ if( !newFocus ) {
+ getStatusBar().setGraphic( null );
+ }
+ }
+ );
+ }
+
+ /**
+ * Watch for changes to external files. In particular, this awaits
+ * modifications to any XSL files associated with XML files being edited.
+ * When
+ * an XSL file is modified (external to the application), the snitch's ears
+ * perk up and the file is reloaded. This keeps the XSL transformation up to
+ * date with what's on the file system.
+ */
+ private void initSnitch() {
+ SNITCH.addObserver( this );
+ }
+
+ /**
+ * Listen for {@link FileEditorTabPane} to receive open definition file
+ * event.
+ */
+ private void initDefinitionListener() {
+ getFileEditorPane().onOpenDefinitionFileProperty().addListener(
+ ( final ObservableValue<? extends Path> file,
+ final Path oldPath, final Path newPath ) -> {
+ openDefinitions( newPath );
+ rerender();
+ }
+ );
+ }
+
+ /**
+ * Re-instantiates all processors then re-renders the active tab. This
+ * will refresh the resolved map, force R to re-initialize, and brute-force
+ * XSLT file reloads.
+ */
+ private void rerender() {
+ runLater(
+ () -> {
+ resetProcessors();
+ renderActiveTab();
+ }
+ );
+ }
+
+ /**
+ * When tabs are added, hook the various change listeners onto the new
+ * tab sothat the preview pane refreshes as necessary.
+ */
+ private void initTabAddedListener() {
+ final FileEditorTabPane editorPane = getFileEditorPane();
+
+ // Make sure the text processor kicks off when new files are opened.
+ final ObservableList<Tab> tabs = editorPane.getTabs();
+
+ // Update the preview pane on tab changes.
+ tabs.addListener(
+ ( final Change<? extends Tab> change ) -> {
+ while( change.next() ) {
+ if( change.wasAdded() ) {
+ // Multiple tabs can be added simultaneously.
+ for( final Tab newTab : change.getAddedSubList() ) {
+ final FileEditorTab tab = (FileEditorTab) newTab;
+
+ initTextChangeListener( tab );
+ initScrollEventListener( tab );
+ initSpellCheckListener( tab );
+// initSyntaxListener( tab );
+ }
+ }
+ }
+ }
+ );
+ }
+
+ private void initTextChangeListener( final FileEditorTab tab ) {
+ tab.addTextChangeListener(
+ ( __, ov, nv ) -> {
+ process( tab );
+ scrollToParagraph( getCurrentParagraphIndex() );
+ }
+ );
+ }
+
+ private void initScrollEventListener( final FileEditorTab tab ) {
+ final var scrollPane = tab.getScrollPane();
+ final var scrollBar = getPreviewPane().getVerticalScrollBar();
+
+ addShowListener( scrollPane, ( __ ) -> {
+ final var handler = new ScrollEventHandler( scrollPane, scrollBar );
+ handler.enabledProperty().bind( tab.selectedProperty() );
+ } );
+ }
+
+ /**
+ * Listen for changes to the any particular paragraph and perform a quick
+ * spell check upon it. The style classes in the editor will be changed to
+ * mark any spelling mistakes in the paragraph. The user may then interact
+ * with any misspelled word (i.e., any piece of text that is marked) to
+ * revise the spelling.
+ *
+ * @param tab The tab to spellcheck.
+ */
+ private void initSpellCheckListener( final FileEditorTab tab ) {
+ final var editor = tab.getEditorPane().getEditor();
+
+ // When the editor first appears, run a full spell check. This allows
+ // spell checking while typing to be restricted to the active paragraph,
+ // which is usually substantially smaller than the whole document.
+ addShowListener(
+ editor, ( __ ) -> spellcheck( editor, editor.getText() )
+ );
+
+ // Use the plain text changes so that notifications of style changes
+ // are suppressed. Checking against the identity ensures that only
+ // new text additions or deletions trigger proofreading.
+ editor.plainTextChanges()
+ .filter( p -> !p.isIdentity() ).subscribe( change -> {
+
+ // Only perform a spell check on the current paragraph. The
+ // entire document is processed once, when opened.
+ final var offset = change.getPosition();
+ final var position = editor.offsetToPosition( offset, Forward );
+ final var paraId = position.getMajor();
+ final var paragraph = editor.getParagraph( paraId );
+ final var text = paragraph.getText();
+
+ // Ensure that styles aren't doubled-up.
+ editor.clearStyle( paraId );
+
+ spellcheck( editor, text, paraId );
+ } );
+ }
+
+ /**
+ * Listen for new tab selection events.
+ */
+ private void initTabChangedListener() {
+ final FileEditorTabPane editorPane = getFileEditorPane();
+
+ // Update the preview pane changing tabs.
+ editorPane.addTabSelectionListener(
+ ( tabPane, oldTab, newTab ) -> {
+ if( newTab == null ) {
+ // Clear the preview pane when closing an editor. When the last
+ // tab is closed, this ensures that the preview pane is empty.
+ getPreviewPane().clear();
+ }
+ else {
+ final var tab = (FileEditorTab) newTab;
+ updateVariableNameInjector( tab );
+ process( tab );
+ }
+ }
+ );
+ }
+
+ /**
+ * Reloads the preferences from the previous session.
+ */
+ private void initPreferences() {
+ initDefinitionPane();
+ getFileEditorPane().initPreferences();
+ getUserPreferences().addSaveEventHandler( mRPreferencesListener );
+ }
+
+ private void initVariableNameInjector() {
+ updateVariableNameInjector( getActiveFileEditorTab() );
+ }
+
+ /**
+ * Calls the listener when the given node is shown for the first time. The
+ * visible property is not the same as the initial showing event; visibility
+ * can be triggered numerous times (such as going off screen).
+ * <p>
+ * This is called, for example, before the drag handler can be attached,
+ * because the scrollbar for the text editor pane must be visible.
+ * </p>
+ *
+ * @param node The node to watch for showing.
+ * @param consumer The consumer to invoke when the event fires.
+ */
+ private void addShowListener(
+ final Node node, final Consumer<Void> consumer ) {
+ final ChangeListener<? super Boolean> listener = ( o, oldShow, newShow ) ->
+ runLater( () -> {
+ if( newShow ) {
+ try {
+ consumer.accept( null );
+ } catch( final Exception ex ) {
+ alert( ex );
+ }
+ }
+ } );
+
+ Val.flatMap( node.sceneProperty(), Scene::windowProperty )
+ .flatMap( Window::showingProperty )
+ .addListener( listener );
+ }
+
+ private void scrollToParagraph( final int id ) {
+ scrollToParagraph( id, false );
+ }
+
+ /**
+ * @param id The paragraph to scroll to, will be approximated if it doesn't
+ * exist.
+ * @param force {@code true} means to force scrolling immediately, which
+ * should only be attempted when it is known that the document
+ * has been fully rendered. Otherwise the internal map of ID
+ * attributes will be incomplete and scrolling will flounder.
+ */
+ private void scrollToParagraph( final int id, final boolean force ) {
+ synchronized( mMutex ) {
+ final var previewPane = getPreviewPane();
+ final var scrollPane = previewPane.getScrollPane();
+ final int approxId = getActiveEditorPane().approximateParagraphId( id );
+
+ if( force ) {
+ previewPane.scrollTo( approxId );
+ }
+ else {
+ previewPane.tryScrollTo( approxId );
+ }
+
+ scrollPane.repaint();
+ }
+ }
+
+ private void updateVariableNameInjector( final FileEditorTab tab ) {
+ getDefinitionNameInjector().addListener( tab );
+ }
+
+ /**
+ * Called whenever the preview pane becomes out of sync with the file editor
+ * tab. This can be called when the text changes, the caret paragraph
+ * changes, or the file tab changes.
+ *
+ * @param tab The file editor tab that has been changed in some fashion.
+ */
+ private void process( final FileEditorTab tab ) {
+ if( tab != null ) {
+ getPreviewPane().setPath( tab.getPath() );
+
+ final Processor<String> processor = getProcessors().computeIfAbsent(
+ tab, p -> createProcessors( tab )
+ );
+
+ try {
+ processChain( processor, tab.getEditorText() );
+ } catch( final Exception ex ) {
+ alert( ex );
+ }
+ }
+ }
+
+ /**
+ * Executes the processing chain, operating on the given string.
+ *
+ * @param handler The first processor in the chain to call.
+ * @param text The initial value of the text to process.
+ * @return The final value of the text that was processed by the chain.
+ */
+ private String processChain( Processor<String> handler, String text ) {
+ while( handler != null && text != null ) {
+ text = handler.apply( text );
+ handler = handler.next();
+ }
+
+ return text;
+ }
+
+ private void renderActiveTab() {
+ process( getActiveFileEditorTab() );
+ }
+
+ /**
+ * Called when a definition source is opened.
+ *
+ * @param path Path to the definition source that was opened.
+ */
+ private void openDefinitions( final Path path ) {
+ try {
+ final var ds = createDefinitionSource( path );
+ setDefinitionSource( ds );
+
+ final var prefs = getUserPreferences();
+ prefs.definitionPathProperty().setValue( path.toFile() );
+ prefs.save();
+
+ final var tooltipPath = new Tooltip( path.toString() );
+ tooltipPath.setShowDelay( Duration.millis( 200 ) );
+
+ final var pane = getDefinitionPane();
+ pane.update( ds );
+ pane.addTreeChangeHandler( mTreeHandler );
+ pane.addKeyEventHandler( mDefinitionKeyHandler );
+ pane.filenameProperty().setValue( path.getFileName().toString() );
+ pane.setTooltip( tooltipPath );
+
+ interpolateResolvedMap();
+ } catch( final Exception ex ) {
+ alert( ex );
+ }
+ }
+
+ private void exportDefinitions( final Path path ) {
+ try {
+ final var pane = getDefinitionPane();
+ final var root = pane.getTreeView().getRoot();
+ final var problemChild = pane.isTreeWellFormed();
+
+ if( problemChild == null ) {
+ getDefinitionSource().getTreeAdapter().export( root, path );
+ }
+ else {
+ alert( "yaml.error.tree.form", problemChild.getValue() );
+ }
+ } catch( final Exception ex ) {
+ alert( ex );
+ }
+ }
+
+ private void interpolateResolvedMap() {
+ final var treeMap = getDefinitionPane().toMap();
+ final var map = new HashMap<>( treeMap );
+ MapInterpolator.interpolate( map );
+
+ getResolvedMap().clear();
+ getResolvedMap().putAll( map );
+ }
+
+ private void initDefinitionPane() {
+ openDefinitions( getDefinitionPath() );
+ }
+
+ //---- File actions -------------------------------------------------------
+
+ /**
+ * Called when an {@link Observable} instance has changed. This is called
+ * by both the {@link Snitch} service and the notify service. The @link
+ * Snitch} service can be called for different file types, including
+ * {@link DefinitionSource} instances.
+ *
+ * @param observable The observed instance.
+ * @param value The noteworthy item.
+ */
+ @Override
+ public void update( final Observable observable, final Object value ) {
+ if( value instanceof Path && observable instanceof Snitch ) {
+ updateSelectedTab();
+ }
+ }
+
+ /**
+ * Called when a file has been modified.
+ */
+ private void updateSelectedTab() {
+ rerender();
+ }
+
+ /**
+ * After resetting the processors, they will refresh anew to be up-to-date
+ * with the files (text and definition) currently loaded into the editor.
+ */
+ private void resetProcessors() {
+ getProcessors().clear();
+ }
+
+ //---- File actions -------------------------------------------------------
+
+ private void fileNew() {
+ getFileEditorPane().newEditor();
+ }
+
+ private void fileOpen() {
+ getFileEditorPane().openFileDialog();
+ }
+
+ private void fileClose() {
+ getFileEditorPane().closeEditor( getActiveFileEditorTab(), true );
+ }
+
+ /**
+ * TODO: Upon closing, first remove the tab change listeners. (There's no
+ * need to re-render each tab when all are being closed.)
+ */
+ private void fileCloseAll() {
+ getFileEditorPane().closeAllEditors();
+ }
+
+ private void fileSave() {
+ getFileEditorPane().saveEditor( getActiveFileEditorTab() );
+ }
+
+ private void fileSaveAs() {
+ final FileEditorTab editor = getActiveFileEditorTab();
+ getFileEditorPane().saveEditorAs( editor );
+ getProcessors().remove( editor );
+
+ try {
+ process( editor );
+ } catch( final Exception ex ) {
+ alert( ex );
+ }
+ }
+
+ private void fileSaveAll() {
+ getFileEditorPane().saveAllEditors();
+ }
+
+ private void fileExit() {
+ final Window window = getWindow();
+ fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
+ }
+
+ //---- Edit actions -------------------------------------------------------
+
+ /**
+ * Transform the Markdown into HTML then copy that HTML into the copy
+ * buffer.
+ */
+ private void copyHtml() {
+ final var markdown = getActiveEditorPane().getText();
+ final var processors = createProcessorFactory().createProcessors(
+ getActiveFileEditorTab()
+ );
+
+ final var chain = processors.remove( HtmlPreviewProcessor.class );
+
+ final String html = processChain( chain, markdown );
+
+ final Clipboard clipboard = Clipboard.getSystemClipboard();
+ final ClipboardContent content = new ClipboardContent();
+ content.putString( html );
+ clipboard.setContent( content );
+ }
+
+ /**
+ * Used to find text in the active file editor window.
+ */
+ private void editFind() {
+ final TextField input = getFindTextField();
+ getStatusBar().setGraphic( input );
+ input.requestFocus();
+ }
+
+ public void editFindNext() {
+ getActiveFileEditorTab().searchNext( getFindTextField().getText() );
+ }
+
+ public void editPreferences() {
+ getUserPreferences().show();
+ }
+
+ //---- Insert actions -----------------------------------------------------
+
+ /**
+ * Delegates to the active editor to handle wrapping the current text
+ * selection with leading and trailing strings.
+ *
+ * @param leading The string to put before the selection.
+ * @param trailing The string to put after the selection.
+ */
+ private void insertMarkdown(
+ final String leading, final String trailing ) {
+ getActiveEditorPane().surroundSelection( leading, trailing );
+ }
+
+ private void insertMarkdown(
+ final String leading, final String trailing, final String hint ) {
+ getActiveEditorPane().surroundSelection( leading, trailing, hint );
+ }
+
+ //---- View actions -------------------------------------------------------
+
+ private void viewRefresh() {
+ rerender();
+ }
+
+ //---- Help actions -------------------------------------------------------
+
+ private void helpAbout() {
+ final Alert alert = new Alert( AlertType.INFORMATION );
+ alert.setTitle( get( "Dialog.about.title" ) );
+ alert.setHeaderText( get( "Dialog.about.header" ) );
+ alert.setContentText( get( "Dialog.about.content" ) );
+ alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
+ alert.initOwner( getWindow() );
+
+ alert.showAndWait();
+ }
+
+ //---- Member creators ----------------------------------------------------
+
+ private SpellChecker createSpellChecker() {
+ try {
+ final Collection<String> lexicon = readLexicon( "en.txt" );
+ return SymSpellSpeller.forLexicon( lexicon );
+ } catch( final Exception ex ) {
+ alert( ex );
+ return new PermissiveSpeller();
+ }
+ }
+
+ /**
+ * Factory to create processors that are suited to different file types.
+ *
+ * @param tab The tab that is subjected to processing.
+ * @return A processor suited to the file type specified by the tab's path.
+ */
+ private Processor<String> createProcessors( final FileEditorTab tab ) {
+ return createProcessorFactory().createProcessors( tab );
+ }
+
+ private ProcessorFactory createProcessorFactory() {
+ return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
+ }
+
+ private DefinitionPane createDefinitionPane() {
+ return new DefinitionPane();
}
logging/README.md
+# Logging
+
+The files in this directory can be used to log the entry/exit to every
+method for debugging purposes. These changes are not meant to be pushed
+onto the mainline branch (i.e., not for production use).
+
+The instructions are relative to the directory containing these instructions.
+
+# Build
+
+If modifications to the existing JAR are needed, rebuild the changes
+as follows:
+
+ git clone https://github.com/javaparser/javaparser
+ cd javaparser
+ cp Main.java ./javaparser-core/src/main/java/com/github/javaparser/.
+ mvn package -Dmaven.test.skip=true
+ cp javaparser-core/target/javaparser-core-3.16.2-SNAPSHOT.jar jp.jar
+
+The file `jp.jar` is built with `Main.class`.
+
+# Usage
+
+The `Main.class` is run using the name of the Java class file to parse.
+Run the parser as follows:
+
+ pushd ../src/main/java
+ for i in $(find . -type f -name "*.java"); do \
+ java -cp ../../../logging/jp.jar com.github.javaparser.Main "$i" > \
+ "$i.jp";
+ done
+
+That command creates a series of processed Java files with a `.jp` extension.
+Replace the original files with the updated versions as follows:
+
+ find . -type f -name "*jp" -size +100c -exec \
+ sh -c 'mv {} $(dirname {})/$(basename {} .jp)' \;
+
+The `+100c` is ensures that files without contents do not overwrite
+existing files. Not sure why 0-byte files are created.
+
+# Revert
+
+When finished building a debug version of the application, reset the repo
+as follows:
+
+ popd
+ git reset --hard HEAD
+
logging/jp.jar
Binary files differ