Dave Jarvis' Repositories

M build.gradle
1
version = '1.0.3'
1
version = '1.0.6'
22
33
apply plugin: 'java'
...
2424
  compile 'de.jensd:fontawesomefx-fontawesome:4.5.0'
2525
  compile 'org.ahocorasick:ahocorasick:0.3.0'
26
  compile 'com.vladsch.flexmark:flexmark:0.6.1'
27
  compile 'com.vladsch.flexmark:flexmark-ext-gfm-tables:0.6.1'
26
  compile 'com.vladsch.flexmark:flexmark:0.9.0'
27
  compile 'com.vladsch.flexmark:flexmark-ext-gfm-tables:0.9.0'
28
  compile 'com.vladsch.flexmark:flexmark-ext-superscript:0.9.0'
29
  compile 'com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:0.9.0'
2830
  compile 'com.fasterxml.jackson.core:jackson-core:2.8.4'
2931
  compile 'com.fasterxml.jackson.core:jackson-databind:2.8.4'
M src/main/java/com/scrivenvar/FileEditorTab.java
272272
    );
273273
274
    service.createError( message ).showAndWait();
274
    // TODO: Put this into a status bar or status area
275
    System.out.println( e );
276
277
//    service.createError( message ).showAndWait();
275278
    return false;
276279
  }
M src/main/java/com/scrivenvar/MainWindow.java
326326
   */
327327
  private void alert( final Exception e ) {
328
    // TODO: Update the status bar.
329
  }
330
331
  //---- File actions -------------------------------------------------------
332
  /**
333
   * Called when a file has been modified.
334
   *
335
   * @param snitch The watchdog file monitoring instance.
336
   * @param file The file that was modified.
337
   */
338
  @Override
339
  public void update( final Observable snitch, final Object file ) {
340
    if( file instanceof Path ) {
341
      update( (Path)file );
342
    }
343
  }
344
345
  /**
346
   * Called when a file has been modified.
347
   *
348
   * @param file Path to the modified file.
349
   */
350
  private void update( final Path file ) {
351
    // Avoid throwing IllegalStateException by running from a non-JavaFX thread.
352
    Platform.runLater(
353
      () -> {
354
        // Brute-force XSLT file reload by re-instantiating all processors.
355
        resetProcessors();
356
        refreshSelectedTab( getActiveFileEditor() );
357
      }
358
    );
359
  }
360
361
  /**
362
   * After resetting the processors, they will refresh anew to be up-to-date
363
   * with the files (text and definition) currently loaded into the editor.
364
   */
365
  private void resetProcessors() {
366
    getProcessors().clear();
367
  }
368
369
  //---- File actions -------------------------------------------------------
370
  private void fileNew() {
371
    getFileEditorPane().newEditor();
372
  }
373
374
  private void fileOpen() {
375
    getFileEditorPane().openFileDialog();
376
  }
377
378
  private void fileClose() {
379
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
380
  }
381
382
  private void fileCloseAll() {
383
    getFileEditorPane().closeAllEditors();
384
  }
385
386
  private void fileSave() {
387
    getFileEditorPane().saveEditor( getActiveFileEditor() );
388
  }
389
390
  private void fileSaveAll() {
391
    getFileEditorPane().saveAllEditors();
392
  }
393
394
  private void fileExit() {
395
    final Window window = getWindow();
396
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
397
  }
398
399
  //---- Help actions -------------------------------------------------------
400
  private void helpAbout() {
401
    Alert alert = new Alert( AlertType.INFORMATION );
402
    alert.setTitle( get( "Dialog.about.title" ) );
403
    alert.setHeaderText( get( "Dialog.about.header" ) );
404
    alert.setContentText( get( "Dialog.about.content" ) );
405
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
406
    alert.initOwner( getWindow() );
407
408
    alert.showAndWait();
409
  }
410
411
  //---- Convenience accessors ----------------------------------------------
412
  private float getFloat( final String key, final float defaultValue ) {
413
    return getPreferences().getFloat( key, defaultValue );
414
  }
415
416
  private Preferences getPreferences() {
417
    return getOptions().getState();
418
  }
419
420
  private Window getWindow() {
421
    return getScene().getWindow();
422
  }
423
424
  private MarkdownEditorPane getActiveEditor() {
425
    final EditorPane pane = getActiveFileEditor().getEditorPane();
426
427
    return pane instanceof MarkdownEditorPane ? (MarkdownEditorPane)pane : null;
428
  }
429
430
  private FileEditorTab getActiveFileEditor() {
431
    return getFileEditorPane().getActiveFileEditor();
432
  }
433
434
  //---- Member accessors ---------------------------------------------------
435
  private void setScene( Scene scene ) {
436
    this.scene = scene;
437
  }
438
439
  public Scene getScene() {
440
    return this.scene;
441
  }
442
443
  private void setProcessors( final Map<FileEditorTab, Processor<String>> map ) {
444
    this.processors = map;
445
  }
446
447
  private Map<FileEditorTab, Processor<String>> getProcessors() {
448
    if( this.processors == null ) {
449
      setProcessors( new HashMap<>() );
450
    }
451
452
    return this.processors;
453
  }
454
455
  private FileEditorTabPane getFileEditorPane() {
456
    if( this.fileEditorPane == null ) {
457
      this.fileEditorPane = createFileEditorPane();
458
    }
459
460
    return this.fileEditorPane;
461
  }
462
463
  private HTMLPreviewPane getPreviewPane() {
464
    if( this.previewPane == null ) {
465
      this.previewPane = createPreviewPane();
466
    }
467
468
    return this.previewPane;
469
  }
470
471
  private void setDefinitionSource( final DefinitionSource definitionSource ) {
472
    this.definitionSource = definitionSource;
473
  }
474
475
  private DefinitionSource getDefinitionSource() {
476
    if( this.definitionSource == null ) {
477
      this.definitionSource = new EmptyDefinitionSource();
478
    }
479
480
    return this.definitionSource;
481
  }
482
483
  private DefinitionPane getDefinitionPane() {
484
    if( this.definitionPane == null ) {
485
      this.definitionPane = createDefinitionPane();
486
    }
487
488
    return this.definitionPane;
489
  }
490
491
  private Options getOptions() {
492
    return this.options;
493
  }
494
495
  private Snitch getSnitch() {
496
    return this.snitch;
497
  }
498
499
  public void setMenuBar( MenuBar menuBar ) {
500
    this.menuBar = menuBar;
501
  }
502
503
  public MenuBar getMenuBar() {
504
    return this.menuBar;
505
  }
506
507
  //---- Member creators ----------------------------------------------------
508
  /**
509
   * Factory to create processors that are suited to different file types.
510
   *
511
   * @param tab The tab that is subjected to processing.
512
   *
513
   * @return A processor suited to the file type specified by the tab's path.
514
   */
515
  private Processor<String> createProcessor( final FileEditorTab tab ) {
516
    return createProcessorFactory().createProcessor( tab );
517
  }
518
519
  private ProcessorFactory createProcessorFactory() {
520
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
521
  }
522
523
  private DefinitionSource createDefinitionSource( final String path ) {
524
    return createDefinitionFactory().createDefinitionSource( path );
525
  }
526
527
  /**
528
   * Create an editor pane to hold file editor tabs.
529
   *
530
   * @return A new instance, never null.
531
   */
532
  private FileEditorTabPane createFileEditorPane() {
533
    return new FileEditorTabPane();
534
  }
535
536
  private HTMLPreviewPane createPreviewPane() {
537
    return new HTMLPreviewPane();
538
  }
539
540
  private DefinitionPane createDefinitionPane() {
541
    return new DefinitionPane( getTreeView() );
542
  }
543
544
  private DefinitionFactory createDefinitionFactory() {
545
    return new DefinitionFactory();
546
  }
547
548
  private Node createMenuBar() {
549
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
550
551
    // File actions
552
    Action fileNewAction = new Action( get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
553
    Action fileOpenAction = new Action( get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
554
    Action fileCloseAction = new Action( get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
555
    Action fileCloseAllAction = new Action( get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
556
    Action fileSaveAction = new Action( get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
557
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
558
    Action fileSaveAllAction = new Action( get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
559
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
560
    Action fileExitAction = new Action( get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
561
562
    // Edit actions
563
    Action editUndoAction = new Action( get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
564
      e -> getActiveEditor().undo(),
565
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
566
    Action editRedoAction = new Action( get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
567
      e -> getActiveEditor().redo(),
568
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
569
570
    // Insert actions
571
    Action insertBoldAction = new Action( get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
572
      e -> getActiveEditor().surroundSelection( "**", "**" ),
573
      activeFileEditorIsNull );
574
    Action insertItalicAction = new Action( get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
575
      e -> getActiveEditor().surroundSelection( "*", "*" ),
576
      activeFileEditorIsNull );
577
    Action insertStrikethroughAction = new Action( get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
578
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
579
      activeFileEditorIsNull );
580
    Action insertBlockquoteAction = new Action( get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
581
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
582
      activeFileEditorIsNull );
583
    Action insertCodeAction = new Action( get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
584
      e -> getActiveEditor().surroundSelection( "`", "`" ),
585
      activeFileEditorIsNull );
586
    Action insertFencedCodeBlockAction = new Action( get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
587
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", get( "Main.menu.insert.fenced_code_block.prompt" ) ),
588
      activeFileEditorIsNull );
589
590
    Action insertLinkAction = new Action( get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
591
      e -> getActiveEditor().insertLink(),
592
      activeFileEditorIsNull );
593
    Action insertImageAction = new Action( get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
594
      e -> getActiveEditor().insertImage(),
595
      activeFileEditorIsNull );
596
597
    final Action[] headers = new Action[ 6 ];
598
599
    // Insert header actions (H1 ... H6)
600
    for( int i = 1; i <= 6; i++ ) {
601
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
602
      final String markup = String.format( "%n%n%s ", hashes );
603
      final String text = get( "Main.menu.insert.header_" + i );
604
      final String accelerator = "Shortcut+" + i;
605
      final String prompt = get( "Main.menu.insert.header_" + i + ".prompt" );
606
607
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
608
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
609
        activeFileEditorIsNull );
610
    }
611
612
    Action insertUnorderedListAction = new Action( get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
613
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
614
      activeFileEditorIsNull );
615
    Action insertOrderedListAction = new Action( get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
616
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
617
      activeFileEditorIsNull );
618
    Action insertHorizontalRuleAction = new Action( get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
619
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
620
      activeFileEditorIsNull );
621
622
    // Help actions
623
    Action helpAboutAction = new Action( get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
624
625
    //---- MenuBar ----
626
    Menu fileMenu = ActionUtils.createMenu( get( "Main.menu.file" ),
627
      fileNewAction,
628
      fileOpenAction,
629
      null,
630
      fileCloseAction,
631
      fileCloseAllAction,
632
      null,
633
      fileSaveAction,
634
      fileSaveAllAction,
635
      null,
636
      fileExitAction );
637
638
    Menu editMenu = ActionUtils.createMenu( get( "Main.menu.edit" ),
639
      editUndoAction,
640
      editRedoAction );
641
642
    Menu insertMenu = ActionUtils.createMenu( get( "Main.menu.insert" ),
643
      insertBoldAction,
644
      insertItalicAction,
645
      insertStrikethroughAction,
646
      insertBlockquoteAction,
647
      insertCodeAction,
648
      insertFencedCodeBlockAction,
649
      null,
650
      insertLinkAction,
651
      insertImageAction,
652
      null,
653
      headers[ 0 ],
654
      headers[ 1 ],
655
      headers[ 2 ],
656
      headers[ 3 ],
657
      headers[ 4 ],
658
      headers[ 5 ],
659
      null,
660
      insertUnorderedListAction,
661
      insertOrderedListAction,
662
      insertHorizontalRuleAction );
663
664
    Menu helpMenu = ActionUtils.createMenu( get( "Main.menu.help" ),
665
      helpAboutAction );
666
667
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
668
669
    //---- ToolBar ----
670
    ToolBar toolBar = ActionUtils.createToolBar(
671
      fileNewAction,
672
      fileOpenAction,
673
      fileSaveAction,
674
      null,
675
      editUndoAction,
676
      editRedoAction,
677
      null,
678
      insertBoldAction,
679
      insertItalicAction,
328
    // TODO: Update the status bar or do something clever with the error.
329
  }
330
331
  //---- File actions -------------------------------------------------------
332
  /**
333
   * Called when a file has been modified.
334
   *
335
   * @param snitch The watchdog file monitoring instance.
336
   * @param file The file that was modified.
337
   */
338
  @Override
339
  public void update( final Observable snitch, final Object file ) {
340
    if( file instanceof Path ) {
341
      update( (Path)file );
342
    }
343
  }
344
345
  /**
346
   * Called when a file has been modified.
347
   *
348
   * @param file Path to the modified file.
349
   */
350
  private void update( final Path file ) {
351
    // Avoid throwing IllegalStateException by running from a non-JavaFX thread.
352
    Platform.runLater(
353
      () -> {
354
        // Brute-force XSLT file reload by re-instantiating all processors.
355
        resetProcessors();
356
        refreshSelectedTab( getActiveFileEditor() );
357
      }
358
    );
359
  }
360
361
  /**
362
   * After resetting the processors, they will refresh anew to be up-to-date
363
   * with the files (text and definition) currently loaded into the editor.
364
   */
365
  private void resetProcessors() {
366
    getProcessors().clear();
367
  }
368
369
  //---- File actions -------------------------------------------------------
370
  private void fileNew() {
371
    getFileEditorPane().newEditor();
372
  }
373
374
  private void fileOpen() {
375
    getFileEditorPane().openFileDialog();
376
  }
377
378
  private void fileClose() {
379
    getFileEditorPane().closeEditor( getActiveFileEditor(), true );
380
  }
381
382
  private void fileCloseAll() {
383
    getFileEditorPane().closeAllEditors();
384
  }
385
386
  private void fileSave() {
387
    getFileEditorPane().saveEditor( getActiveFileEditor() );
388
  }
389
390
  private void fileSaveAll() {
391
    getFileEditorPane().saveAllEditors();
392
  }
393
394
  private void fileExit() {
395
    final Window window = getWindow();
396
    fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) );
397
  }
398
399
  //---- Help actions -------------------------------------------------------
400
  private void helpAbout() {
401
    Alert alert = new Alert( AlertType.INFORMATION );
402
    alert.setTitle( get( "Dialog.about.title" ) );
403
    alert.setHeaderText( get( "Dialog.about.header" ) );
404
    alert.setContentText( get( "Dialog.about.content" ) );
405
    alert.setGraphic( new ImageView( new Image( FILE_LOGO_32 ) ) );
406
    alert.initOwner( getWindow() );
407
408
    alert.showAndWait();
409
  }
410
411
  //---- Convenience accessors ----------------------------------------------
412
  private float getFloat( final String key, final float defaultValue ) {
413
    return getPreferences().getFloat( key, defaultValue );
414
  }
415
416
  private Preferences getPreferences() {
417
    return getOptions().getState();
418
  }
419
420
  private Window getWindow() {
421
    return getScene().getWindow();
422
  }
423
424
  private MarkdownEditorPane getActiveEditor() {
425
    final EditorPane pane = getActiveFileEditor().getEditorPane();
426
427
    return pane instanceof MarkdownEditorPane ? (MarkdownEditorPane)pane : null;
428
  }
429
430
  private FileEditorTab getActiveFileEditor() {
431
    return getFileEditorPane().getActiveFileEditor();
432
  }
433
434
  //---- Member accessors ---------------------------------------------------
435
  private void setScene( Scene scene ) {
436
    this.scene = scene;
437
  }
438
439
  public Scene getScene() {
440
    return this.scene;
441
  }
442
443
  private void setProcessors( final Map<FileEditorTab, Processor<String>> map ) {
444
    this.processors = map;
445
  }
446
447
  private Map<FileEditorTab, Processor<String>> getProcessors() {
448
    if( this.processors == null ) {
449
      setProcessors( new HashMap<>() );
450
    }
451
452
    return this.processors;
453
  }
454
455
  private FileEditorTabPane getFileEditorPane() {
456
    if( this.fileEditorPane == null ) {
457
      this.fileEditorPane = createFileEditorPane();
458
    }
459
460
    return this.fileEditorPane;
461
  }
462
463
  private HTMLPreviewPane getPreviewPane() {
464
    if( this.previewPane == null ) {
465
      this.previewPane = createPreviewPane();
466
    }
467
468
    return this.previewPane;
469
  }
470
471
  private void setDefinitionSource( final DefinitionSource definitionSource ) {
472
    this.definitionSource = definitionSource;
473
  }
474
475
  private DefinitionSource getDefinitionSource() {
476
    if( this.definitionSource == null ) {
477
      this.definitionSource = new EmptyDefinitionSource();
478
    }
479
480
    return this.definitionSource;
481
  }
482
483
  private DefinitionPane getDefinitionPane() {
484
    if( this.definitionPane == null ) {
485
      this.definitionPane = createDefinitionPane();
486
    }
487
488
    return this.definitionPane;
489
  }
490
491
  private Options getOptions() {
492
    return this.options;
493
  }
494
495
  private Snitch getSnitch() {
496
    return this.snitch;
497
  }
498
499
  public void setMenuBar( MenuBar menuBar ) {
500
    this.menuBar = menuBar;
501
  }
502
503
  public MenuBar getMenuBar() {
504
    return this.menuBar;
505
  }
506
507
  //---- Member creators ----------------------------------------------------
508
  /**
509
   * Factory to create processors that are suited to different file types.
510
   *
511
   * @param tab The tab that is subjected to processing.
512
   *
513
   * @return A processor suited to the file type specified by the tab's path.
514
   */
515
  private Processor<String> createProcessor( final FileEditorTab tab ) {
516
    return createProcessorFactory().createProcessor( tab );
517
  }
518
519
  private ProcessorFactory createProcessorFactory() {
520
    return new ProcessorFactory( getPreviewPane(), getResolvedMap() );
521
  }
522
523
  private DefinitionSource createDefinitionSource( final String path ) {
524
    return createDefinitionFactory().createDefinitionSource( path );
525
  }
526
527
  /**
528
   * Create an editor pane to hold file editor tabs.
529
   *
530
   * @return A new instance, never null.
531
   */
532
  private FileEditorTabPane createFileEditorPane() {
533
    return new FileEditorTabPane();
534
  }
535
536
  private HTMLPreviewPane createPreviewPane() {
537
    return new HTMLPreviewPane();
538
  }
539
540
  private DefinitionPane createDefinitionPane() {
541
    return new DefinitionPane( getTreeView() );
542
  }
543
544
  private DefinitionFactory createDefinitionFactory() {
545
    return new DefinitionFactory();
546
  }
547
548
  private Node createMenuBar() {
549
    final BooleanBinding activeFileEditorIsNull = getFileEditorPane().activeFileEditorProperty().isNull();
550
551
    // File actions
552
    Action fileNewAction = new Action( get( "Main.menu.file.new" ), "Shortcut+N", FILE_ALT, e -> fileNew() );
553
    Action fileOpenAction = new Action( get( "Main.menu.file.open" ), "Shortcut+O", FOLDER_OPEN_ALT, e -> fileOpen() );
554
    Action fileCloseAction = new Action( get( "Main.menu.file.close" ), "Shortcut+W", null, e -> fileClose(), activeFileEditorIsNull );
555
    Action fileCloseAllAction = new Action( get( "Main.menu.file.close_all" ), null, null, e -> fileCloseAll(), activeFileEditorIsNull );
556
    Action fileSaveAction = new Action( get( "Main.menu.file.save" ), "Shortcut+S", FLOPPY_ALT, e -> fileSave(),
557
      createActiveBooleanProperty( FileEditorTab::modifiedProperty ).not() );
558
    Action fileSaveAllAction = new Action( get( "Main.menu.file.save_all" ), "Shortcut+Shift+S", null, e -> fileSaveAll(),
559
      Bindings.not( getFileEditorPane().anyFileEditorModifiedProperty() ) );
560
    Action fileExitAction = new Action( get( "Main.menu.file.exit" ), null, null, e -> fileExit() );
561
562
    // Edit actions
563
    Action editUndoAction = new Action( get( "Main.menu.edit.undo" ), "Shortcut+Z", UNDO,
564
      e -> getActiveEditor().undo(),
565
      createActiveBooleanProperty( FileEditorTab::canUndoProperty ).not() );
566
    Action editRedoAction = new Action( get( "Main.menu.edit.redo" ), "Shortcut+Y", REPEAT,
567
      e -> getActiveEditor().redo(),
568
      createActiveBooleanProperty( FileEditorTab::canRedoProperty ).not() );
569
570
    // Insert actions
571
    Action insertBoldAction = new Action( get( "Main.menu.insert.bold" ), "Shortcut+B", BOLD,
572
      e -> getActiveEditor().surroundSelection( "**", "**" ),
573
      activeFileEditorIsNull );
574
    Action insertItalicAction = new Action( get( "Main.menu.insert.italic" ), "Shortcut+I", ITALIC,
575
      e -> getActiveEditor().surroundSelection( "*", "*" ),
576
      activeFileEditorIsNull );
577
    Action insertSuperscriptAction = new Action( get( "Main.menu.insert.superscript" ), "Shortcut+[", SUPERSCRIPT,
578
      e -> getActiveEditor().surroundSelection( "^", "^" ),
579
      activeFileEditorIsNull );
580
    Action insertSubscriptAction = new Action( get( "Main.menu.insert.subscript" ), "Shortcut+]", SUBSCRIPT,
581
      e -> getActiveEditor().surroundSelection( "~", "~" ),
582
      activeFileEditorIsNull );
583
    Action insertStrikethroughAction = new Action( get( "Main.menu.insert.strikethrough" ), "Shortcut+T", STRIKETHROUGH,
584
      e -> getActiveEditor().surroundSelection( "~~", "~~" ),
585
      activeFileEditorIsNull );
586
    Action insertBlockquoteAction = new Action( get( "Main.menu.insert.blockquote" ), "Ctrl+Q", QUOTE_LEFT, // not Shortcut+Q because of conflict on Mac
587
      e -> getActiveEditor().surroundSelection( "\n\n> ", "" ),
588
      activeFileEditorIsNull );
589
    Action insertCodeAction = new Action( get( "Main.menu.insert.code" ), "Shortcut+K", CODE,
590
      e -> getActiveEditor().surroundSelection( "`", "`" ),
591
      activeFileEditorIsNull );
592
    Action insertFencedCodeBlockAction = new Action( get( "Main.menu.insert.fenced_code_block" ), "Shortcut+Shift+K", FILE_CODE_ALT,
593
      e -> getActiveEditor().surroundSelection( "\n\n```\n", "\n```\n\n", get( "Main.menu.insert.fenced_code_block.prompt" ) ),
594
      activeFileEditorIsNull );
595
596
    Action insertLinkAction = new Action( get( "Main.menu.insert.link" ), "Shortcut+L", LINK,
597
      e -> getActiveEditor().insertLink(),
598
      activeFileEditorIsNull );
599
    Action insertImageAction = new Action( get( "Main.menu.insert.image" ), "Shortcut+G", PICTURE_ALT,
600
      e -> getActiveEditor().insertImage(),
601
      activeFileEditorIsNull );
602
603
    final Action[] headers = new Action[ 6 ];
604
605
    // Insert header actions (H1 ... H6)
606
    for( int i = 1; i <= 6; i++ ) {
607
      final String hashes = new String( new char[ i ] ).replace( "\0", "#" );
608
      final String markup = String.format( "%n%n%s ", hashes );
609
      final String text = get( "Main.menu.insert.header_" + i );
610
      final String accelerator = "Shortcut+" + i;
611
      final String prompt = get( "Main.menu.insert.header_" + i + ".prompt" );
612
613
      headers[ i - 1 ] = new Action( text, accelerator, HEADER,
614
        e -> getActiveEditor().surroundSelection( markup, "", prompt ),
615
        activeFileEditorIsNull );
616
    }
617
618
    Action insertUnorderedListAction = new Action( get( "Main.menu.insert.unordered_list" ), "Shortcut+U", LIST_UL,
619
      e -> getActiveEditor().surroundSelection( "\n\n* ", "" ),
620
      activeFileEditorIsNull );
621
    Action insertOrderedListAction = new Action( get( "Main.menu.insert.ordered_list" ), "Shortcut+Shift+O", LIST_OL,
622
      e -> getActiveEditor().surroundSelection( "\n\n1. ", "" ),
623
      activeFileEditorIsNull );
624
    Action insertHorizontalRuleAction = new Action( get( "Main.menu.insert.horizontal_rule" ), "Shortcut+H", null,
625
      e -> getActiveEditor().surroundSelection( "\n\n---\n\n", "" ),
626
      activeFileEditorIsNull );
627
628
    // Help actions
629
    Action helpAboutAction = new Action( get( "Main.menu.help.about" ), null, null, e -> helpAbout() );
630
631
    //---- MenuBar ----
632
    Menu fileMenu = ActionUtils.createMenu( get( "Main.menu.file" ),
633
      fileNewAction,
634
      fileOpenAction,
635
      null,
636
      fileCloseAction,
637
      fileCloseAllAction,
638
      null,
639
      fileSaveAction,
640
      fileSaveAllAction,
641
      null,
642
      fileExitAction );
643
644
    Menu editMenu = ActionUtils.createMenu( get( "Main.menu.edit" ),
645
      editUndoAction,
646
      editRedoAction );
647
648
    Menu insertMenu = ActionUtils.createMenu( get( "Main.menu.insert" ),
649
      insertBoldAction,
650
      insertItalicAction,
651
      insertSuperscriptAction,
652
      insertSubscriptAction,
653
      insertStrikethroughAction,
654
      insertBlockquoteAction,
655
      insertCodeAction,
656
      insertFencedCodeBlockAction,
657
      null,
658
      insertLinkAction,
659
      insertImageAction,
660
      null,
661
      headers[ 0 ],
662
      headers[ 1 ],
663
      headers[ 2 ],
664
      headers[ 3 ],
665
      headers[ 4 ],
666
      headers[ 5 ],
667
      null,
668
      insertUnorderedListAction,
669
      insertOrderedListAction,
670
      insertHorizontalRuleAction );
671
672
    Menu helpMenu = ActionUtils.createMenu( get( "Main.menu.help" ),
673
      helpAboutAction );
674
675
    menuBar = new MenuBar( fileMenu, editMenu, insertMenu, helpMenu );
676
677
    //---- ToolBar ----
678
    ToolBar toolBar = ActionUtils.createToolBar(
679
      fileNewAction,
680
      fileOpenAction,
681
      fileSaveAction,
682
      null,
683
      editUndoAction,
684
      editRedoAction,
685
      null,
686
      insertBoldAction,
687
      insertItalicAction,
688
      insertSuperscriptAction,
689
      insertSubscriptAction,
680690
      insertBlockquoteAction,
681691
      insertCodeAction,
M src/main/java/com/scrivenvar/decorators/RVariableDecorator.java
3434
 */
3535
public class RVariableDecorator implements VariableDecorator {
36
  public static final String PREFIX = "`r#";
37
  public static final String SUFFIX = "`";
3638
3739
  /**
...
4547
  @Override
4648
  public String decorate( final String variableName ) {
47
    return "`r#x(" + variableName + ")`";
49
    return PREFIX + "x(" + variableName + ")" + SUFFIX ;
4850
  }
4951
}
M src/main/java/com/scrivenvar/decorators/YamlVariableDecorator.java
4747
   * @param variableName The string to decorate.
4848
   *
49
   * @return "$" + variableName + "$".
49
   * @return '$' + variableName + '$';
5050
   */
5151
  @Override
5252
  public String decorate( final String variableName ) {
53
    return "$" + variableName + "$";
53
    return '$' + variableName + '$';
5454
  }
5555
}
A src/main/java/com/scrivenvar/processors/DefaultVariableProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
31
import java.util.Map;
32
33
/**
34
 * Processes variables in the document and inserts their values into the
35
 * post-processed text. The default variable syntax is <code>$variable$</code>.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class DefaultVariableProcessor extends AbstractProcessor<String> {
40
41
  private Map<String, String> definitions;
42
43
  /**
44
   * Constructs a variable processor for dereferencing variables.
45
   *
46
   * @param successor Usually the HTML Preview Processor.
47
   */
48
  private DefaultVariableProcessor( final Processor<String> successor ) {
49
    super( successor );
50
  }
51
52
  public DefaultVariableProcessor(
53
    final Processor<String> successor, final Map<String, String> map ) {
54
    this( successor );
55
    setDefinitions( map );
56
  }
57
58
  /**
59
   * Processes the given text document by replacing variables with their values.
60
   *
61
   * @param text The document text that includes variables that should be
62
   * replaced with values when rendered as HTML.
63
   *
64
   * @return The text with all variables replaced.
65
   */
66
  @Override
67
  public String processLink( final String text ) {
68
    return replace( text, getDefinitions() );
69
  }
70
71
  /**
72
   * Returns the map to use for variable substitution.
73
   *
74
   * @return A map of variable names to values.
75
   */
76
  protected Map<String, String> getDefinitions() {
77
    return this.definitions;
78
  }
79
80
  private void setDefinitions( final Map<String, String> definitions ) {
81
    this.definitions = definitions;
82
  }
83
}
184
A src/main/java/com/scrivenvar/processors/InlineRProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.decorators.RVariableDecorator.PREFIX;
31
import static com.scrivenvar.decorators.RVariableDecorator.SUFFIX;
32
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
33
import static java.lang.Math.min;
34
import java.nio.file.Path;
35
import java.util.Map;
36
import javax.script.ScriptEngine;
37
import javax.script.ScriptEngineManager;
38
import javax.script.ScriptException;
39
40
/**
41
 * Transforms a document containing R statements into Markdown.
42
 *
43
 * @author White Magic Software, Ltd.
44
 */
45
public final class InlineRProcessor extends DefaultVariableProcessor {
46
47
  private ScriptEngine engine;
48
49
  /**
50
   * Constructs a processor capable of evaluating R statements.
51
   *
52
   * @param processor Subsequent link in the processing chain.
53
   * @param map Resolved definitions map.
54
   * @param path Path to the file being edited so that its working directory can
55
   * be extracted. Must not be null.
56
   */
57
  public InlineRProcessor(
58
    final Processor<String> processor,
59
    final Map<String, String> map,
60
    final Path path ) {
61
    super( processor, map );
62
    init( path.getParent() );
63
  }
64
65
  public void init( final Path workingDirectory ) {
66
    eval( replace( ""
67
      + "assign( 'anchor', as.Date( '$date.anchor$', format='%Y-%m-%d' ), envir = .GlobalEnv );"
68
      + "setwd( '" + workingDirectory + "' );"
69
      + "source( '../bin/pluralize.R' );"
70
      + "source( '../bin/common.R' )", getDefinitions() ) );
71
  }
72
73
  @Override
74
  public String processLink( final String text ) {
75
    final int length = text.length();
76
    final int prefixLength = PREFIX.length();
77
78
    // Pre-allocate the same amount of space. A calculation is longer to write
79
    // than its computed value inserted into the text.
80
    final StringBuilder sb = new StringBuilder( length );
81
82
    int prevIndex = 0;
83
    int currIndex = text.indexOf( PREFIX );
84
85
    while( currIndex >= 0 ) {
86
      // Copy everything up to, but not including, an R statement (`r#).
87
      sb.append( text.substring( prevIndex, currIndex ) );
88
89
      prevIndex = currIndex + prefixLength;
90
91
      // Find the statement ending (`), without indexing past the text boundary.
92
      currIndex = text.indexOf( SUFFIX, min( currIndex + 1, length ) );
93
94
      // Only evalutate inline R statements that have end delimiters.
95
      if( currIndex > 1 ) {
96
        // Extract the inline R statement to be evaluated.
97
        final String r = text.substring( prevIndex, currIndex );
98
99
        // Pass the R statement into the R engine for evaluation.
100
        final Object result = eval( r );
101
102
        // Append the string representation of the result into the text.
103
        sb.append( result );
104
105
        // Retain the R statement's ending position in the text.
106
        prevIndex = currIndex + 1;
107
108
      } else {
109
        // There was a starting prefix but no ending suffix. Ignore the
110
        // problem, copy to the end, and exit the loop.
111
//        sb.append()
112
113
      }
114
115
      // Find the start of the next inline R statement.
116
      currIndex = text.indexOf( PREFIX, min( currIndex + 1, length ) );
117
    }
118
119
    // Copy from the previous index to the end of the string.
120
    sb.append( text.substring( min( prevIndex, length ) ) );
121
122
    return sb.toString();
123
  }
124
125
  /**
126
   * Evaluate an R expression and return the resulting object.
127
   *
128
   * @param r The expression to evaluate.
129
   *
130
   * @return The object resulting from the evaluation.
131
   */
132
  private Object eval( final String r ) {
133
    try {
134
      return getScriptEngine().eval( r );
135
    } catch( final ScriptException ex ) {
136
      problem( ex );
137
    }
138
139
    return "";
140
  }
141
142
  private synchronized ScriptEngine getScriptEngine() {
143
    if( this.engine == null ) {
144
      this.engine = (new ScriptEngineManager()).getEngineByName( "Renjin" );
145
    }
146
147
    return this.engine;
148
  }
149
150
  /**
151
   * Notify the user (passively) of the problem.
152
   *
153
   * @param ex A problem parsing the text.
154
   */
155
  private void problem( final Exception ex ) {
156
    // TODO: Use the notify service to warn the user that there's an issue.
157
    System.out.println( ex );
158
  }
159
}
1160
M src/main/java/com/scrivenvar/processors/MarkdownProcessor.java
3030
import com.vladsch.flexmark.Extension;
3131
import com.vladsch.flexmark.ast.Node;
32
import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension;
3233
import com.vladsch.flexmark.ext.gfm.tables.TablesExtension;
3334
import com.vladsch.flexmark.html.HtmlRenderer;
3435
import com.vladsch.flexmark.parser.Parser;
36
import com.vladsch.flexmark.superscript.SuperscriptExtension;
3537
import java.util.ArrayList;
3638
import java.util.List;
...
126128
    final List<Extension> result = new ArrayList<>();
127129
    result.add( TablesExtension.create() );
130
    result.add( SuperscriptExtension.create() );
131
    result.add( StrikethroughSubscriptExtension.create() );
128132
    return result;
129133
  }
D src/main/java/com/scrivenvar/processors/MarkdownVariableProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import static com.scrivenvar.processors.text.TextReplacementFactory.replace;
31
import java.util.Map;
32
33
/**
34
 * Processes variables in the document and inserts their values into the
35
 * post-processed text.
36
 *
37
 * @author White Magic Software, Ltd.
38
 */
39
public class MarkdownVariableProcessor extends AbstractProcessor<String> {
40
41
  private Map<String, String> definitions;
42
43
  /**
44
   * Constructs a new Markdown processor that can create HTML documents.
45
   *
46
   * @param successor Usually the HTML Preview Processor.
47
   */
48
  private MarkdownVariableProcessor( final Processor<String> successor ) {
49
    super( successor );
50
  }
51
52
  public MarkdownVariableProcessor(
53
    final Processor<String> successor, final Map<String, String> map ) {
54
    this( successor );
55
    setDefinitions( map );
56
  }
57
58
  /**
59
   *
60
   * @param text The document text that includes variables that should be
61
   * replaced with values when rendered as HTML.
62
   *
63
   * @return The text with all variables replaced.
64
   */
65
  @Override
66
  public String processLink( final String text ) {
67
    return replace( text, getDefinitions() );
68
  }
69
70
  private Map<String, String> getDefinitions() {
71
    return this.definitions;
72
  }
73
74
  private void setDefinitions( final Map<String, String> definitions ) {
75
    this.definitions = definitions;
76
  }
77
}
781
M src/main/java/com/scrivenvar/processors/ProcessorFactory.java
7979
    switch( fileType ) {
8080
      case RMARKDOWN:
81
        processor = createRMarkdownProcessor( tab );
81
        processor = createRProcessor( tab );
8282
        break;
8383
8484
      case MARKDOWN:
8585
        processor = createMarkdownProcessor( tab );
8686
        break;
8787
8888
      case XML:
8989
        processor = createXMLProcessor( tab );
90
        break;
91
92
      case RXML:
93
        processor = createRXMLProcessor( tab );
9094
        break;
9195
...
104108
   * @return Processors at the end of the processing chain.
105109
   */
106
  private Processor<String> getTerminalProcessChain() {
110
  private Processor<String> getCommonProcessor() {
107111
    if( this.terminalProcessChain == null ) {
108
      this.terminalProcessChain = createCommonChain();
112
      this.terminalProcessChain = createCommonProcessor();
109113
    }
110114
...
117121
   * @return A markdown, caret replacement, and preview pane processor chain.
118122
   */
119
  private Processor<String> createCommonChain() {
123
  private Processor<String> createCommonProcessor() {
120124
    final Processor<String> hpp = new HTMLPreviewProcessor( getPreviewPane() );
121125
    final Processor<String> mcrp = new CaretReplacementProcessor( hpp );
122126
    final Processor<String> mpp = new MarkdownProcessor( mcrp );
123127
124128
    return mpp;
125
  }
126
127
  private Processor<String> createInsertionProcessor(
128
    final Processor<String> tpc, final ObservableValue<Integer> caret ) {
129
    return new MarkdownCaretInsertionProcessor( tpc, caret );
130129
  }
131130
132131
  protected Processor<String> createMarkdownProcessor( final FileEditorTab tab ) {
133132
    final ObservableValue<Integer> caret = tab.caretPositionProperty();
134
    final Processor<String> tpc = getTerminalProcessChain();
133
    final Processor<String> tpc = getCommonProcessor();
135134
    final Processor<String> cip = createInsertionProcessor( tpc, caret );
136
    final Processor<String> vp = new MarkdownVariableProcessor( cip, getResolvedMap() );
135
    final Processor<String> dvp = new DefaultVariableProcessor( cip, getResolvedMap() );
137136
138
    return vp;
137
    return dvp;
139138
  }
140139
141
  protected Processor<String> createRMarkdownProcessor( final FileEditorTab tab ) {
140
  protected Processor<String> createRProcessor( final FileEditorTab tab ) {
142141
    final ObservableValue<Integer> caret = tab.caretPositionProperty();
143
    final Processor<String> tpc = getTerminalProcessChain();
142
    final Processor<String> tpc = getCommonProcessor();
144143
    final Processor<String> cip = createInsertionProcessor( tpc, caret );
145
    final Processor<String> rp = new RProcessor( cip );
146
    
147
    return rp;
144
    final Processor<String> rp = new InlineRProcessor( cip, getResolvedMap(), tab.getPath() );
145
    final Processor<String> rvp = new RVariableProcessor( rp, getResolvedMap() );
146
147
    return rvp;
148148
  }
149149
150150
  protected Processor<String> createXMLProcessor( final FileEditorTab tab ) {
151
    final Processor<String> tpc = getTerminalProcessChain();
151
    final ObservableValue<Integer> caret = tab.caretPositionProperty();
152
    final Processor<String> tpc = getCommonProcessor();
152153
    final Processor<String> xmlp = new XMLProcessor( tpc, tab.getPath() );
153
    final Processor<String> xcip = new XMLCaretInsertionProcessor( xmlp, tab.caretPositionProperty() );
154
    final Processor<String> vp = new MarkdownVariableProcessor( xcip, getResolvedMap() );
154
    final Processor<String> xcip = createXMLInsertionProcessor( xmlp, caret );
155
    final Processor<String> dvp = new DefaultVariableProcessor( xcip, getResolvedMap() );
155156
156
    return vp;
157
    return dvp;
158
  }
159
160
  protected Processor<String> createRXMLProcessor( final FileEditorTab tab ) {
161
    final ObservableValue<Integer> caret = tab.caretPositionProperty();
162
    final Processor<String> tpc = getCommonProcessor();
163
    final Processor<String> xmlp = new XMLProcessor( tpc, tab.getPath() );
164
    final Processor<String> xcip = createXMLInsertionProcessor( xmlp, caret );
165
    final Processor<String> rp = new InlineRProcessor( xcip, getResolvedMap(), tab.getPath() );
166
    final Processor<String> rvp = new RVariableProcessor( rp, getResolvedMap() );
167
168
    return rvp;
169
  }
170
171
  private Processor<String> createInsertionProcessor(
172
    final Processor<String> tpc, final ObservableValue<Integer> caret ) {
173
    return new MarkdownCaretInsertionProcessor( tpc, caret );
174
  }
175
176
  private Processor<String> createXMLInsertionProcessor(
177
    final Processor<String> tpc, final ObservableValue<Integer> caret ) {
178
    return new XMLCaretInsertionProcessor( tpc, caret );
157179
  }
158180
D src/main/java/com/scrivenvar/processors/RProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import javax.script.ScriptEngine;
31
import org.renjin.script.RenjinScriptEngineFactory;
32
33
/**
34
 * Transforms an R document into markdown using knitr:
35
 *
36
 * @author White Magic Software, Ltd.
37
 */
38
public class RProcessor extends AbstractProcessor<String> {
39
40
  public RProcessor( Processor<String> processor ) {
41
    super( processor );
42
  }
43
44
  @Override
45
  public String processLink( final String text ) {
46
    System.out.println( "Renjin + Knitr Smackdown" );
47
48
    RenjinScriptEngineFactory factory = new RenjinScriptEngineFactory();
49
    ScriptEngine engine = factory.getScriptEngine();
50
51
    return text;
52
  }
53
}
541
A src/main/java/com/scrivenvar/processors/RVariableProcessor.java
1
/*
2
 * Copyright 2016 White Magic Software, Ltd.
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 *  o Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 *
12
 *  o Redistributions in binary form must reproduce the above copyright
13
 *    notice, this list of conditions and the following disclaimer in the
14
 *    documentation and/or other materials provided with the distribution.
15
 *
16
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
 */
28
package com.scrivenvar.processors;
29
30
import java.util.HashMap;
31
import java.util.Map;
32
33
/**
34
 * Converts the keys of the resolved map from default form to R form, then
35
 * performs a substitution on the text. The default R variable syntax is
36
 * <code>v$tree$leaf</code>.
37
 *
38
 * @author White Magic Software, Ltd.
39
 */
40
class RVariableProcessor extends DefaultVariableProcessor {
41
42
  public RVariableProcessor(
43
    final Processor<String> rp, final Map<String, String> map ) {
44
    super( rp, map );
45
  }
46
47
  /**
48
   * Returns the R-based version of the interpolated variable definitions.
49
   *
50
   * @return Variable names transmogrified from the default syntax to R syntax.
51
   */
52
  @Override
53
  protected Map<String, String> getDefinitions() {
54
    return toR( super.getDefinitions() );
55
  }
56
57
  /**
58
   * Converts the given map from regular variables to R variables.
59
   *
60
   * @param map Map of variable names to values.
61
   *
62
   * @return
63
   */
64
  private Map<String, String> toR( final Map<String, String> map ) {
65
    final Map<String, String> rMap = new HashMap<>( map.size() );
66
67
    for( final String key : map.keySet() ) {
68
      rMap.put( toR( key ), '\'' + map.get( key ) + '\'' );
69
    }
70
71
    return rMap;
72
  }
73
74
  /**
75
   * Transforms a variable name from $tree.branch.leaf$ to v$tree$branch$leaf
76
   * form.
77
   *
78
   * @param key The variable name to transform, can be empty but not null.
79
   *
80
   * @return The transformed variable name.
81
   */
82
  private String toR( final String key ) {
83
    // Replace all the periods with dollar symbols.
84
    final StringBuilder sb = new StringBuilder( 'v' + key );
85
    final int length = sb.length();
86
87
    // Replace all periods with dollar symbols. Normally we'd check i >= 0,
88
    // but the prepended 'v' is always going to be a 'v', not a dot.
89
    for( int i = length - 1; i > 0; i-- ) {
90
      if( sb.charAt( i ) == '.' ) {
91
        sb.setCharAt( i, '$' );
92
      }
93
    }
94
95
    // The length is always at least 1 (the 'v'), so bounds aren't broken here.
96
    sb.setLength( length - 1 );
97
98
    return sb.toString();
99
  }
100
}
1101
M src/main/java/com/scrivenvar/processors/XMLProcessor.java
205205
        }
206206
      }
207
208
      sr.close();
209207
    }
210208
M src/main/java/com/scrivenvar/processors/text/TextReplacementFactory.java
5959
  /**
6060
   * Convenience method to instantiate a suitable text replacer algorithm and
61
   * perform a replacement using the given map.
61
   * perform a replacement using the given map. At this point, the values should
62
   * be already dereferenced and ready to be substituted verbatim; any
63
   * recursively defined values must have been interpolated previously.
6264
   *
6365
   * @param text The text containing zero or more variables to replace.
6466
   * @param map The map of variables to their dereferenced values.
6567
   *
6668
   * @return The text with all variables replaced.
6769
   */
68
  public static String replace( final String text, final Map<String, String> map ) {
70
  public static String replace(
71
    final String text, final Map<String, String> map ) {
6972
    return getTextReplacer( text.length() ).replace( text, map );
7073
  }
M src/main/resources/com/scrivenvar/messages.properties
5454
Main.menu.insert.bold=Bold
5555
Main.menu.insert.italic=Italic
56
Main.menu.insert.superscript=Superscript
57
Main.menu.insert.subscript=Subscript
5658
Main.menu.insert.strikethrough=Strikethrough
5759
Main.menu.insert.blockquote=Blockquote