Merge branch 'tempBranch' into 'master'

Miscellaneous changes

See merge request oysteikt/h20-tdt4100-project!7
This commit is contained in:
Oystein Kristoffer Tveit 2021-04-26 19:23:25 +00:00
commit e0d093fa4a
20 changed files with 593 additions and 193 deletions

View File

@ -13,7 +13,6 @@ import app.controllers.*;
import app.events.ExitApplicationEvent; import app.events.ExitApplicationEvent;
import app.events.LanguageChangedEvent; import app.events.LanguageChangedEvent;
import app.events.OpenLinkInBrowserEvent; import app.events.OpenLinkInBrowserEvent;
import app.events.SaveFileEvent;
import app.events.ThemeChangedEvent; import app.events.ThemeChangedEvent;
import app.model.Model; import app.model.Model;
import javafx.application.HostServices; import javafx.application.HostServices;
@ -21,6 +20,9 @@ import javafx.application.Platform;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
/**
* An FXML controller that controls the application and all subcontrollers
*/
public class MainController implements Initializable { public class MainController implements Initializable {
@FXML @FXML
@ -67,7 +69,9 @@ public class MainController implements Initializable {
return hostServices; return hostServices;
} }
// TODO: Document /**
* @return All subcontrollers of this controller
*/
public List<Controller> getInnerControllers() { public List<Controller> getInnerControllers() {
return List.of(editorController, filetreeController, modelineController, menubarController); return List.of(editorController, filetreeController, modelineController, menubarController);
} }
@ -95,13 +99,17 @@ public class MainController implements Initializable {
Model.getScene().getStylesheets().set(position, nextStyleSheet); Model.getScene().getStylesheets().set(position, nextStyleSheet);
} }
/* ------------------------------------------------------------------------ */
/* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */
/** /**
* Change the CSS according to which language is being used * Change the CSS according to which language is being used
* *
* @param event * @param event
*/ */
@Subscribe @Subscribe
private void handle(LanguageChangedEvent event) { public void handle(LanguageChangedEvent event) {
this.setCSSAt(1, "/styling/languages/" + event.getLanguage().toLowerCase() + ".css"); this.setCSSAt(1, "/styling/languages/" + event.getLanguage().toLowerCase() + ".css");
} }
@ -111,7 +119,7 @@ public class MainController implements Initializable {
* @param event * @param event
*/ */
@Subscribe @Subscribe
private void handle(ThemeChangedEvent event) { public void handle(ThemeChangedEvent event) {
this.setCSSAt(0, "/styling/themes/" + event.getTheme().toLowerCase().replace(" ", "-") + ".css"); this.setCSSAt(0, "/styling/themes/" + event.getTheme().toLowerCase().replace(" ", "-") + ".css");
} }
@ -121,7 +129,7 @@ public class MainController implements Initializable {
* @param event * @param event
*/ */
@Subscribe @Subscribe
private void handle(OpenLinkInBrowserEvent event) { public void handle(OpenLinkInBrowserEvent event) {
this.getHostServices().showDocument(event.getLink()); this.getHostServices().showDocument(event.getLink());
} }
@ -133,18 +141,14 @@ public class MainController implements Initializable {
* @param event * @param event
*/ */
@Subscribe @Subscribe
private void handle(ExitApplicationEvent event) { public void handle(ExitApplicationEvent event) {
if (!Model.getFileIsSaved()) { if (!Model.getFileIsSaved()) {
int g = JOptionPane.showConfirmDialog(null, "Your files are not saved.\nSave before exit?", "Exit", int g = JOptionPane.showConfirmDialog(null, "Your files are not saved.\nSave before exit?", "Exit",
JOptionPane.YES_NO_OPTION); JOptionPane.YES_NO_OPTION);
if (g == JOptionPane.YES_OPTION) { if (g == JOptionPane.YES_OPTION)
this.eventBus.post(new SaveFileEvent()); this.editorController.saveCodeArea(Model.getActiveFilePath().isEmpty());
}
Platform.exit(); Platform.exit();
} }
} else {
Platform.exit();
}
}
} }

View File

@ -1,6 +1,14 @@
package app; package app;
/**
* A launcher class to point towards as the start point for a packaged JAR
*/
public class MainLauncher { public class MainLauncher {
/**
* The root function of the call stack
*
* @param args Commandline arguments
*/
public static void main(String[] args) { public static void main(String[] args) {
Main.main(args); Main.main(args);
} }

View File

@ -3,12 +3,12 @@ package app.controllers;
import com.google.common.eventbus.EventBus; import com.google.common.eventbus.EventBus;
/** /**
* Interface describing a controller that contains an EventBus * Interface describing a JavaFX controller that contains an EventBus
*/ */
public interface Controller { public interface Controller {
/** /**
* Registers the main EventBus into the controller. * Registers the main EventBus into the controller.
* @param eventBus The EventBus * @param eventBus
*/ */
public void setEventBus(EventBus eventBus); public void setEventBus(EventBus eventBus);
} }

View File

@ -34,7 +34,7 @@ import javafx.fxml.Initializable;
import javafx.stage.Stage; import javafx.stage.Stage;
/** /**
* A FXML controller that controls the editor component of the UI * An FXML controller that controls the CodeArea
*/ */
public class EditorController implements Initializable, Controller { public class EditorController implements Initializable, Controller {
@ -60,15 +60,10 @@ public class EditorController implements Initializable, Controller {
this.eventBus.register(this); this.eventBus.register(this);
} }
// TODO: document
public CodeArea getEditor() {
return editor;
}
/** /**
* Applies highlighting to the editor. * Applies highlighting to the editor.
* *
* @param highlighting highlighting data * @param highlighting Syntax highlighting data
*/ */
private void setHighlighting(StyleSpans<Collection<String>> highlighting) { private void setHighlighting(StyleSpans<Collection<String>> highlighting) {
this.editor.setStyleSpans(0, highlighting); this.editor.setStyleSpans(0, highlighting);
@ -90,7 +85,6 @@ public class EditorController implements Initializable, Controller {
* ProgrammingLanguage.commentLine(line) * ProgrammingLanguage.commentLine(line)
*/ */
private void toggleComment() { private void toggleComment() {
// TODO: This logic might need to be moved to LanguageOperations
if (editor.getSelectedText().equals("")) { if (editor.getSelectedText().equals("")) {
String currentLine = editor.getText(editor.getCurrentParagraph()); String currentLine = editor.getText(editor.getCurrentParagraph());
@ -118,7 +112,7 @@ public class EditorController implements Initializable, Controller {
/** /**
* Updates the wraptext setting of the code area * Updates the wraptext setting of the code area
* *
* @param isWrapText The new value for the setting * @param isWrapText The updated setting value
*/ */
private void setWrapText(boolean isWrapText) { private void setWrapText(boolean isWrapText) {
this.editor.setWrapText(isWrapText); this.editor.setWrapText(isWrapText);
@ -143,14 +137,16 @@ public class EditorController implements Initializable, Controller {
* *
* @param newContent The String to be inserted into the editor * @param newContent The String to be inserted into the editor
*/ */
public void setEditorContent(String newContent) { private void setEditorContent(String newContent) {
editor.clear(); editor.clear();
editor.appendText(newContent); editor.appendText(newContent);
} }
/** /**
* Saving/Writing to the file based on the spesific filepath. Otherwise it will * Saving/Writing to the file based on the active filepath in {@link app.model.Model Model}
* open an error dialog to give the user feedback about what has happened. * if it is a new File. Otherwise it will open a dialog to ask the user where to save the file.
*
* @param isNewFile Whether or not the file already has a path
*/ */
public void saveCodeArea(boolean isNewFile) { public void saveCodeArea(boolean isNewFile) {
Stage stage = (Stage) editor.getScene().getWindow(); Stage stage = (Stage) editor.getScene().getWindow();
@ -164,17 +160,14 @@ public class EditorController implements Initializable, Controller {
} }
} }
/**
* Checking if all is saved before closing the app. The user can either choose
* to exit or go back to the application and save.
*/
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* SUBSCRIPTIONS */ /* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/** /**
* Updates Code Area (read from file) whenever the FileSelected is changed * Updates the CodeArea whenever a new file is opened.
*
* @param event
*/ */
@Subscribe @Subscribe
public void handle(OpenFileEvent event) { public void handle(OpenFileEvent event) {
@ -187,54 +180,96 @@ public class EditorController implements Initializable, Controller {
} }
/** /**
* Save file (write to file) whenever the save in the menubare is selected * Saves the editor content to a file
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(SaveFileEvent event) { public void handle(SaveFileEvent event) {
this.saveCodeArea(event.getIsNewFile()); this.saveCodeArea(event.getIsNewFile());
} }
/**
* Refreshes the syntax highlighting when the Programming language is changed
*
* @param event
*/
@Subscribe @Subscribe
private void handle(LanguageChangedEvent event) { public void handle(LanguageChangedEvent event) {
this.refreshHighlighting(); this.refreshHighlighting();
} }
/**
* Toggles a comment based on the editor state
*
* @param event
*/
@Subscribe @Subscribe
public void handle(ToggleCommentEvent event) { public void handle(ToggleCommentEvent event) {
this.toggleComment(); this.toggleComment();
} }
/**
* Toggles the WrapText setting
*
* @param event
*/
@Subscribe @Subscribe
private void handle(ToggleWrapTextEvent event) { public void handle(ToggleWrapTextEvent event) {
this.setWrapText(event.getIsWrapped()); this.setWrapText(event.getIsWrapped());
} }
/**
* Undo if focused
*
* @param event
*/
@Subscribe @Subscribe
private void handle(UndoEvent event) { public void handle(UndoEvent event) {
if (this.editor.isFocused()) if (this.editor.isFocused())
this.editor.undo(); this.editor.undo();
} }
/**
* Redo if focused
*
* @param event
*/
@Subscribe @Subscribe
private void handle(RedoEvent event) { public void handle(RedoEvent event) {
if (this.editor.isFocused()) if (this.editor.isFocused())
this.editor.redo(); this.editor.redo();
} }
/**
* Copy selected content if focused
*
* @param event
*/
@Subscribe @Subscribe
private void handle(CopyEvent event) { public void handle(CopyEvent event) {
if (this.editor.isFocused()) if (this.editor.isFocused())
this.editor.copy(); this.editor.copy();
} }
/**
* Cut selected content if focused
*
* @param event
*/
@Subscribe @Subscribe
private void handle(CutEvent event) { public void handle(CutEvent event) {
if (this.editor.isFocused()) if (this.editor.isFocused())
this.editor.cut(); this.editor.cut();
} }
/**
* Paste from clipboard if focused
*
* @param event
*/
@Subscribe @Subscribe
private void handle(PasteEvent event) { public void handle(PasteEvent event) {
if (this.editor.isFocused()) if (this.editor.isFocused())
this.editor.paste(); this.editor.paste();
} }

View File

@ -26,7 +26,7 @@ import app.service.FiletreeOperations;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
/** /**
* A FXML controller that controls the filetree component of the UI * An FXML controller that controls the Filetree
*/ */
public class FiletreeController implements Initializable, Controller { public class FiletreeController implements Initializable, Controller {
@ -44,19 +44,16 @@ public class FiletreeController implements Initializable, Controller {
this.eventBus.register(this); this.eventBus.register(this);
} }
/* ------------------------------------------------------------------------ */
/* FILETREE */
/* ------------------------------------------------------------------------ */
/** /**
* The displaying of the fileTree. The inputChosen(the path) is aquired from the * Generate a tree structure of a directory, and set the filetree to
* eventBus (OpeFileProjectEvent). The root is created as a CheckBoxItems and * show the new tree
* sends it to generateTree, and after that setting it to the root. *
* @param rootDir Path to the directory to be the root of the tree
*/ */
private void showTree(String inputChosen) { private void showTree(Path rootDir) {
CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>(inputChosen); CheckBoxTreeItem<String> root = new CheckBoxTreeItem<>(rootDir.getFileName().toString());
filetree.setShowRoot(false); filetree.setShowRoot(false);
File fileInputChosen = new File(inputChosen); File fileInputChosen = rootDir.toFile();
try { try {
FiletreeOperations.generateTree(fileInputChosen, root); FiletreeOperations.generateTree(fileInputChosen, root);
@ -67,18 +64,13 @@ public class FiletreeController implements Initializable, Controller {
DialogBoxes.showErrorMessage( DialogBoxes.showErrorMessage(
"Could not open folder.\n\n" "Could not open folder.\n\n"
+ "Do you have the right permissions for this folder?\n" + "Do you have the right permissions for this folder?\n"
+ "Or does the folder contain any shortcut to somewhere within itself?"); + "Or does the folder contain any shortcut to somewhere within itself?"
);
} }
} }
/* ------------------------------------------------------------------------ */
/* MouseClick */
/* ------------------------------------------------------------------------ */
/** /**
* Handles whenever a filetree item is clicked twice. A while loop to create the * Handles opening a file whenever a filetree item is clicked twice. */
* correct filepath.
*/
@FXML @FXML
private void handleMouseClick(MouseEvent event) { private void handleMouseClick(MouseEvent event) {
if (event.getClickCount() == 2) { if (event.getClickCount() == 2) {
@ -98,27 +90,33 @@ public class FiletreeController implements Initializable, Controller {
} }
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/* SUBSCRIPTIONS */ /* EVENT BUS LISTENERS */
/* ------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------ */
/** /**
* Updates the filetree whenever a new ProjectPath is selected. * Updates the filetree whenever a new project is opened
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(OpenProjectEvent event) { private void handle(OpenProjectEvent event) {
event.getPath().ifPresentOrElse( event.getPath().ifPresentOrElse(
path -> this.showTree(path.toString()), path -> this.showTree(path),
() -> System.err.println("[ERROR] OpenProjectEvent was empty") () -> System.err.println("[ERROR] OpenProjectEvent was empty")
); );
} }
/**
* Updates the filetree whenever a new file gets saved
*
* @param event
*/
@Subscribe @Subscribe
private void handle(SaveFileEvent event) { private void handle(SaveFileEvent event) {
if (event.getIsNewFile()) if (event.getIsNewFile())
Model Model
.getProjectPath() .getProjectPath()
.ifPresent(path -> this.showTree(path.toString())); .ifPresent(path -> this.showTree(path));
} }
} }

View File

@ -60,28 +60,25 @@ public class MenubarController implements Initializable, Controller {
this.eventBus.register(this); this.eventBus.register(this);
} }
/* ------------------------------------------------------------------------ */ /* ---------------------------------- File ---------------------------------- */
/* CREATE FILE/DIRECTORY */
/* ------------------------------------------------------------------------ */ /**
* Handles whenever the New File button is pressed in the menubar
*
* @param event
*/
@FXML @FXML
private void handleNewFile() { private void handleNewFile() {
this.eventBus.post(new OpenFileEvent(Optional.empty())); this.eventBus.post(new OpenFileEvent(Optional.empty()));
} }
@FXML
private void handleNewFolder() {
}
/* ------------------------------------------------------------------------ */
/* OPEN FILE/PROJECT */
/* ------------------------------------------------------------------------ */
/** /**
* Handles whenever the Open File button is pressed in the menubar * Handles whenever the Open File button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
public void handleOpenFile() { private void handleOpenFile() {
Stage stage = (Stage) menubar.getScene().getWindow(); Stage stage = (Stage) menubar.getScene().getWindow();
try { try {
@ -95,6 +92,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the Open Project button is pressed in the menubar * Handles whenever the Open Project button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleOpenProject() { private void handleOpenProject() {
@ -107,12 +106,11 @@ public class MenubarController implements Initializable, Controller {
} catch (FileNotFoundException e) {} } catch (FileNotFoundException e) {}
} }
/* ------------------------------------------------------------------------ */
/* SAVE FILE */
/* ------------------------------------------------------------------------ */
/** /**
* Handles whenever the Save button is pressed in the menubar * Handles whenever the Save button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleSaveFile() { private void handleSaveFile() {
@ -121,6 +119,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the Save as button is pressed in the menubar * Handles whenever the Save as button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleSaveAsFile() { private void handleSaveAsFile() {
@ -129,6 +129,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the programming language is changed from the menubar. * Handles whenever the programming language is changed from the menubar.
*
* @param event
*/ */
@FXML @FXML
private void handleLanguageChange(ActionEvent event) { private void handleLanguageChange(ActionEvent event) {
@ -137,6 +139,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the wraptext togglebutton is pressed in the menubar * Handles whenever the wraptext togglebutton is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleToggleWraptext(ActionEvent event) { private void handleToggleWraptext(ActionEvent event) {
@ -146,6 +150,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the theme is changed from the menubar * Handles whenever the theme is changed from the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleThemeChange(ActionEvent event) { private void handleThemeChange(ActionEvent event) {
@ -154,18 +160,20 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the exit button is pressed in the menubar * Handles whenever the exit button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleExitApplication(ActionEvent event) { private void handleExitApplication(ActionEvent event) {
this.eventBus.post(new ExitApplicationEvent()); this.eventBus.post(new ExitApplicationEvent());
} }
/* ------------------------------------------------------------------------ */ /* ---------------------------------- Edit ---------------------------------- */
/* EDIT */
/* ------------------------------------------------------------------------ */
/** /**
* Handles whenever the undo button is pressed in the menubar * Handles whenever the undo button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleUndo(ActionEvent event) { private void handleUndo(ActionEvent event) {
@ -174,6 +182,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the redo button is pressed in the menubar * Handles whenever the redo button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleRedo(ActionEvent event) { private void handleRedo(ActionEvent event) {
@ -182,6 +192,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the copy button is pressed in the menubar * Handles whenever the copy button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleCopy(ActionEvent event) { private void handleCopy(ActionEvent event) {
@ -190,6 +202,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the cut button is pressed in the menubar * Handles whenever the cut button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleCut(ActionEvent event) { private void handleCut(ActionEvent event) {
@ -198,6 +212,8 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the paste button is pressed in the menubar * Handles whenever the paste button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handlePaste(ActionEvent event) { private void handlePaste(ActionEvent event) {
@ -206,18 +222,20 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Handles whenever the Toggle Comment button is pressed in the menubar * Handles whenever the Toggle Comment button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleToggleComment(ActionEvent event) { private void handleToggleComment(ActionEvent event) {
this.eventBus.post(new ToggleCommentEvent()); this.eventBus.post(new ToggleCommentEvent());
} }
/* ------------------------------------------------------------------------ */ /* ---------------------------------- About --------------------------------- */
/* ABOUT */
/* ------------------------------------------------------------------------ */
/** /**
* Handles whenever the About button is pressed in the menubar * Handles whenever the About button is pressed in the menubar
*
* @param event
*/ */
@FXML @FXML
private void handleAbout(ActionEvent event) { private void handleAbout(ActionEvent event) {
@ -231,22 +249,37 @@ public class MenubarController implements Initializable, Controller {
/** /**
* Updates menubuttons whenever the language is changed * Updates menubuttons whenever the language is changed
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(LanguageChangedEvent event) { public void handle(LanguageChangedEvent event) {
this.languageToggleGroup.getToggles().stream().map(RadioMenuItem.class::cast) this.languageToggleGroup
.filter(t -> t.getId().equals("toggle" + event.getLanguage())).findFirst().orElseThrow().setSelected(true); .getToggles()
.stream()
.map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getLanguage()))
.findFirst()
// This should never happen!
.orElseThrow(() -> new IllegalStateException("Language button missing: " + event.getLanguage()))
.setSelected(true);
} }
/** /**
* Updates menubuttons whenever the theme is changed * Updates menubuttons whenever the theme is changed
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(ThemeChangedEvent event) { public void handle(ThemeChangedEvent event) {
this.themeToggleGroup.getToggles().stream().map(RadioMenuItem.class::cast) this.themeToggleGroup
.filter(t -> t.getId().equals("toggle" + event.getTheme().replace(" ", "_"))).findFirst().orElseThrow().setSelected(true); .getToggles()
.stream()
.map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getTheme().replace(" ", "_")))
.findFirst()
// This should never happen!
.orElseThrow(() -> new IllegalStateException("Theme button missing: " + event.getTheme()))
.setSelected(true);
} }
} }

View File

@ -53,34 +53,48 @@ public class ModelineController implements Initializable, Controller {
this.columnrow.setText(String.format("[%d:%d]", row, column)); this.columnrow.setText(String.format("[%d:%d]", row, column));
} }
/* ------------------------------------------------------------------------ */
/* SUBSCRIPTIONS */
/* ------------------------------------------------------------------------ */
/** /**
* Updates the column-row number display whenever the editor cursor * Updates the column-row number display whenever the editor cursor
* changes position. * changes position.
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(EditorChangedEvent event) { public void handle(EditorChangedEvent event) {
this.setColumnRow(event.getColumn(), event.getLine()); this.setColumnRow(event.getColumn(), event.getLine());
} }
/** /**
* Updates the saveState label whenever the file either is saved or modified * Updates the saveState label whenever the file either is saved or modified
*
* @param event
*/ */
@Subscribe @Subscribe
private void handle(FileSaveStateChangedEvent event) { public void handle(FileSaveStateChangedEvent event) {
// TODO: Add CSS styleclass for coloring the saveState label // TODO: Add CSS styleclass for coloring the saveState label
// whenever it changes // whenever it changes
this.saveState.setText(event.getIsSaved() ? "Saved!" : "Modified"); this.saveState.setText(event.getIsSaved() ? "Saved!" : "Modified");
} }
/** /**
* Updates the modeline to display a new language * Updates the modeline to display a new language when changed.
* whenever it is changed. *
* @param event
*/ */
@Subscribe @Subscribe
private void handle(LanguageChangedEvent event) { private void handle(LanguageChangedEvent event) {
this.language.setText(event.getLanguage()); this.language.setText(event.getLanguage());
} }
/**
* Updates the modeline to display the name of the current file when changed
*
* @param event
*/
@Subscribe @Subscribe
private void handle(OpenFileEvent event) { private void handle(OpenFileEvent event) {
this.filename.setText( this.filename.setText(

View File

@ -5,22 +5,21 @@ import java.util.regex.Pattern;
/** /**
* An interface describing functions required for a class to * An interface describing functions required for a class to
* provide language specific details and functionality to the * provide language specific details and functionality.
* editor
*/ */
public interface ProgrammingLanguage { public interface ProgrammingLanguage {
/** /**
* The name of the programming language * @return The name of the programming language
*/ */
public String getName(); public String getName();
/** /**
* The map containing the regex and corresponding style-classes to be used for syntax highlighting * @return The map containing the regexes and corresponding style-classes to be used for syntax highlighting
*/ */
public Map<Pattern,String> getPatternMap(); public Map<Pattern,String> getPatternMap();
/** /**
* The pattern containing all regexes for syntax highlighting * @return A combined regex for syntax highlighting
*/ */
public Pattern getPattern(); public Pattern getPattern();
@ -39,8 +38,8 @@ public interface ProgrammingLanguage {
public String unCommentLine(String line); public String unCommentLine(String line);
/** /**
* Whether or not a line is commented
* @param line The text of the line * @param line The text of the line
* @return Whether or not a line is commented
*/ */
public boolean isCommentedLine(String line); public boolean isCommentedLine(String line);
@ -52,15 +51,15 @@ public interface ProgrammingLanguage {
public String commentSelection(String selection); public String commentSelection(String selection);
/** /**
* Uncomment a line * Uncomment an area of text
* @param selection The text of the line to uncomment * @param selection The text of the area to uncomment
* @return The uncommented area * @return The uncommented area
*/ */
public String unCommentSelection(String selection); public String unCommentSelection(String selection);
/** /**
* Whether or not an area of text is commented
* @param selection The content of the area * @param selection The content of the area
* @return Whether or not an area of text is commented
*/ */
public boolean isCommentedSelection(String selection); public boolean isCommentedSelection(String selection);

View File

@ -15,6 +15,7 @@ import app.model.ProgrammingLanguage;
public class Java implements ProgrammingLanguage { public class Java implements ProgrammingLanguage {
private String name = "Java"; private String name = "Java";
private static Map<Pattern, String> pattern;
private static final String[] keywords = new String[] { private static final String[] keywords = new String[] {
"abstract", "assert", "boolean", "break", "byte", "abstract", "assert", "boolean", "break", "byte",
@ -52,9 +53,11 @@ public class Java implements ProgrammingLanguage {
e("(?://.*)|/\\*(?:\\n|.)*?\\*/", "comment") e("(?://.*)|/\\*(?:\\n|.)*?\\*/", "comment")
); );
private static Map<Pattern, String> pattern;
public Java() { public Java() {
this.initializePatternMap();
}
private void initializePatternMap() {
pattern = new LinkedHashMap<>(); pattern = new LinkedHashMap<>();
patternList patternList
.forEach(e -> pattern.put(e.getKey(), e.getValue())); .forEach(e -> pattern.put(e.getKey(), e.getValue()));

View File

@ -14,6 +14,7 @@ import app.model.ProgrammingLanguage;
public class Markdown implements ProgrammingLanguage { public class Markdown implements ProgrammingLanguage {
private String name = "Markdown"; private String name = "Markdown";
private static Map<Pattern, String> pattern;
private static Entry<Pattern, String> e(String k, String v) { private static Entry<Pattern, String> e(String k, String v) {
return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v); return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v);
@ -38,9 +39,12 @@ public class Markdown implements ProgrammingLanguage {
e("\\[\\d+\\]: .*", "source") e("\\[\\d+\\]: .*", "source")
); );
private static Map<Pattern, String> pattern;
public Markdown() { public Markdown() {
this.initializePatternMap();
}
private void initializePatternMap() {
pattern = new LinkedHashMap<>(); pattern = new LinkedHashMap<>();
patternList patternList
.forEach(e -> pattern.put(e.getKey(), e.getValue())); .forEach(e -> pattern.put(e.getKey(), e.getValue()));

View File

@ -1,16 +1,51 @@
package app.service; package app.service;
import java.io.File;
import app.model.Model;
import javafx.scene.control.Alert; import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
public class DialogBoxes { public class DialogBoxes {
private DialogBoxes() {} private DialogBoxes() {}
private static FileChooser fc = new FileChooser();
private static DirectoryChooser dc = new DirectoryChooser();
private static Alert error = new Alert(AlertType.ERROR);
public static void showErrorMessage(String errorMessage) { public static void showErrorMessage(String errorMessage) {
Alert error = new Alert(AlertType.ERROR);
error.setContentText(errorMessage); error.setContentText(errorMessage);
error.showAndWait(); error.showAndWait();
} }
public static File showopenFileWithDialog(Stage stage) {
fc.setTitle("Open File");
File chosenFile = fc.showOpenDialog(stage);
return chosenFile;
}
public static File showOpenFolderWithDialog(Stage stage) {
dc.setTitle("Open Project");
File dir = dc.showDialog(stage);
return dir;
}
public static File showSaveFileWithDialog(Stage stage) {
FileChooser fc = new FileChooser();
fc.setTitle("Save as");
Model
.getProjectPath()
.ifPresent(path -> fc.setInitialDirectory(path.toFile()));
File chosenLocation = fc.showSaveDialog(stage);
return chosenLocation;
}
} }

View File

@ -4,12 +4,11 @@ import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.NoSuchElementException;
import java.util.Optional; import java.util.Optional;
import java.util.Scanner; import java.util.Scanner;
import app.model.Model; import app.model.Model;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
public class FileOperations { public class FileOperations {
@ -20,11 +19,7 @@ public class FileOperations {
// TODO: This class needs to be extensively error checked // TODO: This class needs to be extensively error checked
public static File openFileWithDialog(Stage stage) throws FileNotFoundException { public static File openFileWithDialog(Stage stage) throws FileNotFoundException {
File chosenFile = DialogBoxes.showopenFileWithDialog(stage);
FileChooser fc = new FileChooser();
fc.setTitle("Open File");
File chosenFile = fc.showOpenDialog(stage);
if (chosenFile == null) if (chosenFile == null)
throw new FileNotFoundException(); throw new FileNotFoundException();
@ -34,11 +29,7 @@ public class FileOperations {
} }
public static File openFolderWithDialog(Stage stage) throws FileNotFoundException { public static File openFolderWithDialog(Stage stage) throws FileNotFoundException {
File dir = DialogBoxes.showOpenFolderWithDialog(stage);
DirectoryChooser dc = new DirectoryChooser();
dc.setTitle("Open Project");
File dir = dc.showDialog(stage);
if (dir == null) if (dir == null)
throw new FileNotFoundException(); throw new FileNotFoundException();
@ -58,16 +49,15 @@ public class FileOperations {
} }
public static boolean saveFileWithDialog(Stage stage, String content) { public static boolean saveFileWithDialog(Stage stage, String content) {
FileChooser fc = new FileChooser(); File chosenLocation;
fc.setTitle("Save as");
Model try {
.getProjectPath() chosenLocation = DialogBoxes.showSaveFileWithDialog(stage);
.ifPresent(path -> fc.setInitialDirectory(path.toFile())); } catch (NoSuchElementException e) {
File chosenLocation = fc.showSaveDialog(stage);
if (chosenLocation == null)
return false; return false;
}
if (chosenLocation == null) return false;
if (saveFile(chosenLocation.toPath(), content)) { if (saveFile(chosenLocation.toPath(), content)) {
Model.setActiveFilePath(Optional.of(chosenLocation.toPath())); Model.setActiveFilePath(Optional.of(chosenLocation.toPath()));

View File

@ -17,17 +17,23 @@ import app.model.Model;
public class SettingsProvider implements SettingsProviderI { public class SettingsProvider implements SettingsProviderI {
private static EventBus eventBus; private EventBus eventBus;
private static final String SETTINGS_PATH = private String settingsPath =
(System.getProperty("os.name").startsWith("Windows")) (System.getProperty("os.name").startsWith("Windows"))
? System.getProperty("user.home") + "\\AppData\\Roaming\\/BNNsettings.dat" ? System.getProperty("user.home") + "\\AppData\\Roaming\\/BNNsettings.dat"
: System.getProperty("user.home") + System.getProperty("file.separator") + ".BNNsettings.dat"; : System.getProperty("user.home") + System.getProperty("file.separator") + ".BNNsettings.dat";
private static List<String> legalSettings = private List<String> legalSettings =
Arrays.asList("Java", "Markdown", "Monokai", "Solarized Light"); Arrays.asList("Java", "Markdown", "Monokai", "Solarized Light");
// Only for testing purposes
protected void setSettingsPath(String settingsPath) {
this.settingsPath = settingsPath;
}
public SettingsProvider(EventBus eB) { public SettingsProvider(EventBus eB) {
setEventBus(eB); setEventBus(eB);
Model.setSettingsProvider(this); Model.setSettingsProvider(this);
@ -35,13 +41,13 @@ public class SettingsProvider implements SettingsProviderI {
public void setEventBus(EventBus eB) { public void setEventBus(EventBus eB) {
eventBus = eB; eventBus = eB;
SettingsProvider.eventBus.register(this); eventBus.register(this);
} }
@Override @Override
public void loadSettings() { public void loadSettings() {
List<String> settings = new ArrayList<>(); List<String> settings = new ArrayList<>();
try (Scanner sc = new Scanner(new File(SETTINGS_PATH))) { try (Scanner sc = new Scanner(new File(settingsPath))) {
while (sc.hasNextLine()) { while (sc.hasNextLine()) {
var nextLine = sc.nextLine().trim(); var nextLine = sc.nextLine().trim();
@ -69,7 +75,7 @@ public class SettingsProvider implements SettingsProviderI {
@Override @Override
public void saveSettings() { public void saveSettings() {
try (PrintWriter writer = new PrintWriter(new File(SETTINGS_PATH))) { try (PrintWriter writer = new PrintWriter(new File(settingsPath))) {
writer.println("- Settings:"); writer.println("- Settings:");
writer.println("Programming Language = " + Model.getLanguage().getName()); writer.println("Programming Language = " + Model.getLanguage().getName());
writer.println("Theme = " + Model.getTheme()); writer.println("Theme = " + Model.getTheme());

View File

@ -2,8 +2,14 @@ package app.settings;
public interface SettingsProviderI { public interface SettingsProviderI {
/**
* Load settings from disk, and fire events to update the program state
*/
void loadSettings(); void loadSettings();
/**
* Save the state from {@link app.model.Model Model} to disk.
*/
void saveSettings(); void saveSettings();
} }

View File

@ -19,7 +19,6 @@
<Menu mnemonicParsing="false" text="File"> <Menu mnemonicParsing="false" text="File">
<items> <items>
<MenuItem mnemonicParsing="false" text="New File" accelerator="Shortcut+n" onAction="#handleNewFile"/> <MenuItem mnemonicParsing="false" text="New File" accelerator="Shortcut+n" onAction="#handleNewFile"/>
<MenuItem mnemonicParsing="false" text="New Folder" accelerator="Shortcut+Shift+N" onAction="#handleNewFolder"/>
<SeparatorMenuItem/> <SeparatorMenuItem/>
<MenuItem mnemonicParsing="false" text="Open File" accelerator="Shortcut+o" onAction="#handleOpenFile"/> <MenuItem mnemonicParsing="false" text="Open File" accelerator="Shortcut+o" onAction="#handleOpenFile"/>
<MenuItem mnemonicParsing="false" text="Open Project" accelerator="Shortcut+Shift+O" onAction="#handleOpenProject"/> <MenuItem mnemonicParsing="false" text="Open Project" accelerator="Shortcut+Shift+O" onAction="#handleOpenProject"/>

View File

@ -1,44 +0,0 @@
package app;
import javafx.scene.Node;
import javafx.stage.Stage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.testfx.api.FxToolkit;
import org.testfx.framework.junit5.ApplicationTest;
import org.testfx.util.WaitForAsyncUtils;
import java.util.concurrent.TimeoutException;
public class FxTestTemplate extends ApplicationTest {
private Stage stage;
@BeforeEach
public void runAppToTests() throws Exception {
FxToolkit.registerPrimaryStage();
FxToolkit.setupApplication(Main::new);
FxToolkit.showStage();
WaitForAsyncUtils.waitForFxEvents(100);
}
@AfterEach
public void stopApp() throws TimeoutException {
FxToolkit.cleanupStages();
}
@Override
public void start(Stage primaryStage){
this.stage = primaryStage;
primaryStage.toFront();
}
public Stage getStage() {
return stage;
}
public <T extends Node> T find(final String query) {
/** TestFX provides many operations to retrieve elements from the loaded GUI. */
return lookup(query).query();
}
}

View File

@ -2,6 +2,7 @@ package app.controllers;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths;
import java.util.Optional; import java.util.Optional;
import org.fxmisc.richtext.CodeArea; import org.fxmisc.richtext.CodeArea;
@ -19,7 +20,6 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -63,7 +63,7 @@ public class EditorControllerTest extends FxTestTemplate {
private String mockContent = """ private String mockContent = """
class HelloWorld { class HelloWorld {
private String message = "Hello world"; private String message = \"Hello world\";
public String getMessage() { public String getMessage() {
return message; return message;
@ -100,11 +100,15 @@ public class EditorControllerTest extends FxTestTemplate {
@Test @Test
@DisplayName("Test handling of OpenFileEvent with a file that doesn't exist") @DisplayName("Test handling of OpenFileEvent with a file that doesn't exist")
public void testOpenFileEventWithUnrealFile() throws IOException { public void testOpenFileEventWithUnrealFile() throws IOException {
try (MockedStatic<FileOperations> mocked = mockStatic(FileOperations.class)) {
mocked.when(() -> FileOperations.readFile(any()))
.thenReturn(null);
String brokenFilePath = "/doesNotExist.txt"; String brokenFilePath = "/doesNotExist.txt";
eventBus.post(new OpenFileEvent(Optional.ofNullable(new File(brokenFilePath).toPath()))); eventBus.post(new OpenFileEvent(Optional.ofNullable(Paths.get(brokenFilePath))));
verify(editor, never()).clear(); verify(editor).appendText("");
}
} }
@Test @Test

View File

@ -0,0 +1,8 @@
package app.service;
public class DialogBoxesTest {
// THIS CLASS COULD NOT BE UNITTESTED BECAUSE OF LACKING SUPPORT FOR MOCKING
// STATIC OBJECTS WITH MOCKITO AND JUNI5
}

View File

@ -0,0 +1,190 @@
package app.service;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import com.google.common.io.Files;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
import app.model.Model;
import javafx.scene.control.Alert;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
@ExtendWith(MockitoExtension.class)
public class FileOperationsTest {
// THIS CLASS COULD NOT BE UNITTESTED BECAUSE OF LACKING SUPPORT FOR MOCKING
// STATIC OBJECTS WITH MOCKITO AND JUNI5
// @TempDir
// File tmp;
// @Mock
// FileChooser fc = mock(FileChooser.class);
// @Mock
// DirectoryChooser dc = mock(DirectoryChooser.class);
// @Mock
// Alert error = mock(Alert.class);
// @InjectMocks
// MockedStatic<DialogBoxes> db = mockStatic(DialogBoxes.class);
// @Test
// @DisplayName("Test openFileWithDialog")
// public void testOpenFileWithDialog() {
// // try (MockedStatic<DialogBoxes> mocked = mockStatic(DialogBoxes.class)) {
// Stage stage = mock(Stage.class);
// db.when(() -> DialogBoxes.showopenFileWithDialog(any()))
// .thenReturn(null);
// assertThrows(FileNotFoundException.class, () -> FileOperations.openFileWithDialog(stage));
// File file = mock(File.class);
// db.when(() -> DialogBoxes.showopenFileWithDialog(any()))
// .thenReturn(file);
// try {
// assertEquals(file, FileOperations.openFileWithDialog(stage));
// } catch (FileNotFoundException e) {
// fail("Chosen file was null when it was expected to be mock file");
// }
// // }
// }
// @Test
// @DisplayName("Test openFolderWithDialog")
// public void testOpenFolderWithDialog() {
// try (MockedStatic<DialogBoxes> mocked = mockStatic(DialogBoxes.class)) {
// Stage stage = mock(Stage.class);
// mocked.when(() -> DialogBoxes.showOpenFolderWithDialog(any()))
// .thenReturn(null);
// assertThrows(FileNotFoundException.class, () -> FileOperations.openFolderWithDialog(stage));
// File file = mock(File.class);
// mocked.when(() -> DialogBoxes.showOpenFolderWithDialog(any()))
// .thenReturn(file);
// try {
// assertEquals(file, FileOperations.openFolderWithDialog(stage));
// } catch (FileNotFoundException e) {
// fail("Chosen file was null when it was expected to be mock file");
// }
// }
// }
// private File createTemporaryFile() throws IOException {
// File f = new File(tmp, "test.txt");
// f.createNewFile();
// return f;
// }
// @Test
// @DisplayName("Test saveFile")
// public void testSaveFile() {
// String content = "test\ncontent\nfor\nyou";
// File f;
// try (MockedStatic<DialogBoxes> mocked = mockStatic(DialogBoxes.class)) {
// // mocked.when(() -> DialogBoxes.showErrorMessage(anyString()));
// f = createTemporaryFile();
// assertTrue(FileOperations.saveFile(f.toPath(), content));
// List<String> read = Files.readLines(f, StandardCharsets.UTF_8);
// String value = String.join("\n", read);
// assertEquals(content, value);
// Path wrongPath = Paths.get("wrongPath.txt");
// assertFalse(FileOperations.saveFile(wrongPath, content));
// } catch (IOException e) {
// fail("Unexpected temporary file failure");
// }
// }
// @Test
// @DisplayName("Test saveFileWithDialog")
// public void testSaveFileWithDialog() {
// String content = "test\ncontent\nfor\nyou";
// File f;
// try (MockedStatic<DialogBoxes> mocked = mockStatic(DialogBoxes.class)) {
// Stage stage = mock(Stage.class);
// mocked.when(() -> DialogBoxes.showSaveFileWithDialog(any()))
// .thenReturn(false);
// assertFalse(FileOperations.saveFileWithDialog(stage, content));
// mocked.when(() -> DialogBoxes.showSaveFileWithDialog(any()))
// .thenReturn(null);
// assertFalse(FileOperations.saveFileWithDialog(stage, content));
// f = createTemporaryFile();
// mocked.when(() -> DialogBoxes.showSaveFileWithDialog(any()))
// .thenReturn(f);
// assertTrue(FileOperations.saveFileWithDialog(stage, content));
// assertEquals(Model.getActiveFilePath(), f.toPath());
// File wrongFile = new File("Does not exist");
// mocked.when(() -> DialogBoxes.showSaveFileWithDialog(any()))
// .thenReturn(wrongFile);
// assertFalse(FileOperations.saveFileWithDialog(stage, content));
// } catch (IOException e) {
// fail("Unexpected IOexception when creating temporary file");
// }
// }
// @Test
// @DisplayName("Test readFile")
// public void testReadFile() {
// File f;
// try (MockedStatic<DialogBoxes> mocked = mockStatic(DialogBoxes.class)) {
// // mocked.when(() -> DialogBoxes.showErrorMessage(anyString()));
// assertEquals("", FileOperations.readFile(null));
// String content = "test\ncontent\nfor\nyou";
// f = createTemporaryFile();
// Files.write(content.getBytes(), f);
// assertEquals(content, FileOperations.readFile(f.toPath()));
// Path wrongPath = Paths.get("wrongPath.txt");
// assertThrows(FileNotFoundException.class, () -> FileOperations.readFile(wrongPath));
// } catch (IOException e) {
// fail("Unexpected temporary file failure");
// }
// }
}

View File

@ -0,0 +1,108 @@
package app.settings;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import com.google.common.eventbus.EventBus;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.junit.jupiter.MockitoExtension;
import app.model.Model;
import app.model.languages.Java;
import app.model.languages.Markdown;
@ExtendWith(MockitoExtension.class)
public class SettingsProviderTest {
@TempDir
File tmp;
private EventBus eventBus = new EventBus();
private SettingsProvider sp = new SettingsProvider(eventBus);
@BeforeEach
private void initializeSettingsPath() {
sp.setSettingsPath(Paths.get(tmp.toPath().toString(), "BNNsettings.dat").toString());
}
@Test
@DisplayName("Test loadSettings with pre-existing settings file")
public void testLoadSettings() throws IOException {
File f = new File(tmp, "BNNsettings.dat");
f.createNewFile();
Files.writeString(
f.toPath(),
"- Settings:\n"
+ "Programming Language = Markdown\n"
+ "Theme = Solarized Light",
StandardOpenOption.WRITE
);
sp.loadSettings();
assertTrue(Model.getLanguage() instanceof Markdown);
assertEquals("Solarized Light", Model.getTheme());
}
@Test
@DisplayName("Test loadSettings without pre-existing settings file")
public void testLoadSettingsWithoutFile() throws IOException {
sp.loadSettings();
assertTrue(Model.getLanguage() instanceof Java);
assertEquals("Monokai", Model.getTheme());
}
@Test
@DisplayName("Test loadSettings with broken settings file")
public void testLoadSettingsWithErrorFile() throws IOException {
File f = new File(tmp, "BNNsettings.dat");
f.createNewFile();
Files.writeString(
f.toPath(),
"- Settings:\n"
+ "Programming Language = Nonexisting Language\n"
+ "Theme = Solarized Light",
StandardOpenOption.WRITE
);
sp.loadSettings();
assertTrue(Model.getLanguage() instanceof Java);
assertEquals("Monokai", Model.getTheme());
}
@Test
@DisplayName("Test save settings")
public void testSaveSettings() {
Model.setLanguage(new Markdown());
Model.setTheme("Solarized Light");
sp.saveSettings();
try {
assertEquals(
"- Settings:\n"
+ "Programming Language = Markdown\n"
+ "Theme = Solarized Light\n",
Files.readString(Paths.get(tmp.toString(), "BNNsettings.dat"))
);
} catch (IOException e) {
fail("Couldn't read settings file");
}
}
}