155 lines
7.8 KiB
Swift
155 lines
7.8 KiB
Swift
import UIKit
|
|
import SwiftUI
|
|
import Runestone
|
|
|
|
final class EditorCommandHandler: UIViewController {
|
|
|
|
weak var textView: Runestone.TextView?
|
|
var onSave: (() -> Void)?
|
|
var onBuild: (() -> Void)?
|
|
var onRun: (() -> Void)?
|
|
var onToggleNavigator: (() -> Void)?
|
|
var onToggleConsole: (() -> Void)?
|
|
var onFind: (() -> Void)?
|
|
var onJumpToLine: (() -> Void)?
|
|
var onFindInProject: (() -> Void)?
|
|
|
|
override var canBecomeFirstResponder: Bool { true }
|
|
|
|
override var keyCommands: [UIKeyCommand]? {
|
|
let cmds: [(String, String, UIKeyModifierFlags)] = [
|
|
("Save", "s", .command),
|
|
("Undo", "z", .command),
|
|
("Redo", "z", [.command, .shift]),
|
|
("Indent", "]", .command),
|
|
("Unindent", "[", .command),
|
|
("Toggle Comment", "/", .command),
|
|
("Move Line Up", UIKeyCommand.inputUpArrow, [.command, .alternate]),
|
|
("Move Line Down", UIKeyCommand.inputDownArrow, [.command, .alternate]),
|
|
("Duplicate Line", "d", [.command, .shift]),
|
|
("Delete Line", "k", [.command, .shift]),
|
|
("Find", "f", .command),
|
|
("Find in Project", "f", [.command, .shift]),
|
|
("Find Next", "g", .command),
|
|
("Find Prev", "g", [.command, .shift]),
|
|
("Jump to Line", "l", .command),
|
|
("Top", UIKeyCommand.inputUpArrow, .command),
|
|
("Bottom", UIKeyCommand.inputDownArrow, .command),
|
|
("Build", "b", .command),
|
|
("Run", "r", .command),
|
|
("Navigator", "0", .command),
|
|
("Console", "y", [.command, .shift]),
|
|
("Font+", "+", .command),
|
|
("Font-", "-", .command),
|
|
]
|
|
return cmds.map { title, input, mods in
|
|
let sel = selectorFor(title)
|
|
let kc = UIKeyCommand(title: title, action: sel, input: input, modifierFlags: mods, discoverabilityTitle: title)
|
|
if [UIKeyCommand.inputUpArrow, UIKeyCommand.inputDownArrow].contains(input) {
|
|
kc.wantsPriorityOverSystemBehavior = true
|
|
}
|
|
return kc
|
|
}
|
|
}
|
|
|
|
private func selectorFor(_ title: String) -> Selector {
|
|
switch title {
|
|
case "Save": return #selector(cmdSave)
|
|
case "Undo": return #selector(cmdUndo)
|
|
case "Redo": return #selector(cmdRedo)
|
|
case "Indent": return #selector(cmdIndent)
|
|
case "Unindent": return #selector(cmdUnindent)
|
|
case "Toggle Comment": return #selector(cmdComment)
|
|
case "Move Line Up": return #selector(cmdMoveUp)
|
|
case "Move Line Down": return #selector(cmdMoveDown)
|
|
case "Duplicate Line": return #selector(cmdDuplicate)
|
|
case "Delete Line": return #selector(cmdDeleteLine)
|
|
case "Find": return #selector(cmdFind)
|
|
case "Find in Project": return #selector(cmdFindProject)
|
|
case "Find Next": return #selector(cmdFindNext)
|
|
case "Find Prev": return #selector(cmdFindPrev)
|
|
case "Jump to Line": return #selector(cmdJumpToLine)
|
|
case "Top": return #selector(cmdTop)
|
|
case "Bottom": return #selector(cmdBottom)
|
|
case "Build": return #selector(cmdBuild)
|
|
case "Run": return #selector(cmdRun)
|
|
case "Navigator": return #selector(cmdNavigator)
|
|
case "Console": return #selector(cmdConsole)
|
|
case "Font+": return #selector(cmdFontBigger)
|
|
default: return #selector(cmdFontSmaller)
|
|
}
|
|
}
|
|
|
|
@objc func cmdSave() { onSave?() }
|
|
@objc func cmdUndo() { textView?.undoManager?.undo() }
|
|
@objc func cmdRedo() { textView?.undoManager?.redo() }
|
|
@objc func cmdIndent() { textView?.shiftRight() }
|
|
@objc func cmdUnindent() { textView?.shiftLeft() }
|
|
@objc func cmdFind() { onFind?() }
|
|
@objc func cmdFindProject(){ onFindInProject?() }
|
|
@objc func cmdFindNext() {}
|
|
@objc func cmdFindPrev() {}
|
|
@objc func cmdJumpToLine() { onJumpToLine?() }
|
|
@objc func cmdBuild() { onBuild?() }
|
|
@objc func cmdRun() { onRun?() }
|
|
@objc func cmdNavigator() { onToggleNavigator?() }
|
|
@objc func cmdConsole() { onToggleConsole?() }
|
|
@objc func cmdFontBigger() {}
|
|
@objc func cmdFontSmaller(){}
|
|
|
|
@objc func cmdComment() {
|
|
guard let tv = textView, let sel = tv.selectedRange else { return }
|
|
let text = tv.text as NSString; let lr = text.lineRange(for: sel)
|
|
let line = text.substring(with: lr); let trimmed = line.trimmingCharacters(in: .whitespaces)
|
|
let indent = String(line.prefix(while: { $0 == " " || $0 == "\t" }))
|
|
let new: String
|
|
if trimmed.hasPrefix("// ") { new = line.replacingFirstOccurrence(of: "// ", with: "") }
|
|
else if trimmed.hasPrefix("//") { new = line.replacingFirstOccurrence(of: "//", with: "") }
|
|
else { new = indent + "// " + line.dropFirst(indent.count) }
|
|
tv.replace(lr, withText: new)
|
|
}
|
|
|
|
@objc func cmdMoveUp() {
|
|
guard let tv = textView, let sel = tv.selectedRange else { return }
|
|
let text = tv.text as NSString; let cur = text.lineRange(for: sel)
|
|
guard cur.location > 0 else { return }
|
|
let prev = text.lineRange(for: NSRange(location: cur.location - 1, length: 0))
|
|
let curLine = text.substring(with: cur); let prevLine = text.substring(with: prev)
|
|
tv.replace(NSRange(location: prev.location, length: prev.length + cur.length), withText: curLine + prevLine)
|
|
tv.selectedRange = NSRange(location: prev.location + curLine.count - 1, length: 0)
|
|
}
|
|
|
|
@objc func cmdMoveDown() {
|
|
guard let tv = textView, let sel = tv.selectedRange else { return }
|
|
let text = tv.text as NSString; let cur = text.lineRange(for: sel)
|
|
let end = cur.location + cur.length; guard end < text.length else { return }
|
|
let next = text.lineRange(for: NSRange(location: end, length: 0))
|
|
let curLine = text.substring(with: cur); let nextLine = text.substring(with: next)
|
|
tv.replace(NSRange(location: cur.location, length: cur.length + next.length), withText: nextLine + curLine)
|
|
tv.selectedRange = NSRange(location: cur.location + next.length, length: 0)
|
|
}
|
|
|
|
@objc func cmdDuplicate() {
|
|
guard let tv = textView, let sel = tv.selectedRange else { return }
|
|
let text = tv.text as NSString; let lr = text.lineRange(for: sel)
|
|
let line = text.substring(with: lr)
|
|
tv.replace(NSRange(location: lr.location + lr.length, length: 0), withText: line)
|
|
}
|
|
|
|
@objc func cmdDeleteLine() {
|
|
guard let tv = textView, let sel = tv.selectedRange else { return }
|
|
let text = tv.text as NSString
|
|
tv.replace(text.lineRange(for: sel), withText: "")
|
|
}
|
|
|
|
@objc func cmdTop() {
|
|
textView?.selectedRange = NSRange(location: 0, length: 0)
|
|
textView?.scrollRangeToVisible(NSRange(location: 0, length: 0))
|
|
}
|
|
|
|
@objc func cmdBottom() {
|
|
guard let tv = textView else { return }
|
|
let end = NSRange(location: (tv.text as NSString).length, length: 0)
|
|
tv.selectedRange = end; tv.scrollRangeToVisible(end)
|
|
}
|
|
}
|