
After go1.16, go will use module mode by default, even when the repository is checked out under GOPATH or in a one-off directory. Add go.mod, go.sum to keep this repo buildable without opting out of the module mode. > go mod init github.com/mmcgrana/gobyexample > go mod tidy > go mod vendor In module mode, the 'vendor' directory is special and its contents will be actively maintained by the go command. pygments aren't the dependency the go will know about, so it will delete the contents from vendor directory. Move it to `third_party` directory now. And, vendor the blackfriday package. Note: the tutorial contents are not affected by the change in go1.16 because all the examples in this tutorial ask users to run the go command with the explicit list of files to be compiled (e.g. `go run hello-world.go` or `go build command-line-arguments.go`). When the source list is provided, the go command does not have to compute the build list and whether it's running in GOPATH mode or module mode becomes irrelevant.
1011 lines
34 KiB
Nim
1011 lines
34 KiB
Nim
import glib2, gtk2, gdk2, gtksourceview, dialogs, os, pango, osproc, strutils
|
|
import pegs, streams
|
|
import settings, types, cfg, search
|
|
|
|
{.push callConv:cdecl.}
|
|
|
|
const
|
|
NimrodProjectExt = ".nimprj"
|
|
|
|
var win: types.MainWin
|
|
win.Tabs = @[]
|
|
|
|
search.win = addr(win)
|
|
|
|
var lastSession: seq[string] = @[]
|
|
|
|
var confParseFail = False # This gets set to true
|
|
# When there is an error parsing the config
|
|
|
|
# Load the settings
|
|
try:
|
|
win.settings = cfg.load(lastSession)
|
|
except ECFGParse:
|
|
# TODO: Make the dialog show the exception
|
|
confParseFail = True
|
|
win.settings = cfg.defaultSettings()
|
|
except EIO:
|
|
win.settings = cfg.defaultSettings()
|
|
|
|
proc getProjectTab(): int =
|
|
for i in 0..high(win.tabs):
|
|
if win.tabs[i].filename.endswith(NimrodProjectExt): return i
|
|
|
|
proc saveTab(tabNr: int, startpath: string) =
|
|
if tabNr < 0: return
|
|
if win.Tabs[tabNr].saved: return
|
|
var path = ""
|
|
if win.Tabs[tabNr].filename == "":
|
|
path = ChooseFileToSave(win.w, startpath)
|
|
# dialogs.nim STOCK_OPEN instead of STOCK_SAVE
|
|
else:
|
|
path = win.Tabs[tabNr].filename
|
|
|
|
if path != "":
|
|
var buffer = PTextBuffer(win.Tabs[tabNr].buffer)
|
|
# Get the text from the TextView
|
|
var startIter: TTextIter
|
|
buffer.getStartIter(addr(startIter))
|
|
|
|
var endIter: TTextIter
|
|
buffer.getEndIter(addr(endIter))
|
|
|
|
var text = buffer.getText(addr(startIter), addr(endIter), False)
|
|
# Save it to a file
|
|
var f: TFile
|
|
if open(f, path, fmWrite):
|
|
f.write(text)
|
|
f.close()
|
|
|
|
win.tempStuff.lastSaveDir = splitFile(path).dir
|
|
|
|
# Change the tab name and .Tabs.filename etc.
|
|
win.Tabs[tabNr].filename = path
|
|
win.Tabs[tabNr].saved = True
|
|
var name = extractFilename(path)
|
|
|
|
var cTab = win.Tabs[tabNr]
|
|
cTab.label.setText(name)
|
|
else:
|
|
error(win.w, "Unable to write to file")
|
|
|
|
proc saveAllTabs() =
|
|
for i in 0..high(win.tabs):
|
|
saveTab(i, os.splitFile(win.tabs[i].filename).dir)
|
|
|
|
# GTK Events
|
|
# -- w(PWindow)
|
|
proc destroy(widget: PWidget, data: pgpointer) {.cdecl.} =
|
|
# gather some settings
|
|
win.settings.VPanedPos = PPaned(win.sourceViewTabs.getParent()).getPosition()
|
|
win.settings.winWidth = win.w.allocation.width
|
|
win.settings.winHeight = win.w.allocation.height
|
|
|
|
# save the settings
|
|
win.save()
|
|
# then quit
|
|
main_quit()
|
|
|
|
proc delete_event(widget: PWidget, event: PEvent, user_data: pgpointer): bool =
|
|
var quit = True
|
|
for i in low(win.Tabs)..len(win.Tabs)-1:
|
|
if not win.Tabs[i].saved:
|
|
var askSave = dialogNewWithButtons("", win.w, 0,
|
|
STOCK_SAVE, RESPONSE_ACCEPT, STOCK_CANCEL,
|
|
RESPONSE_CANCEL,
|
|
"Close without saving", RESPONSE_REJECT, nil)
|
|
askSave.setTransientFor(win.w)
|
|
# TODO: Make this dialog look better
|
|
var label = labelNew(win.Tabs[i].filename &
|
|
" is unsaved, would you like to save it ?")
|
|
PBox(askSave.vbox).pack_start(label, False, False, 0)
|
|
label.show()
|
|
|
|
var resp = askSave.run()
|
|
gtk2.destroy(PWidget(askSave))
|
|
case resp
|
|
of RESPONSE_ACCEPT:
|
|
saveTab(i, os.splitFile(win.tabs[i].filename).dir)
|
|
quit = True
|
|
of RESPONSE_CANCEL:
|
|
quit = False
|
|
break
|
|
of RESPONSE_REJECT:
|
|
quit = True
|
|
else:
|
|
quit = False
|
|
break
|
|
|
|
# If False is returned the window will close
|
|
return not quit
|
|
|
|
proc windowState_Changed(widget: PWidget, event: PEventWindowState,
|
|
user_data: pgpointer) =
|
|
win.settings.winMaximized = (event.newWindowState and
|
|
WINDOW_STATE_MAXIMIZED) != 0
|
|
|
|
# -- SourceView(PSourceView) & SourceBuffer
|
|
proc updateStatusBar(buffer: PTextBuffer){.cdecl.} =
|
|
# Incase this event gets fired before
|
|
# bottomBar is initialized
|
|
if win.bottomBar != nil and not win.tempStuff.stopSBUpdates:
|
|
var iter: TTextIter
|
|
|
|
win.bottomBar.pop(0)
|
|
buffer.getIterAtMark(addr(iter), buffer.getInsert())
|
|
var row = getLine(addr(iter)) + 1
|
|
var col = getLineOffset(addr(iter))
|
|
discard win.bottomBar.push(0, "Line: " & $row & " Column: " & $col)
|
|
|
|
proc cursorMoved(buffer: PTextBuffer, location: PTextIter,
|
|
mark: PTextMark, user_data: pgpointer){.cdecl.} =
|
|
updateStatusBar(buffer)
|
|
|
|
proc onCloseTab(btn: PButton, user_data: PWidget) =
|
|
if win.sourceViewTabs.getNPages() > 1:
|
|
var tab = win.sourceViewTabs.pageNum(user_data)
|
|
win.sourceViewTabs.removePage(tab)
|
|
|
|
win.Tabs.delete(tab)
|
|
|
|
proc onSwitchTab(notebook: PNotebook, page: PNotebookPage, pageNum: guint,
|
|
user_data: pgpointer) =
|
|
if win.Tabs.len()-1 >= pageNum:
|
|
win.w.setTitle("Aporia IDE - " & win.Tabs[pageNum].filename)
|
|
|
|
proc createTabLabel(name: string, t_child: PWidget): tuple[box: PWidget,
|
|
label: PLabel] =
|
|
var box = hboxNew(False, 0)
|
|
var label = labelNew(name)
|
|
var closebtn = buttonNew()
|
|
closeBtn.setLabel(nil)
|
|
var iconSize = iconSizeFromName("tabIconSize")
|
|
if iconSize == 0:
|
|
iconSize = iconSizeRegister("tabIconSize", 10, 10)
|
|
var image = imageNewFromStock(STOCK_CLOSE, iconSize)
|
|
discard gSignalConnect(closebtn, "clicked", G_Callback(onCloseTab), t_child)
|
|
closebtn.setImage(image)
|
|
gtk2.setRelief(closebtn, RELIEF_NONE)
|
|
box.packStart(label, True, True, 0)
|
|
box.packEnd(closebtn, False, False, 0)
|
|
box.showAll()
|
|
return (box, label)
|
|
|
|
proc changed(buffer: PTextBuffer, user_data: pgpointer) =
|
|
# Update the 'Line & Column'
|
|
#updateStatusBar(buffer)
|
|
|
|
# Change the tabs state to 'unsaved'
|
|
# and add '*' to the Tab Name
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
var name = ""
|
|
if win.Tabs[current].filename == "":
|
|
win.Tabs[current].saved = False
|
|
name = "Untitled *"
|
|
else:
|
|
win.Tabs[current].saved = False
|
|
name = extractFilename(win.Tabs[current].filename) & " *"
|
|
|
|
var cTab = win.Tabs[current]
|
|
cTab.label.setText(name)
|
|
|
|
# Other(Helper) functions
|
|
|
|
proc initSourceView(SourceView: var PWidget, scrollWindow: var PScrolledWindow,
|
|
buffer: var PSourceBuffer) =
|
|
# This gets called by addTab
|
|
# Each tabs creates a new SourceView
|
|
# SourceScrolledWindow(ScrolledWindow)
|
|
scrollWindow = scrolledWindowNew(nil, nil)
|
|
scrollWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
|
scrollWindow.show()
|
|
|
|
# SourceView(gtkSourceView)
|
|
SourceView = sourceViewNew(buffer)
|
|
PSourceView(SourceView).setInsertSpacesInsteadOfTabs(True)
|
|
PSourceView(SourceView).setIndentWidth(win.settings.indentWidth)
|
|
PSourceView(SourceView).setShowLineNumbers(win.settings.showLineNumbers)
|
|
PSourceView(SourceView).setHighlightCurrentLine(
|
|
win.settings.highlightCurrentLine)
|
|
PSourceView(SourceView).setShowRightMargin(win.settings.rightMargin)
|
|
PSourceView(SourceView).setAutoIndent(win.settings.autoIndent)
|
|
|
|
var font = font_description_from_string(win.settings.font)
|
|
SourceView.modifyFont(font)
|
|
|
|
scrollWindow.add(SourceView)
|
|
SourceView.show()
|
|
|
|
buffer.setHighlightMatchingBrackets(
|
|
win.settings.highlightMatchingBrackets)
|
|
|
|
# UGLY workaround for yet another compiler bug:
|
|
discard gsignalConnect(buffer, "mark-set",
|
|
GCallback(aporia.cursorMoved), nil)
|
|
discard gsignalConnect(buffer, "changed", GCallback(aporia.changed), nil)
|
|
|
|
# -- Set the syntax highlighter scheme
|
|
buffer.setScheme(win.scheme)
|
|
|
|
proc addTab(name, filename: string) =
|
|
## Adds a tab, if filename is not "" reads the file. And sets
|
|
## the tabs SourceViews text to that files contents.
|
|
assert(win.nimLang != nil)
|
|
var buffer: PSourceBuffer = sourceBufferNew(win.nimLang)
|
|
|
|
if filename != nil and filename != "":
|
|
var lang = win.langMan.guessLanguage(filename, nil)
|
|
if lang != nil:
|
|
buffer.setLanguage(lang)
|
|
else:
|
|
buffer.setHighlightSyntax(False)
|
|
|
|
var nam = name
|
|
if nam == "": nam = "Untitled"
|
|
if filename == "": nam.add(" *")
|
|
elif filename != "" and name == "":
|
|
# Disable the undo/redo manager.
|
|
buffer.begin_not_undoable_action()
|
|
|
|
# Load the file.
|
|
var file: string = readFile(filename)
|
|
if file != nil:
|
|
buffer.set_text(file, len(file))
|
|
|
|
# Enable the undo/redo manager.
|
|
buffer.end_not_undoable_action()
|
|
|
|
# Get the name.ext of the filename, for the tabs title
|
|
nam = extractFilename(filename)
|
|
|
|
# Init the sourceview
|
|
var sourceView: PWidget
|
|
var scrollWindow: PScrolledWindow
|
|
initSourceView(sourceView, scrollWindow, buffer)
|
|
|
|
var (TabLabel, labelText) = createTabLabel(nam, scrollWindow)
|
|
# Add a tab
|
|
discard win.SourceViewTabs.appendPage(scrollWindow, TabLabel)
|
|
|
|
var nTab: Tab
|
|
nTab.buffer = buffer
|
|
nTab.sourceView = sourceView
|
|
nTab.label = labelText
|
|
nTab.saved = (filename != "")
|
|
nTab.filename = filename
|
|
win.Tabs.add(nTab)
|
|
|
|
PTextView(SourceView).setBuffer(nTab.buffer)
|
|
|
|
# GTK Events Contd.
|
|
# -- TopMenu & TopBar
|
|
|
|
proc newFile(menuItem: PMenuItem, user_data: pgpointer) =
|
|
addTab("", "")
|
|
win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
|
|
|
|
proc openFile(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var startpath = ""
|
|
var currPage = win.SourceViewTabs.getCurrentPage()
|
|
if currPage <% win.tabs.len:
|
|
startpath = os.splitFile(win.tabs[currPage].filename).dir
|
|
|
|
if startpath.len == 0:
|
|
# Use lastSavePath as the startpath
|
|
startpath = win.tempStuff.lastSaveDir
|
|
if startpath.len == 0:
|
|
startpath = os.getHomeDir()
|
|
|
|
var files = ChooseFilesToOpen(win.w, startpath)
|
|
if files.len() > 0:
|
|
for f in items(files):
|
|
try:
|
|
addTab("", f)
|
|
except EIO:
|
|
error(win.w, "Unable to read from file")
|
|
# Switch to the newly created tab
|
|
win.sourceViewTabs.setCurrentPage(win.Tabs.len()-1)
|
|
|
|
proc saveFile_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
saveTab(current, os.splitFile(win.tabs[current].filename).dir)
|
|
|
|
proc saveFileAs_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
var (filename, saved) = (win.Tabs[current].filename, win.Tabs[current].saved)
|
|
|
|
win.Tabs[current].saved = False
|
|
win.Tabs[current].filename = ""
|
|
saveTab(current, os.splitFile(filename).dir)
|
|
# If the user cancels the save file dialog. Restore the previous filename
|
|
# and saved state
|
|
if win.Tabs[current].filename == "":
|
|
win.Tabs[current].filename = filename
|
|
win.Tabs[current].saved = saved
|
|
|
|
proc undo(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[current].buffer.canUndo():
|
|
win.Tabs[current].buffer.undo()
|
|
|
|
proc redo(menuItem: PMenuItem, user_data: pgpointer) =
|
|
var current = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[current].buffer.canRedo():
|
|
win.Tabs[current].buffer.redo()
|
|
|
|
proc find_Activate(menuItem: PMenuItem, user_data: pgpointer) =
|
|
# Get the selected text, and set the findEntry to it.
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
var insertIter: TTextIter
|
|
win.Tabs[currentTab].buffer.getIterAtMark(addr(insertIter),
|
|
win.Tabs[currentTab].buffer.getInsert())
|
|
var insertOffset = addr(insertIter).getOffset()
|
|
|
|
var selectIter: TTextIter
|
|
win.Tabs[currentTab].buffer.getIterAtMark(addr(selectIter),
|
|
win.Tabs[currentTab].buffer.getSelectionBound())
|
|
var selectOffset = addr(selectIter).getOffset()
|
|
|
|
if insertOffset != selectOffset:
|
|
var text = win.Tabs[currentTab].buffer.getText(addr(insertIter),
|
|
addr(selectIter), false)
|
|
win.findEntry.setText(text)
|
|
|
|
win.findBar.show()
|
|
win.findEntry.grabFocus()
|
|
win.replaceEntry.hide()
|
|
win.replaceLabel.hide()
|
|
win.replaceBtn.hide()
|
|
win.replaceAllBtn.hide()
|
|
|
|
proc replace_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
win.findBar.show()
|
|
win.findEntry.grabFocus()
|
|
win.replaceEntry.show()
|
|
win.replaceLabel.show()
|
|
win.replaceBtn.show()
|
|
win.replaceAllBtn.show()
|
|
|
|
proc settings_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
settings.showSettings(win)
|
|
|
|
proc viewBottomPanel_Toggled(menuitem: PCheckMenuItem, user_data: pgpointer) =
|
|
win.settings.bottomPanelVisible = menuitem.itemGetActive()
|
|
if win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
else:
|
|
win.bottomPanelTabs.hide()
|
|
|
|
var
|
|
pegLineError = peg"{[^(]*} '(' {\d+} ', ' \d+ ') Error:' \s* {.*}"
|
|
pegLineWarning = peg"{[^(]*} '(' {\d+} ', ' \d+ ') ' ('Warning:'/'Hint:') \s* {.*}"
|
|
pegOtherError = peg"'Error:' \s* {.*}"
|
|
pegSuccess = peg"'Hint: operation successful'.*"
|
|
|
|
proc addText(textView: PTextView, text: string, colorTag: PTextTag = nil) =
|
|
if text != nil:
|
|
var iter: TTextIter
|
|
textView.getBuffer().getEndIter(addr(iter))
|
|
|
|
if colorTag == nil:
|
|
textView.getBuffer().insert(addr(iter), text, len(text))
|
|
else:
|
|
textView.getBuffer().insertWithTags(addr(iter), text, len(text), colorTag,
|
|
nil)
|
|
|
|
proc createColor(textView: PTextView, name, color: string): PTextTag =
|
|
var tagTable = textView.getBuffer().getTagTable()
|
|
result = tagTable.tableLookup(name)
|
|
if result == nil:
|
|
result = textView.getBuffer().createTag(name, "foreground", color, nil)
|
|
|
|
when not defined(os.findExe):
|
|
proc findExe(exe: string): string =
|
|
## returns "" if the exe cannot be found
|
|
result = addFileExt(exe, os.exeExt)
|
|
if ExistsFile(result): return
|
|
var path = os.getEnv("PATH")
|
|
for candidate in split(path, pathSep):
|
|
var x = candidate / result
|
|
if ExistsFile(x): return x
|
|
result = ""
|
|
|
|
proc GetCmd(cmd, filename: string): string =
|
|
var f = quoteIfContainsWhite(filename)
|
|
if cmd =~ peg"\s* '$' y'findExe' '(' {[^)]+} ')' {.*}":
|
|
var exe = quoteIfContainsWhite(findExe(matches[0]))
|
|
if exe.len == 0: exe = matches[0]
|
|
result = exe & " " & matches[1] % f
|
|
else:
|
|
result = cmd % f
|
|
|
|
proc showBottomPanel() =
|
|
if not win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
win.settings.bottomPanelVisible = true
|
|
PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(true)
|
|
# Scroll to the end of the TextView
|
|
# This is stupid, it works sometimes... it's random
|
|
var endIter: TTextIter
|
|
win.outputTextView.getBuffer().getEndIter(addr(endIter))
|
|
discard win.outputTextView.scrollToIter(
|
|
addr(endIter), 0.25, False, 0.0, 0.0)
|
|
|
|
proc compileRun(currentTab: int, shouldRun: bool) =
|
|
if win.Tabs[currentTab].filename.len == 0: return
|
|
# Clear the outputTextView
|
|
win.outputTextView.getBuffer().setText("", 0)
|
|
|
|
var outp = osProc.execProcess(GetCmd(win.settings.nimrodCmd,
|
|
win.Tabs[currentTab].filename))
|
|
# Colors
|
|
var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
|
|
var errorTag = createColor(win.outputTextView, "errorTag", "red")
|
|
var warningTag = createColor(win.outputTextView, "warningTag", "darkorange")
|
|
var successTag = createColor(win.outputTextView, "successTag", "darkgreen")
|
|
for x in outp.splitLines():
|
|
if x =~ pegLineError / pegOtherError:
|
|
win.outputTextView.addText("\n" & x, errorTag)
|
|
elif x=~ pegSuccess:
|
|
win.outputTextView.addText("\n" & x, successTag)
|
|
|
|
# Launch the process
|
|
if shouldRun:
|
|
var filename = changeFileExt(win.Tabs[currentTab].filename, os.ExeExt)
|
|
var output = "\n" & osProc.execProcess(filename)
|
|
win.outputTextView.addText(output)
|
|
elif x =~ pegLineWarning:
|
|
win.outputTextView.addText("\n" & x, warningTag)
|
|
else:
|
|
win.outputTextView.addText("\n" & x, normalTag)
|
|
showBottomPanel()
|
|
|
|
proc CompileCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveFile_Activate(nil, nil)
|
|
compileRun(win.SourceViewTabs.getCurrentPage(), false)
|
|
|
|
proc CompileRunCurrent_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveFile_Activate(nil, nil)
|
|
compileRun(win.SourceViewTabs.getCurrentPage(), true)
|
|
|
|
proc CompileProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveAllTabs()
|
|
compileRun(getProjectTab(), false)
|
|
|
|
proc CompileRunProject_Activate(menuitem: PMenuItem, user_data: pgpointer) =
|
|
saveAllTabs()
|
|
compileRun(getProjectTab(), true)
|
|
|
|
proc RunCustomCommand(cmd: string) =
|
|
saveFile_Activate(nil, nil)
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
if win.Tabs[currentTab].filename.len == 0 or cmd.len == 0: return
|
|
# Clear the outputTextView
|
|
win.outputTextView.getBuffer().setText("", 0)
|
|
var outp = osProc.execProcess(GetCmd(cmd, win.Tabs[currentTab].filename))
|
|
var normalTag = createColor(win.outputTextView, "normalTag", "#3d3d3d")
|
|
for x in outp.splitLines():
|
|
win.outputTextView.addText("\n" & x, normalTag)
|
|
showBottomPanel()
|
|
|
|
proc RunCustomCommand1(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd1)
|
|
|
|
proc RunCustomCommand2(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd2)
|
|
|
|
proc RunCustomCommand3(menuitem: PMenuItem, user_data: pgpointer) =
|
|
RunCustomCommand(win.settings.customCmd3)
|
|
|
|
# -- FindBar
|
|
|
|
proc nextBtn_Clicked(button: PButton, user_data: pgpointer) = findText(True)
|
|
proc prevBtn_Clicked(button: PButton, user_data: pgpointer) = findText(False)
|
|
|
|
proc replaceBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var currentTab = win.SourceViewTabs.getCurrentPage()
|
|
var start, theEnd: TTextIter
|
|
if not win.Tabs[currentTab].buffer.getSelectionBounds(
|
|
addr(start), addr(theEnd)):
|
|
# If no text is selected, try finding a match.
|
|
findText(True)
|
|
if not win.Tabs[currentTab].buffer.getSelectionBounds(
|
|
addr(start), addr(theEnd)):
|
|
# No match
|
|
return
|
|
|
|
# Remove the text
|
|
win.Tabs[currentTab].buffer.delete(addr(start), addr(theEnd))
|
|
# Insert the replacement
|
|
var text = getText(win.replaceEntry)
|
|
win.Tabs[currentTab].buffer.insert(addr(start), text, len(text))
|
|
|
|
proc replaceAllBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var find = getText(win.findEntry)
|
|
var replace = getText(win.replaceEntry)
|
|
discard replaceAll(find, replace)
|
|
|
|
proc closeBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
win.findBar.hide()
|
|
|
|
proc caseSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "casesens"
|
|
proc caseInSens_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "caseinsens"
|
|
proc style_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "style"
|
|
proc regex_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "regex"
|
|
proc peg_Changed(radiomenuitem: PRadioMenuitem, user_data: pgpointer) =
|
|
win.settings.search = "peg"
|
|
|
|
proc extraBtn_Clicked(button: PButton, user_data: pgpointer) =
|
|
var extraMenu = menuNew()
|
|
var group: PGSList
|
|
|
|
var caseSensMenuItem = radio_menu_item_new(group, "Case sensitive")
|
|
extraMenu.append(caseSensMenuItem)
|
|
discard signal_connect(caseSensMenuItem, "toggled",
|
|
SIGNAL_FUNC(caseSens_Changed), nil)
|
|
caseSensMenuItem.show()
|
|
group = caseSensMenuItem.ItemGetGroup()
|
|
|
|
var caseInSensMenuItem = radio_menu_item_new(group, "Case insensitive")
|
|
extraMenu.append(caseInSensMenuItem)
|
|
discard signal_connect(caseInSensMenuItem, "toggled",
|
|
SIGNAL_FUNC(caseInSens_Changed), nil)
|
|
caseInSensMenuItem.show()
|
|
group = caseInSensMenuItem.ItemGetGroup()
|
|
|
|
var styleMenuItem = radio_menu_item_new(group, "Style insensitive")
|
|
extraMenu.append(styleMenuItem)
|
|
discard signal_connect(styleMenuItem, "toggled",
|
|
SIGNAL_FUNC(style_Changed), nil)
|
|
styleMenuItem.show()
|
|
group = styleMenuItem.ItemGetGroup()
|
|
|
|
var regexMenuItem = radio_menu_item_new(group, "Regex")
|
|
extraMenu.append(regexMenuItem)
|
|
discard signal_connect(regexMenuItem, "toggled",
|
|
SIGNAL_FUNC(regex_Changed), nil)
|
|
regexMenuItem.show()
|
|
group = regexMenuItem.ItemGetGroup()
|
|
|
|
var pegMenuItem = radio_menu_item_new(group, "Pegs")
|
|
extraMenu.append(pegMenuItem)
|
|
discard signal_connect(pegMenuItem, "toggled",
|
|
SIGNAL_FUNC(peg_Changed), nil)
|
|
pegMenuItem.show()
|
|
|
|
# Make the correct radio button active
|
|
case win.settings.search
|
|
of "casesens":
|
|
PCheckMenuItem(caseSensMenuItem).ItemSetActive(True)
|
|
of "caseinsens":
|
|
PCheckMenuItem(caseInSensMenuItem).ItemSetActive(True)
|
|
of "style":
|
|
PCheckMenuItem(styleMenuItem).ItemSetActive(True)
|
|
of "regex":
|
|
PCheckMenuItem(regexMenuItem).ItemSetActive(True)
|
|
of "peg":
|
|
PCheckMenuItem(pegMenuItem).ItemSetActive(True)
|
|
|
|
extraMenu.popup(nil, nil, nil, nil, 0, get_current_event_time())
|
|
|
|
# GUI Initialization
|
|
|
|
proc createAccelMenuItem(toolsMenu: PMenu, accGroup: PAccelGroup,
|
|
label: string, acc: gint,
|
|
action: proc (i: PMenuItem, p: pgpointer)) =
|
|
var result = menu_item_new(label)
|
|
result.addAccelerator("activate", accGroup, acc, 0, ACCEL_VISIBLE)
|
|
ToolsMenu.append(result)
|
|
show(result)
|
|
discard signal_connect(result, "activate", SIGNAL_FUNC(action), nil)
|
|
|
|
proc createSeparator(menu: PMenu) =
|
|
var sep = separator_menu_item_new()
|
|
menu.append(sep)
|
|
sep.show()
|
|
|
|
proc initTopMenu(MainBox: PBox) =
|
|
# Create a accelerator group, used for shortcuts
|
|
# like CTRL + S in SaveMenuItem
|
|
var accGroup = accel_group_new()
|
|
add_accel_group(win.w, accGroup)
|
|
|
|
# TopMenu(MenuBar)
|
|
var TopMenu = menuBarNew()
|
|
|
|
# FileMenu
|
|
var FileMenu = menuNew()
|
|
|
|
var NewMenuItem = menu_item_new("New") # New
|
|
FileMenu.append(NewMenuItem)
|
|
show(NewMenuItem)
|
|
discard signal_connect(NewMenuItem, "activate",
|
|
SIGNAL_FUNC(newFile), nil)
|
|
|
|
createSeparator(FileMenu)
|
|
|
|
var OpenMenuItem = menu_item_new("Open...") # Open...
|
|
# CTRL + O
|
|
OpenMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_o, CONTROL_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(OpenMenuItem)
|
|
show(OpenMenuItem)
|
|
discard signal_connect(OpenMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.openFile), nil)
|
|
|
|
var SaveMenuItem = menu_item_new("Save") # Save
|
|
# CTRL + S
|
|
SaveMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_s, CONTROL_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(SaveMenuItem)
|
|
show(SaveMenuItem)
|
|
discard signal_connect(SaveMenuItem, "activate",
|
|
SIGNAL_FUNC(saveFile_activate), nil)
|
|
|
|
var SaveAsMenuItem = menu_item_new("Save As...") # Save as...
|
|
|
|
SaveAsMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_s, CONTROL_MASK or gdk2.SHIFT_MASK, ACCEL_VISIBLE)
|
|
FileMenu.append(SaveAsMenuItem)
|
|
show(SaveAsMenuItem)
|
|
discard signal_connect(SaveAsMenuItem, "activate",
|
|
SIGNAL_FUNC(saveFileAs_Activate), nil)
|
|
|
|
var FileMenuItem = menuItemNewWithMnemonic("_File")
|
|
|
|
FileMenuItem.setSubMenu(FileMenu)
|
|
FileMenuItem.show()
|
|
TopMenu.append(FileMenuItem)
|
|
|
|
# Edit menu
|
|
var EditMenu = menuNew()
|
|
|
|
var UndoMenuItem = menu_item_new("Undo") # Undo
|
|
EditMenu.append(UndoMenuItem)
|
|
show(UndoMenuItem)
|
|
discard signal_connect(UndoMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.undo), nil)
|
|
|
|
var RedoMenuItem = menu_item_new("Redo") # Undo
|
|
EditMenu.append(RedoMenuItem)
|
|
show(RedoMenuItem)
|
|
discard signal_connect(RedoMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.redo), nil)
|
|
|
|
createSeparator(EditMenu)
|
|
|
|
var FindMenuItem = menu_item_new("Find") # Find
|
|
FindMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_f, CONTROL_MASK, ACCEL_VISIBLE)
|
|
EditMenu.append(FindMenuItem)
|
|
show(FindMenuItem)
|
|
discard signal_connect(FindMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.find_Activate), nil)
|
|
|
|
var ReplaceMenuItem = menu_item_new("Replace") # Replace
|
|
ReplaceMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_h, CONTROL_MASK, ACCEL_VISIBLE)
|
|
EditMenu.append(ReplaceMenuItem)
|
|
show(ReplaceMenuItem)
|
|
discard signal_connect(ReplaceMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.replace_Activate), nil)
|
|
|
|
createSeparator(EditMenu)
|
|
|
|
var SettingsMenuItem = menu_item_new("Settings...") # Settings
|
|
EditMenu.append(SettingsMenuItem)
|
|
show(SettingsMenuItem)
|
|
discard signal_connect(SettingsMenuItem, "activate",
|
|
SIGNAL_FUNC(aporia.Settings_Activate), nil)
|
|
|
|
var EditMenuItem = menuItemNewWithMnemonic("_Edit")
|
|
|
|
EditMenuItem.setSubMenu(EditMenu)
|
|
EditMenuItem.show()
|
|
TopMenu.append(EditMenuItem)
|
|
|
|
# View menu
|
|
var ViewMenu = menuNew()
|
|
|
|
win.viewBottomPanelMenuItem = check_menu_item_new("Bottom Panel")
|
|
PCheckMenuItem(win.viewBottomPanelMenuItem).itemSetActive(
|
|
win.settings.bottomPanelVisible)
|
|
win.viewBottomPanelMenuItem.add_accelerator("activate", accGroup,
|
|
KEY_f9, CONTROL_MASK, ACCEL_VISIBLE)
|
|
ViewMenu.append(win.viewBottomPanelMenuItem)
|
|
show(win.viewBottomPanelMenuItem)
|
|
discard signal_connect(win.viewBottomPanelMenuItem, "toggled",
|
|
SIGNAL_FUNC(aporia.viewBottomPanel_Toggled), nil)
|
|
|
|
var ViewMenuItem = menuItemNewWithMnemonic("_View")
|
|
|
|
ViewMenuItem.setSubMenu(ViewMenu)
|
|
ViewMenuItem.show()
|
|
TopMenu.append(ViewMenuItem)
|
|
|
|
|
|
# Tools menu
|
|
var ToolsMenu = menuNew()
|
|
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile current file",
|
|
KEY_F4, aporia.CompileCurrent_Activate)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile & run current file",
|
|
KEY_F5, aporia.CompileRunCurrent_Activate)
|
|
createSeparator(ToolsMenu)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile project",
|
|
KEY_F8, aporia.CompileProject_Activate)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Compile & run project",
|
|
KEY_F9, aporia.CompileRunProject_Activate)
|
|
createSeparator(ToolsMenu)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 1",
|
|
KEY_F1, aporia.RunCustomCommand1)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 2",
|
|
KEY_F2, aporia.RunCustomCommand2)
|
|
createAccelMenuItem(ToolsMenu, accGroup, "Run custom command 3",
|
|
KEY_F3, aporia.RunCustomCommand3)
|
|
|
|
var ToolsMenuItem = menuItemNewWithMnemonic("_Tools")
|
|
|
|
ToolsMenuItem.setSubMenu(ToolsMenu)
|
|
ToolsMenuItem.show()
|
|
TopMenu.append(ToolsMenuItem)
|
|
|
|
# Help menu
|
|
MainBox.packStart(TopMenu, False, False, 0)
|
|
TopMenu.show()
|
|
|
|
proc initToolBar(MainBox: PBox) =
|
|
# TopBar(ToolBar)
|
|
var TopBar = toolbarNew()
|
|
TopBar.setStyle(TOOLBAR_ICONS)
|
|
|
|
var NewFileItem = TopBar.insertStock(STOCK_NEW, "New File",
|
|
"New File", SIGNAL_FUNC(aporia.newFile), nil, 0)
|
|
TopBar.appendSpace()
|
|
var OpenItem = TopBar.insertStock(STOCK_OPEN, "Open",
|
|
"Open", SIGNAL_FUNC(aporia.openFile), nil, -1)
|
|
var SaveItem = TopBar.insertStock(STOCK_SAVE, "Save",
|
|
"Save", SIGNAL_FUNC(saveFile_Activate), nil, -1)
|
|
TopBar.appendSpace()
|
|
var UndoItem = TopBar.insertStock(STOCK_UNDO, "Undo",
|
|
"Undo", SIGNAL_FUNC(aporia.undo), nil, -1)
|
|
var RedoItem = TopBar.insertStock(STOCK_REDO, "Redo",
|
|
"Redo", SIGNAL_FUNC(aporia.redo), nil, -1)
|
|
|
|
MainBox.packStart(TopBar, False, False, 0)
|
|
TopBar.show()
|
|
|
|
proc initSourceViewTabs() =
|
|
win.SourceViewTabs = notebookNew()
|
|
#win.sourceViewTabs.dragDestSet(DEST_DEFAULT_DROP, nil, 0, ACTION_MOVE)
|
|
discard win.SourceViewTabs.signalConnect(
|
|
"switch-page", SIGNAL_FUNC(onSwitchTab), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-drop", SIGNAL_FUNC(svTabs_DragDrop), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-data-received", SIGNAL_FUNC(svTabs_DragDataRecv), nil)
|
|
#discard win.SourceViewTabs.signalConnect(
|
|
# "drag-motion", SIGNAL_FUNC(svTabs_DragMotion), nil)
|
|
win.SourceViewTabs.set_scrollable(True)
|
|
|
|
win.SourceViewTabs.show()
|
|
if lastSession.len != 0:
|
|
for i in 0 .. len(lastSession)-1:
|
|
var splitUp = lastSession[i].split('|')
|
|
var (filename, offset) = (splitUp[0], splitUp[1])
|
|
addTab("", filename)
|
|
|
|
var iter: TTextIter
|
|
win.Tabs[i].buffer.getIterAtOffset(addr(iter), offset.parseInt())
|
|
win.Tabs[i].buffer.moveMarkByName("insert", addr(iter))
|
|
win.Tabs[i].buffer.moveMarkByName("selection_bound", addr(iter))
|
|
|
|
# TODO: Fix this..... :(
|
|
discard PTextView(win.Tabs[i].sourceView).
|
|
scrollToIter(addr(iter), 0.25, true, 0.0, 0.0)
|
|
else:
|
|
addTab("", "")
|
|
|
|
# This doesn't work :\
|
|
win.Tabs[0].sourceView.grabFocus()
|
|
|
|
|
|
proc initBottomTabs() =
|
|
win.bottomPanelTabs = notebookNew()
|
|
if win.settings.bottomPanelVisible:
|
|
win.bottomPanelTabs.show()
|
|
|
|
# output tab
|
|
var tabLabel = labelNew("Output")
|
|
var outputTab = vboxNew(False, 0)
|
|
discard win.bottomPanelTabs.appendPage(outputTab, tabLabel)
|
|
# Compiler tabs, gtktextview
|
|
var outputScrolledWindow = scrolledwindowNew(nil, nil)
|
|
outputScrolledWindow.setPolicy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
|
outputTab.packStart(outputScrolledWindow, true, true, 0)
|
|
outputScrolledWindow.show()
|
|
|
|
win.outputTextView = textviewNew()
|
|
outputScrolledWindow.add(win.outputTextView)
|
|
win.outputTextView.show()
|
|
|
|
outputTab.show()
|
|
|
|
proc initTAndBP(MainBox: PBox) =
|
|
# This init's the HPaned, which splits the sourceViewTabs
|
|
# and the BottomPanelTabs
|
|
initSourceViewTabs()
|
|
initBottomTabs()
|
|
|
|
var TAndBPVPaned = vpanedNew()
|
|
tandbpVPaned.pack1(win.sourceViewTabs, resize=True, shrink=False)
|
|
tandbpVPaned.pack2(win.bottomPanelTabs, resize=False, shrink=False)
|
|
MainBox.packStart(TAndBPVPaned, True, True, 0)
|
|
tandbpVPaned.setPosition(win.settings.VPanedPos)
|
|
TAndBPVPaned.show()
|
|
|
|
proc initFindBar(MainBox: PBox) =
|
|
# Create a fixed container
|
|
win.findBar = HBoxNew(False, 0)
|
|
win.findBar.setSpacing(4)
|
|
|
|
# Add a Label 'Find'
|
|
var findLabel = labelNew("Find:")
|
|
win.findBar.packStart(findLabel, False, False, 0)
|
|
findLabel.show()
|
|
|
|
# Add a (find) text entry
|
|
win.findEntry = entryNew()
|
|
win.findBar.packStart(win.findEntry, False, False, 0)
|
|
discard win.findEntry.signalConnect("activate", SIGNAL_FUNC(
|
|
aporia.nextBtn_Clicked), nil)
|
|
win.findEntry.show()
|
|
var rq: TRequisition
|
|
win.findEntry.sizeRequest(addr(rq))
|
|
|
|
# Make the (find) text entry longer
|
|
win.findEntry.set_size_request(190, rq.height)
|
|
|
|
# Add a Label 'Replace'
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceLabel = labelNew("Replace:")
|
|
win.findBar.packStart(win.replaceLabel, False, False, 0)
|
|
#replaceLabel.show()
|
|
|
|
# Add a (replace) text entry
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceEntry = entryNew()
|
|
win.findBar.packStart(win.replaceEntry, False, False, 0)
|
|
#win.replaceEntry.show()
|
|
var rq1: TRequisition
|
|
win.replaceEntry.sizeRequest(addr(rq1))
|
|
|
|
# Make the (replace) text entry longer
|
|
win.replaceEntry.set_size_request(100, rq1.height)
|
|
|
|
# Find next button
|
|
var nextBtn = buttonNew("Next")
|
|
win.findBar.packStart(nextBtn, false, false, 0)
|
|
discard nextBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.nextBtn_Clicked), nil)
|
|
nextBtn.show()
|
|
var nxtBtnRq: TRequisition
|
|
nextBtn.sizeRequest(addr(nxtBtnRq))
|
|
|
|
# Find previous button
|
|
var prevBtn = buttonNew("Previous")
|
|
win.findBar.packStart(prevBtn, false, false, 0)
|
|
discard prevBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.prevBtn_Clicked), nil)
|
|
prevBtn.show()
|
|
|
|
# Replace button
|
|
# - This Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceBtn = buttonNew("Replace")
|
|
win.findBar.packStart(win.replaceBtn, false, false, 0)
|
|
discard win.replaceBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.replaceBtn_Clicked), nil)
|
|
#replaceBtn.show()
|
|
|
|
# Replace all button
|
|
# - this Is only shown, when the 'Search & Replace'(CTRL + H) is shown
|
|
win.replaceAllBtn = buttonNew("Replace All")
|
|
win.findBar.packStart(win.replaceAllBtn, false, false, 0)
|
|
discard win.replaceAllBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.replaceAllBtn_Clicked), nil)
|
|
#replaceAllBtn.show()
|
|
|
|
# Right side ...
|
|
|
|
# Close button - With a close stock image
|
|
var closeBtn = buttonNew()
|
|
var closeImage = imageNewFromStock(STOCK_CLOSE, ICON_SIZE_SMALL_TOOLBAR)
|
|
var closeBox = hboxNew(False, 0)
|
|
closeBtn.add(closeBox)
|
|
closeBox.show()
|
|
closeBox.add(closeImage)
|
|
closeImage.show()
|
|
discard closeBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.closeBtn_Clicked), nil)
|
|
win.findBar.packEnd(closeBtn, False, False, 2)
|
|
closeBtn.show()
|
|
|
|
# Extra button - When clicked shows a menu with options like 'Use regex'
|
|
var extraBtn = buttonNew()
|
|
var extraImage = imageNewFromStock(STOCK_PROPERTIES, ICON_SIZE_SMALL_TOOLBAR)
|
|
|
|
var extraBox = hboxNew(False, 0)
|
|
extraBtn.add(extraBox)
|
|
extraBox.show()
|
|
extraBox.add(extraImage)
|
|
extraImage.show()
|
|
discard extraBtn.signalConnect("clicked",
|
|
SIGNAL_FUNC(aporia.extraBtn_Clicked), nil)
|
|
win.findBar.packEnd(extraBtn, False, False, 0)
|
|
extraBtn.show()
|
|
|
|
MainBox.packStart(win.findBar, False, False, 0)
|
|
win.findBar.show()
|
|
|
|
proc initStatusBar(MainBox: PBox) =
|
|
win.bottomBar = statusbarNew()
|
|
MainBox.packStart(win.bottomBar, False, False, 0)
|
|
win.bottomBar.show()
|
|
|
|
discard win.bottomBar.push(0, "Line: 0 Column: 0")
|
|
|
|
proc initControls() =
|
|
# Load up the language style
|
|
win.langMan = languageManagerGetDefault()
|
|
var langpaths: array[0..1, cstring] =
|
|
[cstring(os.getApplicationDir() / langSpecs), nil]
|
|
win.langMan.setSearchPath(addr(langpaths))
|
|
var nimLang = win.langMan.getLanguage("nimrod")
|
|
win.nimLang = nimLang
|
|
|
|
# Load the scheme
|
|
var schemeMan = schemeManagerGetDefault()
|
|
var schemepaths: array[0..1, cstring] =
|
|
[cstring(os.getApplicationDir() / styles), nil]
|
|
schemeMan.setSearchPath(addr(schemepaths))
|
|
win.scheme = schemeMan.getScheme(win.settings.colorSchemeID)
|
|
|
|
# Window
|
|
win.w = windowNew(gtk2.WINDOW_TOPLEVEL)
|
|
win.w.setDefaultSize(win.settings.winWidth, win.settings.winHeight)
|
|
win.w.setTitle("Aporia IDE")
|
|
if win.settings.winMaximized: win.w.maximize()
|
|
|
|
win.w.show() # The window has to be shown before
|
|
# setting the position of the VPaned so that
|
|
# it gets set correctly, when the window is maximized.
|
|
|
|
discard win.w.signalConnect("destroy", SIGNAL_FUNC(aporia.destroy), nil)
|
|
discard win.w.signalConnect("delete_event",
|
|
SIGNAL_FUNC(aporia.delete_event), nil)
|
|
discard win.w.signalConnect("window-state-event",
|
|
SIGNAL_FUNC(aporia.windowState_Changed), nil)
|
|
|
|
# MainBox (vbox)
|
|
var MainBox = vboxNew(False, 0)
|
|
win.w.add(MainBox)
|
|
|
|
initTopMenu(MainBox)
|
|
initToolBar(MainBox)
|
|
initTAndBP(MainBox)
|
|
initFindBar(MainBox)
|
|
initStatusBar(MainBox)
|
|
|
|
MainBox.show()
|
|
if confParseFail:
|
|
dialogs.warning(win.w, "Error parsing config file, using default settings.")
|
|
|
|
nimrod_init()
|
|
initControls()
|
|
main()
|
|
|