theme.go
func (s *ThemeManager) loadTheme(name string) {
	thememtx.Lock()
	defer thememtx.Unlock()

	if _, ok := s.themes[name]; ok {
		return
	}

	theme := theme{parent: defaultTheme, title: "", author: ""}
	theme.colors = make(map[string]term.Attribute, 0)
	theme.objects = make(map[string]string, 0)

	file, err := os.Open(s.themePath + string(os.PathSeparator) + name + themeSuffix)
	if err != nil {
		panic("Failed to open theme " + name + " : " + err.Error())
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)

		// skip comments
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
			continue
		}

		// skip invalid lines
		if !strings.Contains(line, "=") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		if (strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")) ||
			(strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"")) {
			toTrim, _ := utf8.DecodeRuneInString(value)
			value = strings.Trim(value, string(toTrim))
		}

		low := strings.ToLower(key)
		if low == "parent" {
			theme.parent = value
		} else if low == "author" {
			theme.author = value
		} else if low == "name" || low == "title" {
			theme.title = value
		} else if low == "version" {
			theme.version = value
		} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
			// the first case is a reference to existing color (of this or parent theme)
			// the second is the real color
			if strings.HasSuffix(value, "Back") || strings.HasSuffix(value, "Text") {
				clr, ok := theme.colors[value]
				if !ok {
					v := value
					// if color starts with 'parent.' it means the parent color
					// must be used always. It may be useful to load inversed
					// text and background colors of parent theme
					if strings.HasPrefix(v, "parent.") {
						v = strings.TrimPrefix(v, "parent.")
					}
					sch, schOk := s.themes[theme.parent]
					if schOk {
						clr, ok = sch.colors[v]
					} else {
						panic(fmt.Sprintf("%v: Parent theme '%v' not found", name, theme.parent))
					}
				}
				if ok {
					theme.colors[key] = clr
				} else {
					panic(fmt.Sprintf("%v: Failed to find color '%v' by reference", name, value))
				}
			} else {
				c := StringToColor(value)
				if c%32 == 0 {
					panic("Failed to read color: " + value)
				}
				theme.colors[key] = c
			}
		} else {
			theme.objects[key] = value
		}
	}

	s.themes[name] = theme
}
func (s *ThemeManager) loadTheme(name string) {
	thememtx.Lock()
	defer thememtx.Unlock()

	if _, ok := s.themes[name]; ok {
		return
	}

	theme := theme{parent: defaultTheme, title: "", author: ""}
	theme.colors = make(map[string]term.Attribute, 0)
	theme.objects = make(map[string]string, 0)

	file, err := os.Open(s.themePath + string(os.PathSeparator) + name + themeSuffix)
	if err != nil {
		panic("Failed to open theme " + name + " : " + err.Error())
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		line = strings.TrimSpace(line)

		// skip comments
		if strings.HasPrefix(line, "#") || strings.HasPrefix(line, "/") {
			continue
		}

		// skip invalid lines
		if !strings.Contains(line, "=") {
			continue
		}

		parts := strings.SplitN(line, "=", 2)
		key := strings.TrimSpace(parts[0])
		value := strings.TrimSpace(parts[1])

		if (strings.HasPrefix(value, "'") && strings.HasSuffix(value, "'")) ||
			(strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"")) {
			toTrim, _ := utf8.DecodeRuneInString(value)
			value = strings.Trim(value, string(toTrim))
		}

		low := strings.ToLower(key)
		if low == "parent" {
			theme.parent = value
		} else if low == "author" {
			theme.author = value
		} else if low == "name" || low == "title" {
			theme.title = value
		} else if low == "version" {
			theme.version = value
		} else if strings.HasSuffix(key, "Back") || strings.HasSuffix(key, "Text") {
			// the first case is a reference to existing color (of this or parent theme)
			// the second is the real color
			if strings.HasSuffix(value, "Back") || strings.HasSuffix(value, "Text") {
				clr, ok := theme.colors[value]
				if !ok {
					v := value
					// if color starts with 'parent.' it means the parent color
					// must be used always. It may be useful to load inversed
					// text and background colors of parent theme
					if strings.HasPrefix(v, "parent.") {
						v = strings.TrimPrefix(v, "parent.")
					}
					sch, schOk := s.themes[theme.parent]
					if schOk {
						clr, ok = sch.colors[v]
					} else {
						panic(fmt.Sprintf("%v: Parent theme '%v' not found", name, theme.parent))
					}
				}
				if ok {
					theme.colors[key] = clr
				} else {
					panic(fmt.Sprintf("%v: Failed to find color '%v' by reference", name, value))
				}
			} else {
				c := StringToColor(value)
				if c%32 == 0 {
					panic("Failed to read color: " + value)
				}
				theme.colors[key] = c
			}
		} else {
			theme.objects[key] = value
		}
	}

	s.themes[name] = theme
}
// loadTheme loads the theme if it is not in the cache already.
// If theme is in the cache loadTheme does nothing