Automate switching between languages

This commit is contained in:
Oystein Kristoffer Tveit 2021-02-23 00:46:43 +01:00
parent 1860d4c57f
commit 4e24363e63
14 changed files with 418 additions and 74 deletions

View File

@ -9,41 +9,70 @@ import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import app.events.LanguageChangedEvent;
import app.model.Model;
public class Main extends Application {
private Stage window;
private Scene scene;
private FXMLLoader fxmlLoader;
private Parent fxmlRoot;
private static final String TITLE = "Banana Editor";
private static final String ICON_PATH = "/graphics/logo.png";
/**
* Boilerplate function to launch the application
* Boilerplate function to launch the application.
*/
public static void main(String[] args) {
launch(args);
}
/**
* The entrypoint of the application
* Set up a window with title and icon.
*/
private void setupWindow(Stage window) {
this.window = window;
window.setTitle(TITLE);
window.getIcons().add(new Image(getClass().getResourceAsStream(ICON_PATH)));
}
private void loadFXML() throws IOException {
this.fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
this.fxmlRoot = fxmlLoader.load();
}
private void createScene() {
this.scene = new Scene(fxmlRoot);
Model.setScene(scene);
}
private void applyCSS() {
scene.getStylesheets().add(
getClass().getResource("/styling/app.css").toExternalForm());
}
/**
* Set up default values and settings for the editor.
*/
private void setupDefaults() {
MainController mainController = fxmlLoader.getController();
mainController.getEventBus().post(new LanguageChangedEvent("Java"));
}
/**
* The entrypoint of the application.
* @param window The primary window of the application
*/
@Override
public void start(Stage window) throws IOException {
window.setTitle("Banana Editor");
window.getIcons().add(new Image(getClass().getResourceAsStream("/graphics/logo.png")));
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Main.fxml"));
Parent document;
try {
document = fxmlLoader.load();
} catch (IOException e) {
System.err.println(e);
throw new RuntimeException(
"Couldn\'t find or missing permissions to load "
+ getClass().getResource("/fxml/Main.fxml")
);
}
Scene scene = new Scene(document);
scene.getStylesheets().add(getClass().getResource("/styling/app.css").toExternalForm());
setupWindow(window);
loadFXML();
createScene();
applyCSS();
setupDefaults();
window.setScene(scene);
window.show();

View File

@ -5,8 +5,10 @@ import java.util.List;
import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.controllers.*;
import app.events.LanguageChangedEvent;
import app.model.Model;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
@ -26,11 +28,11 @@ public class MainController implements Initializable {
private MenubarController menubarController;
private EventBus eventBus;
private Model model;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
this.eventBus = new EventBus();
this.eventBus.register(this);
List.of(
editorController,
@ -38,18 +40,35 @@ public class MainController implements Initializable {
modelineController,
menubarController
).forEach(c -> c.setEventBus(this.eventBus));
}
/**
* Get the global eventbus.
* @return The eventbus
*/
public EventBus getEventBus() {
return this.eventBus;
}
/**
* Links the controller to the global model
* @param model The model to be linked
* Change the CSS according to which language is being used.
* @param event The event containing data about the language to switch to
*/
public void setModel(Model model) {
this.model = model;
@Subscribe
private void handle(LanguageChangedEvent event) {
boolean containsSeveralStylesheets = Model.getScene().getStylesheets().size() != 1;
if (containsSeveralStylesheets)
Model.getScene().getStylesheets().remove(1);
String nextStyleSheet =
getClass()
.getResource("/styling/language/" + event.getLanguage().toLowerCase() + ".css")
.toExternalForm();
Model.getScene().getStylesheets().add(nextStyleSheet);
}
}

View File

@ -5,6 +5,7 @@ import java.util.Collection;
import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import org.fxmisc.richtext.CodeArea;
import org.fxmisc.richtext.LineNumberFactory;
@ -13,6 +14,7 @@ import org.fxmisc.richtext.model.TwoDimensional.Bias;
import org.fxmisc.richtext.model.TwoDimensional.Position;
import app.events.EditorChangedEvent;
import app.events.LanguageChangedEvent;
import app.model.Model;
import app.service.LanguageOperations;
import javafx.fxml.FXML;
@ -39,20 +41,26 @@ public class EditorController implements Initializable, Controller {
}
/**
* Links the controller to the global model
* @param model The model to be linked
* Applies highlighting to the editor.
* @param highlighting highlighting data
*/
public void setModel(Model model) {
this.model = model;
}
public void setHighlighting(StyleSpans<Collection<String>> highlighting) {
private void setHighlighting(StyleSpans<Collection<String>> highlighting) {
this.editor.setStyleSpans(0, highlighting);
}
/**
* Handles events from the editor, and reflects them in the model
* @param event The object containing metadata of the event
* Recalculates and refreshes the syntax highlighting of the editor.
*/
private void refreshHighlighting() {
this.setHighlighting(
LanguageOperations.syntaxHighlight(
this.editor.getText(),
Model.getLanguage()));
}
/**
* Handles the event whenever the content of the editor is changed.
* @param event JavaFX event containing metadata
*/
@FXML
public void editorChanged(KeyEvent event) {
@ -64,10 +72,16 @@ public class EditorController implements Initializable, Controller {
pos.getMajor() + 1,
pos.getMinor()
));
this.setHighlighting(
LanguageOperations.syntaxHighlight(
this.editor.getText(),
Model.getLanguage()));
this.refreshHighlighting();
}
/**
* Refreshes the editor whenever the language is changed.
* @param event Which language was switched to
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
this.refreshHighlighting();
}
}

View File

@ -5,11 +5,16 @@ import java.net.URL;
import java.util.ResourceBundle;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.events.LanguageChangedEvent;
import app.model.Model;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.MenuBar;
import javafx.scene.control.RadioMenuItem;
import javafx.scene.control.ToggleGroup;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import javafx.stage.Stage;
@ -17,11 +22,13 @@ import javafx.stage.Stage;
public class MenubarController implements Initializable, Controller {
private EventBus eventBus;
private Model model;
@FXML
private MenuBar menubar;
@FXML
private ToggleGroup languageToggleGroup;
@FXML
public String handleOpenFile() {
FileChooser fc = new FileChooser();
@ -76,12 +83,27 @@ public class MenubarController implements Initializable, Controller {
}
/**
* Links the controller to the global model
*
* @param model The model to be linked
* Handles the event where the language was change from the menu.
*/
public void setModel(Model model) {
this.model = model;
@FXML
private void handleLanguageChange(ActionEvent event) {
this.eventBus.post(
new LanguageChangedEvent(
((RadioMenuItem) event.getSource()).getText()));
}
/**
* Updates menubuttons whenever the language is changed
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
this.languageToggleGroup
.getToggles()
.stream()
.map(RadioMenuItem.class::cast)
.filter(t -> t.getId().equals("toggle" + event.getLanguage()))
.findFirst()
.orElseThrow()
.setSelected(true);
}
}

View File

@ -7,6 +7,7 @@ import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import app.events.EditorChangedEvent;
import app.events.LanguageChangedEvent;
import app.model.Model;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
@ -21,7 +22,6 @@ public class ModelineController implements Initializable, Controller {
private Label language;
private EventBus eventBus;
private Model model;
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
@ -34,14 +34,6 @@ public class ModelineController implements Initializable, Controller {
this.eventBus.register(this);
}
/**
* Links the controller to the global model
* @param model The model to be linked
*/
public void setModel(Model model) {
this.model = model;
}
/**
* Update the colum row counter
* @param column The column number
@ -51,9 +43,23 @@ public class ModelineController implements Initializable, Controller {
this.columnrow.setText(String.format("[%d:%d]", row, column));
}
/**
* Updates the column-row number display whenever the editor cursor
* changes position.
* @param event
*/
@Subscribe
public void handle(EditorChangedEvent event) {
System.out.println("Key pressed: " + event.getKeyCode());
private void handle(EditorChangedEvent event) {
this.setColumnRow(event.getColumn(), event.getLineNumber());
}
/**
* Updates the modeline to display a new language
* whenever it is changed.
* @param event
*/
@Subscribe
private void handle(LanguageChangedEvent event) {
this.language.setText(event.getLanguage());
}
}

View File

@ -2,7 +2,7 @@ package app.events;
import javafx.scene.input.KeyCode;
public class EditorChangedEvent implements Event {
public class EditorChangedEvent extends Event {
private KeyCode keyCode;
private int lineNumber;

View File

@ -1,3 +1,3 @@
package app.events;
interface Event {}
class Event {}

View File

@ -0,0 +1,33 @@
package app.events;
import app.languages.Java;
import app.languages.Markdown;
import app.model.Model;
public class LanguageChangedEvent extends Event {
private String language;
public LanguageChangedEvent(String language) {
this.language = language;
switch (language) {
case "Java":
Model.setLanguage(new Java());
break;
case "Markdown":
Model.setLanguage(new Markdown());
break;
default:
break;
}
}
public String getLanguage() {
return language;
}
}

View File

@ -33,17 +33,20 @@ public class Java implements ProgrammingLanguage {
private static final Map<Pattern, String> pattern =
Map.ofEntries(
e("\\b(" + String.join("|", keywords) + ")\\b", "keyword"), // <public>, <static>, ....
e("this", "this"), // <this>
e("true", "true"), // <true>
e("false", "false"), // <false>
e("\\(|\\)", "paranthesis"), // <(*)>
e("\\{|\\}", "curlyBrackets"), // <{*}>
e("\\[|\\]", "squareBrackets"), // <[*]>
e(";", "semiColon"), // <;>
e("[A-Z]\\w+", "identifier"), // <ClassName> or <InterfaceName>
e("\\.\\w+", "property"), // <.property>
e("(?<=this\\.)?\\w+(?=\\(*\\))", "method") // <this.function(*)>
e("\"([^\"\\\\]|\\\\.)*\"", "string"),
e("\\bthis\\b", "this"),
e("\\btrue\\b", "true"),
e("\\bfalse\\b", "false"),
e("(?<=\\.?)\\w+(?=\\()", "method"),
e("\\(|\\)", "paranthesis"),
e("\\{|\\}", "curlyBrackets"),
e("\\[|\\]", "squareBrackets"),
e(";", "semicolon"),
e("\\.\\w+\\b(?!\\()", "property"),
e("[A-Z]\\w+", "identifier"),
e("\\b(" + String.join("|", keywords) + ")\\b",
"keyword"),
e("(?://.*)|(?:\\/?\\s?\\*.*)", "comment")
);
public String getName() {
@ -58,8 +61,6 @@ public class Java implements ProgrammingLanguage {
return Java.pattern;
}
// https://stackoverflow.com/questions/16517689/java-regex-capturing-groups-indexes/16582083
public Pattern getPattern() {
return Pattern.compile(
Java.pattern

View File

@ -0,0 +1,60 @@
package app.languages;
import java.net.URL;
import java.util.AbstractMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import app.model.ProgrammingLanguage;
public class Markdown implements ProgrammingLanguage {
private String name = "Markdown";
private URL iconPath = this.getClass().getResource("");
private static Entry<Pattern, String> e(String k, String v) {
return new AbstractMap.SimpleEntry<>(Pattern.compile(k), v);
}
private static final Map<Pattern, String> pattern =
Map.ofEntries(
e("##### .*", "ssssheader"),
e("#### .*", "sssheader"),
e("### .*", "ssheader"),
e("## .*", "sheader"),
e("# .*", "header"),
e("\\*\\*\\*.*\\*\\*\\*|___.*___", "emphasizedItalic"),
e("\\*\\*(?!\\*).*\\*\\*|__(?!_).*__", "emphasized"),
e("\\*(?!\\*).*\\*|_(?!_).*_", "italic"),
e("~~.*~~", "strikethrough"),
e("[\\-*+] .*", "listItem"),
e("\\d+\\. .*", "numberedItem"),
e("(?<!\\!)\\[.*\\][\\[\\()].*[\\)\\]]", "link"),
e("!\\[.*\\][\\[\\()].*[\\)\\]]", "image"),
e("\\[\\d+\\]: .*", "source")
);
public String getName() {
return this.name;
}
public URL getIcon() {
return this.iconPath;
}
public Map<Pattern, String> getPatternMap() {
return Markdown.pattern;
}
public Pattern getPattern() {
return Pattern.compile(
Markdown.pattern
.entrySet()
.stream()
.map(e -> String.format("(?<%s>%s)", e.getValue(), e.getKey()))
.collect(Collectors.joining("|")));
}
}

View File

@ -1,12 +1,18 @@
package app.model;
import javafx.scene.Scene;
/**
* Data model of the application
* Data model of the application.
*
* Contains a static reference to state that has to be accessed
* by multiple pieces in the application, including the primary scene.
*/
public class Model {
private static String activeFilePath;
private static String currentProjectPath;
private static ProgrammingLanguage currentProgrammingLanguage;
private static Scene scene;
public static String getActiveFilePath() {
return activeFilePath;
@ -20,7 +26,15 @@ public class Model {
return currentProgrammingLanguage;
}
public static Scene getScene() {
return scene;
}
public static void setLanguage(ProgrammingLanguage language) {
Model.currentProgrammingLanguage = language;
}
public static void setScene(Scene scene) {
Model.scene = scene;
}
}

View File

@ -5,6 +5,8 @@
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.control.RadioMenuItem?>
<MenuBar
fx:id="menubar"
@ -24,10 +26,22 @@
<MenuItem mnemonicParsing="false" text="Save" accelerator="Shortcut+s"/>
<MenuItem mnemonicParsing="false" text="Save as" accelerator="Shortcut+S"/>
<SeparatorMenuItem/>
<fx:define>
<ToggleGroup fx:id="languageToggleGroup"/>
</fx:define>
<Menu mnemonicParsing="false" text="Change programming language">
<items>
<MenuItem mnemonicParsing="false" text="Java"/>
<MenuItem mnemonicParsing="false" text="Markdown"/>
<!-- TODO: Generate buttons based on classes -->
<RadioMenuItem text="Java"
fx:id="toggleJava"
onAction="#handleLanguageChange"
toggleGroup="$languageToggleGroup"/>
<RadioMenuItem text="Markdown"
fx:id="toggleMarkdown"
onAction="#handleLanguageChange"
toggleGroup="$languageToggleGroup"/>
</items>
</Menu>
<SeparatorMenuItem/>

View File

@ -0,0 +1,61 @@
.code-area .this {
-fx-fill: #FFFF00;
-fx-font-weight: bold;
}
.code-area .true {
-fx-fill: #00FF00;
-fx-font-weight: bold;
}
.code-area .false {
-fx-fill: #FF0000;
-fx-font-weight: bold;
}
.code-area .paranthesis {
-fx-fill: -main-fg-color;
}
.code-area .curlyBrackets {
-fx-fill: -main-fg-color;
}
.code-area .squareBrackets {
-fx-fill: -main-fg-color;
}
.code-area .semicolon {
-fx-fill: -main-fg-color;
}
.code-area .identifier {
-fx-fill: -main-blue-color;
-fx-font-weight: bold;
}
.code-area .property {
-fx-fill: -main-fg-color;
}
.code-area .method {
-fx-fill: -main-green-color;
}
.code-area .keyword {
-fx-fill: -main-red-color;
-fx-font-weight: bold;
}
.code-area .comment {
-fx-fill: -light-bg-color;
}
.code-area .string {
-fx-fill: -main-yellow-color;
}
.code-area .escapeChar {
-fx-fill: -main-magenta-color;
}

View File

@ -0,0 +1,71 @@
.code-area {
-test-color: #193814
}
.code-area .header {
-fx-fill: -main-green-color;
-fx-font-size: 200%;
-fx-font-weight: bold;
}
.code-area .sheader {
-fx-fill: -main-blue-color;
-fx-font-size: 150%;
-fx-font-weight: bold;
}
.code-area .ssheader {
-fx-fill: -main-red-color;
-fx-font-size: 130%;
}
.code-area .sssheader {
-fx-fill: -main-magenta-color;
-fx-font-size: 120%;
}
.code-area .ssssheader {
-fx-fill: -main-cyan-color;
-fx-font-size: 110%;
}
.code-area .emphasized {
-fx-fill: -main-blue-color;
-fx-font-weight: bold;
}
.code-area .italic {
-fx-fill: -main-cyan-color;
-fx-font-style: italic;
}
.code-area .emphasizedItalic {
-fx-fill: -main-magenta-color;
-fx-font-weight: bold;
-fx-font-style: italic;
}
.code-area .strikethrough {
-fx-fill: -main-red-color;
-fx-strikethrough: true;
}
.code-area .listItem {
-fx-fill: -main-yellow-color;
}
.code-area .numberedItem {
-fx-fill: -main-yellow-color;
}
.code-area .link {
-fx-fill: -main-red-color;
}
.code-area .image {
-fx-fill: -main-magenta-color;
}
.code-area .source {
-fx-fill: -main-magenta-color;
}