@@ -1099,6 +1099,19 @@ class GhosttyApp {
10991099 effectiveFontFamilies. count > 1
11001100 }
11011101
1102+ mutating func applyFontCodepointMap( _ value: String ) {
1103+ if value. isEmpty {
1104+ containsCodepointMap = false
1105+ return
1106+ }
1107+
1108+ guard value. contains ( " = " ) else {
1109+ return
1110+ }
1111+
1112+ containsCodepointMap = true
1113+ }
1114+
11021115 mutating func recordFontFamily( _ value: String ) {
11031116 if value. isEmpty {
11041117 effectiveFontFamilies. removeAll ( )
@@ -1185,18 +1198,30 @@ class GhosttyApp {
11851198 configPaths: [ String ] = loadedCJKScanPaths ( )
11861199 ) -> UserFontConfigSummary {
11871200 var summary = UserFontConfigSummary ( )
1188- var visited = Set < String > ( )
1189- var pendingPaths = configPaths. map { NSString ( string: $0) . expandingTildeInPath }
1190- var index = 0
1201+ var recursiveConfigPaths : [ String ] = [ ]
11911202
1192- while index < pendingPaths . count {
1193- let includePaths = scanFontConfigFile (
1194- atPath: pendingPaths [ index ] ,
1195- visited : & visited ,
1196- summary : & summary
1203+ for path in configPaths . map ( { NSString ( string : $0 ) . expandingTildeInPath } ) {
1204+ scanFontConfigFile (
1205+ atPath: path ,
1206+ summary : & summary ,
1207+ recursiveConfigPaths : & recursiveConfigPaths
11971208 )
1198- pendingPaths. append ( contentsOf: includePaths)
1209+ }
1210+
1211+ var loadedRecursivePaths = Set < String > ( )
1212+ var index = 0
1213+ while index < recursiveConfigPaths. count {
1214+ let path = recursiveConfigPaths [ index]
11991215 index += 1
1216+ let resolved = ( path as NSString ) . standardizingPath
1217+ guard !loadedRecursivePaths. contains ( resolved) else { continue }
1218+ loadedRecursivePaths. insert ( resolved)
1219+
1220+ scanFontConfigFile (
1221+ atPath: path,
1222+ summary: & summary,
1223+ recursiveConfigPaths: & recursiveConfigPaths
1224+ )
12001225 }
12011226
12021227 return summary
@@ -1266,61 +1291,93 @@ class GhosttyApp {
12661291 return size. intValue
12671292 }
12681293
1269- /// Scans a single config file (and any files it includes) for
1270- /// font settings relevant to cmux's injected CJK fallback. Tracks visited
1271- /// paths to prevent infinite recursion on cyclic includes .
1294+ /// Scans a single config file for font settings relevant to cmux's
1295+ /// injected CJK fallback and updates the pending recursive config-file
1296+ /// queue using Ghostty's repeatable path semantics .
12721297 private static func scanFontConfigFile(
12731298 atPath path: String ,
1274- visited : inout Set < String > ,
1275- summary : inout UserFontConfigSummary
1276- ) -> [ String ] {
1299+ summary : inout UserFontConfigSummary ,
1300+ recursiveConfigPaths : inout [ String ]
1301+ ) {
12771302 let resolved = ( path as NSString ) . standardizingPath
1278- guard !visited. contains ( resolved) else { return [ ] }
1279- visited. insert ( resolved)
1280-
12811303 guard let contents = try ? String ( contentsOfFile: resolved, encoding: . utf8) else {
1282- return [ ]
1304+ return
12831305 }
12841306 let parentDir = ( resolved as NSString ) . deletingLastPathComponent
1285- var includePaths : [ String ] = [ ]
12861307
12871308 for line in contents. components ( separatedBy: . newlines) {
1288- let trimmed = line. trimmingCharacters ( in: . whitespaces)
1289- if trimmed. isEmpty || trimmed. hasPrefix ( " # " ) { continue }
1290- if trimmed. hasPrefix ( " font-codepoint-map " ) {
1291- summary. containsCodepointMap = true
1292- }
1293- if let fontFamily = configValue ( for: " font-family " , in: trimmed) {
1294- summary. recordFontFamily ( fontFamily)
1295- }
1296- if let includePath = configIncludePath ( from: trimmed, parentDir: parentDir) {
1297- includePaths. append ( includePath)
1309+ guard let entry = parsedConfigEntry ( from: line) else { continue }
1310+
1311+ switch entry. key {
1312+ case " font-codepoint-map " :
1313+ guard let value = entry. value else { continue }
1314+ summary. applyFontCodepointMap ( value)
1315+ case " font-family " :
1316+ guard let value = entry. value else { continue }
1317+ summary. recordFontFamily ( value)
1318+ case " config-file " :
1319+ guard let value = entry. value else { continue }
1320+ applyConfigFileDirective (
1321+ value,
1322+ parentDir: parentDir,
1323+ recursiveConfigPaths: & recursiveConfigPaths
1324+ )
1325+ default :
1326+ continue
12981327 }
12991328 }
1300-
1301- return includePaths
13021329 }
13031330
1304- private static func configValue( for key: String , in line: String ) -> String ? {
1305- guard let separatorIndex = line. firstIndex ( of: " = " ) else { return nil }
1331+ private static func parsedConfigEntry(
1332+ from rawLine: String
1333+ ) -> ( key: String , value: String ? ) ? {
1334+ var trimmed = rawLine. trimmingCharacters ( in: . whitespacesAndNewlines)
1335+ if trimmed. hasPrefix ( " \u{FEFF} " ) {
1336+ trimmed. removeFirst ( )
1337+ }
1338+ if trimmed. isEmpty || trimmed. hasPrefix ( " # " ) { return nil }
13061339
1307- let parsedKey = line [ ..< separatorIndex] . trimmingCharacters ( in: . whitespacesAndNewlines)
1308- guard parsedKey == key else { return nil }
1340+ guard let separatorIndex = trimmed. firstIndex ( of: " = " ) else {
1341+ return ( trimmed. trimmingCharacters ( in: . whitespacesAndNewlines) , nil )
1342+ }
13091343
1310- return line [ line. index ( after: separatorIndex) ... ]
1344+ let key = trimmed [ ..< separatorIndex] . trimmingCharacters ( in: . whitespacesAndNewlines)
1345+ var value = trimmed [ trimmed. index ( after: separatorIndex) ... ]
13111346 . trimmingCharacters ( in: . whitespacesAndNewlines)
1312- . trimmingCharacters ( in: CharacterSet ( charactersIn: " \" " ) )
1347+
1348+ if value. count >= 2 , value. hasPrefix ( " \" " ) , value. hasSuffix ( " \" " ) {
1349+ value. removeFirst ( )
1350+ value. removeLast ( )
1351+ }
1352+
1353+ return ( String ( key) , String ( value) )
13131354 }
13141355
1315- private static func configIncludePath( from line: String , parentDir: String ) -> String ? {
1316- guard var includePath = configValue ( for: " config-file " , in: line) else { return nil }
1317- if includePath. hasSuffix ( " ? " ) {
1356+ private static func applyConfigFileDirective(
1357+ _ value: String ,
1358+ parentDir: String ,
1359+ recursiveConfigPaths: inout [ String ]
1360+ ) {
1361+ if value. isEmpty {
1362+ recursiveConfigPaths. removeAll ( )
1363+ return
1364+ }
1365+
1366+ var includePath = value
1367+ if includePath. hasPrefix ( " ? " ) {
1368+ includePath. removeFirst ( )
1369+ }
1370+ if includePath. count >= 2 , includePath. hasPrefix ( " \" " ) , includePath. hasSuffix ( " \" " ) {
1371+ includePath. removeFirst ( )
13181372 includePath. removeLast ( )
13191373 }
1374+ guard !includePath. isEmpty else { return }
1375+
13201376 let expanded = NSString ( string: includePath) . expandingTildeInPath
1321- return ( expanded as NSString ) . isAbsolutePath
1377+ let absolute = ( expanded as NSString ) . isAbsolutePath
13221378 ? expanded
13231379 : ( parentDir as NSString ) . appendingPathComponent ( expanded)
1380+ recursiveConfigPaths. append ( absolute)
13241381 }
13251382
13261383 static func shouldLoadLegacyGhosttyConfig(
0 commit comments