Skip to content

Commit

Permalink
Adding search/find to the code editor
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobias-Kohn committed Mar 18, 2023
1 parent 7a845c8 commit fe4abb7
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 35 deletions.
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ Bug fixes / fixed issues:
stuck (e.g. because of non-matching parentheses and brackets), which should now be avoided;

Known issues:
- Search/find and replace are still missing;
- Code completion is not implemented, yet;
- It seems possible to have two programs with the same name in the system;

### 3.0 Beta 1 (January 2023)

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/tigerjython/files/Documents.scala
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ object Documents {

def makeDocumentNameUnique(name: String): String =
if (findDocumentWithName(name).isDefined) {
if (name.toLowerCase.endsWith("(copy)") || name.toLowerCase.endsWith("(kopie)")) {
if (name.toLowerCase.endsWith("(copy)")) {
var i = 2
val n = name.dropRight(1) + " %d)"
while (findDocumentWithName(n.format(i)).isDefined)
Expand All @@ -122,7 +122,7 @@ object Documents {
val n =
if (index > 0) {
if (name.toLowerCase.endsWith("(copy)"))
"%s %d)".format(name.dropRight(5), index)
"%s %d)".format(name.dropRight(1), index)
else
"%s (%d)".format(name, index)
} else
Expand Down
162 changes: 131 additions & 31 deletions src/main/scala/tigerjython/ui/editor/FindBar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ package tigerjython.ui.editor

import javafx.application.Platform
import javafx.beans.value.{ChangeListener, ObservableValue}
import javafx.scene.Scene
import javafx.scene.control.{TextField, ToolBar}
import javafx.scene.{Group, Node, Scene}
import javafx.scene.control.{Button, Label, TextField, ToolBar}
import javafx.scene.input.KeyCode
import javafx.scene.shape.{Line, StrokeLineCap}

import java.lang

/**
* A simple bar that lets you enter something to be found in the document.
Expand All @@ -20,19 +23,44 @@ class FindBar(val editorTab: EditorTab) extends ToolBar {

private val editor = editorTab.editor
val findTextField: TextField = new TextField()
private val countLabel: Label = new Label()
private val findNextButton = new Button()
private val findPrevButton = new Button()

private var startPos: Int = 0
private var endPos: Int = 0

private var startIndex: Int = 0
private var endIndex: Int = 0
private var currentIndex: Int = 0
private var searchText: String = _
private var fullText: String = _
private var fullTextLC: String = _

private var occurrences: Array[Int] = _

{
getItems.add(findTextField)
getItems.addAll(findTextField, findNextButton, findPrevButton, countLabel)
setPrefHeight(48)
setMinHeight(32)
setPrefWidth(300)
findTextField.setPrefWidth(300)
findTextField.prefWidthProperty().bind(this.widthProperty().divide(3))
findNextButton.setGraphic(createDownImage)
findNextButton.getStyleClass.add("download-text-btn")
findNextButton.setOnAction(_ => findNext())
findNextButton.setFocusTraversable(false)
findPrevButton.setGraphic(createUpImage)
findPrevButton.getStyleClass.add("download-text-btn")
findPrevButton.setOnAction(_ => findPrev())
findPrevButton.setFocusTraversable(false)
}

focusedProperty().addListener(new ChangeListener[java.lang.Boolean] {
override def changed(observableValue: ObservableValue[_ <: lang.Boolean], oldValue: lang.Boolean, newValue: lang.Boolean): Unit = {
if (newValue && !oldValue) {
updateText()
}
}
})

findTextField.sceneProperty().addListener(new ChangeListener[Scene] {
override def changed(observableValue: ObservableValue[_ <: Scene], oldValue: Scene, newValue: Scene): Unit = {
if (oldValue != newValue && newValue != null)
Expand All @@ -57,50 +85,122 @@ class FindBar(val editorTab: EditorTab) extends ToolBar {
}
})

private def createDownImage: Node = {
val result = new Group()
val lines = Array(
new Line(-4, -2, 0, 2),
new Line(0, 2, 4, -2),
)
for (line <- lines)
line.setStrokeLineCap(StrokeLineCap.ROUND)
result.getChildren.addAll(lines: _*)
result
}

private def createUpImage: Node = {
val result = new Group()
val lines = Array(
new Line(-4, 2, 0, -2),
new Line(0, -2, 4, 2),
)
for (line <- lines)
line.setStrokeLineCap(StrokeLineCap.ROUND)
result.getChildren.addAll(lines: _*)
result
}

def activate(): Unit = {
Platform.runLater(() => {
this.requestFocus()
val selRange = editor.selectionProperty().getValue
if (selRange.getLength > 0) {
startIndex = selRange.getStart
endIndex = selRange.getEnd
startPos = selRange.getStart
endPos = selRange.getEnd
} else {
startIndex = 0
endIndex = -1
startPos = 0
endPos = -1
}
fullText = null
updateText()
findTextField.setText("")
findTextField.requestFocus()
})
}

private def doFindText(start: Int): Boolean = {
if (searchText != null && searchText != "") {
val s: String = editor.textProperty().getValue
val index = s.indexOf(searchText, start)
if (index >= start) {
editor.selectRange(index, index + searchText.length)
currentIndex = index
private def hasUppercase(s: String): Boolean = {
for (ch <- s)
if (ch.isUpper)
return true
false
}

private def doFindAll(): Array[Int] =
if (searchText != null && searchText != "") {
val s: String = if (hasUppercase(searchText)) fullText else fullTextLC
val result = collection.mutable.ArrayBuffer[Int]()
val lastIndex =
if (endPos == -1)
s.length - 1
else
endPos - searchText.length
var idx = s.indexOf(searchText, startPos)
while (startPos <= idx && idx <= lastIndex) {
result += idx
idx = s.indexOf(searchText, idx + 1)
}
result.toArray
} else
Array()

private def selectEntry(idx: Int): Unit =
if (idx < 0 && occurrences.nonEmpty)
selectEntry(idx + occurrences.length)
else if (occurrences.nonEmpty && searchText != "") {
currentIndex =
if (occurrences.length > 1)
idx % occurrences.length
else
0
val pos = occurrences(currentIndex)
editor.selectRange(pos, pos + searchText.length)
countLabel.setText("%d/%d".format(currentIndex + 1, occurrences.length))
} else {
if (endPos > startPos)
editor.selectRange(startPos, endPos)
else
editor.selectRange(startPos, startPos)
countLabel.setText("0/0")
currentIndex = -1
}
if (endIndex > startIndex)
editor.selectRange(startIndex, endIndex)
else
editor.selectRange(startIndex, startIndex)
currentIndex = -1
false

private def nextIndexAfterPos: Int = {
val curPos = editor.selectionProperty().getValue.getStart
for ((pos, i) <- occurrences.zipWithIndex)
if (curPos <= pos)
return i
0
}

protected def findFirst(searchText: String): Unit = {
if (this.searchText == null || !searchText.startsWith(this.searchText))
currentIndex = startIndex
currentIndex = startPos
this.searchText = searchText
doFindText(currentIndex)
occurrences = doFindAll()
if (occurrences.nonEmpty)
selectEntry(nextIndexAfterPos)
else
selectEntry(0)
}

protected def findNext(): Unit =
if (currentIndex >= 0) {
if (!doFindText(currentIndex + 1))
doFindText(startIndex)
}
protected def findNext(): Unit = {
selectEntry(currentIndex + 1)
}

protected def findPrev(): Unit = {
selectEntry(currentIndex - 1)
}

protected def updateText(): Unit = {
fullText = editor.textProperty().getValue
fullTextLC = fullText.toLowerCase
}
}

0 comments on commit fe4abb7

Please sign in to comment.