-
-
Save chriseidhof/638ac994a2843a66f09d14e0b244c29a to your computer and use it in GitHub Desktop.
Preview SwiftUI layouts using Emacs org blocks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(use-package org | |
:hook ((org-mode . org-display-inline-images)) | |
:config | |
(use-package ob | |
:bind (:map org-mode-map | |
("C-c C-c" . org-ctrl-c-ctrl-c)) | |
:config | |
(use-package ob-swift | |
:ensure t | |
:config | |
(org-babel-do-load-languages 'org-babel-load-languages | |
(append org-babel-load-languages | |
'((swift . t)))) | |
(defun ar/org-refresh-inline-images () | |
(when org-inline-image-overlays | |
(org-redisplay-inline-images))) | |
;; Automatically refresh inline images. | |
(add-hook 'org-babel-after-execute-hook 'ar/org-refresh-inline-images) | |
(defun adviced:org-babel-execute:swift (f &rest args) | |
"Advice `adviced:org-babel-execute:swift' enabling swiftui header param." | |
(let* ((body (nth 0 args)) | |
(params (nth 1 args)) | |
(swiftui (cdr (assoc :swiftui params))) | |
(output)) | |
(when swiftui | |
(assert (or (string-equal swiftui "preview") | |
(string-equal swiftui "interactive")) | |
nil ":swiftui must be either preview or interactive") | |
(setq body (format | |
" | |
import Cocoa | |
import SwiftUI | |
import Foundation | |
let screenshotURL: URL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString + \".png\") | |
let preview = %s | |
NSApplication.shared.run { | |
%s | |
} | |
extension NSApplication { | |
public func run<V: View>(@ViewBuilder view: () -> V) { | |
let appDelegate = AppDelegate(view()) | |
NSApp.setActivationPolicy(.regular) | |
mainMenu = customMenu | |
delegate = appDelegate | |
run() | |
} | |
} | |
extension NSApplication { | |
var customMenu: NSMenu { | |
let appMenu = NSMenuItem() | |
appMenu.submenu = NSMenu() | |
let quitItem = NSMenuItem( | |
title: \"Quit \(ProcessInfo.processInfo.processName)\", | |
action: #selector(NSApplication.terminate(_:)), keyEquivalent: \"q\") | |
quitItem.keyEquivalentModifierMask = [] | |
appMenu.submenu?.addItem(quitItem) | |
let mainMenu = NSMenu(title: \"Main Menu\") | |
mainMenu.addItem(appMenu) | |
return mainMenu | |
} | |
} | |
class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate { | |
var window = NSWindow( | |
contentRect: NSRect(x: 0, y: 0, width: 414 * 0.2, height: 896 * 0.2), | |
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], | |
backing: .buffered, defer: false) | |
var contentView: V | |
init(_ contentView: V) { | |
self.contentView = contentView | |
} | |
func applicationDidFinishLaunching(_ notification: Notification) { | |
window.delegate = self | |
window.center() | |
window.contentView = NSHostingView(rootView: contentView) | |
window.makeKeyAndOrderFront(nil) | |
if preview { | |
screenshot(view: window.contentView!, saveTo: screenshotURL) | |
// Write path (without newline) so org babel can parse it | |
// and also drop first chars (file://). | |
print(screenshotURL.path, terminator: \"\") | |
NSApplication.shared.terminate(self) | |
return | |
} | |
window.setFrameAutosaveName(\"Main Window\") | |
NSApp.activate(ignoringOtherApps: true) | |
} | |
} | |
func screenshot(view: NSView, saveTo fileURL: URL) { | |
let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds)! | |
view.cacheDisplay(in: view.bounds, to: rep) | |
let pngData = rep.representation(using: .png, properties: [:]) | |
try! pngData?.write(to: fileURL) | |
} | |
" | |
(if (string-equal swiftui "preview") | |
"true" | |
"false") | |
body)) | |
(setq args (list body params))) | |
(setq output (apply f args)) | |
(when org-inline-image-overlays | |
(org-redisplay-inline-images)) | |
output)) | |
(advice-add #'org-babel-execute:swift | |
:around | |
#'adviced:org-babel-execute:swift)))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment