Sources/Orgstar/FileTypes.swift
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}