krz/orgstar

A native macOS editor for org-mode files. editor org-mode swift

Sources/Orgstar/FileTypes.swift

15f6b0709d88971fb62ed432c3e5b8032643670a
orgstar/Sources/Orgstar/FileTypes.swift history · blame · raw

53 lines · 2256 bytes

 1import Foundation
 2import UniformTypeIdentifiers
 3
 4/// How a file that isn't org opens: as text in the editor, or as a preview.
 5enum FileTypes {
 6    /// Whether the file is text: a text type, or, for unknown types, UTF-8 without NUL
 7    /// bytes in its first 8 KB.
 8    static func isText(_ path: String) -> Bool {
 9        let url = URL(fileURLWithPath: path)
10        if let type = UTType(filenameExtension: url.pathExtension), !type.isDynamic {
11            if type.conforms(to: .text) { return true }
12            if type.conforms(to: .image) || type.conforms(to: .audiovisualContent) || type.conforms(to: .pdf) || type.conforms(to: .archive) {
13                return false
14            }
15        }
16        guard let handle = try? FileHandle(forReadingFrom: url) else { return false }
17        defer { try? handle.close() }
18        let head = (try? handle.read(upToCount: 8192)) ?? Data()
19        if head.contains(0) { return false }
20        // A cut multibyte character at the end is still text.
21        for trim in 0...3 where head.count >= trim {
22            if String(data: head.dropLast(trim), encoding: .utf8) != nil { return true }
23        }
24        return false
25    }
26
27    /// The src block language name the highlighter knows for a file's extension.
28    static func language(_ path: String) -> String? {
29        let url = URL(fileURLWithPath: path)
30        switch url.pathExtension.lowercased() {
31        case "sh", "bash", "zsh": return "sh"
32        case "py": return "python"
33        case "el": return "emacs-lisp"
34        case "c", "h": return "c"
35        case "cc", "cpp", "cxx", "hpp", "hh": return "cpp"
36        case "r": return "r"
37        case "js", "mjs", "cjs": return "js"
38        case "java": return "java"
39        case "scm", "ss": return "scheme"
40        case "clj", "cljs", "cljc", "edn": return "clojure"
41        case "hs": return "haskell"
42        case "rs": return "rust"
43        case "go": return "go"
44        case "rb": return "ruby"
45        case "json": return "json"
46        case "yaml", "yml": return "yaml"
47        case "toml": return "toml"
48        case "lua": return "lua"
49        default:
50            return ["zshrc", ".zshrc", "bashrc", ".bashrc", ".profile"].contains(url.lastPathComponent) ? "sh" : nil
51        }
52    }
53}