13 .font_spec
= "Helvetica Neue,sans\\-serif-16.5:style=Light",
14 .line_drawing_font_spec
= "",
15 .monospace_font_spec
= "monospace-16.5",
17 .default_foreground_color
= 0,
18 .default_background_color
= 15,
19 .average_character_width
= 0.6,
20 .double_click_ms
= 300,
21 .word_separator_characters
= " \t!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}",
22 .additional_word_separator_characters
= "",
23 .window_title
= "spft",
24 .working_directory
= "",
26 .column_separation
= 20,
27 .synthetic_tab_spaces
= 0,
30 .default_auto_wrap
= true,
31 .font_size_increment
= 0.5,
35 class parse_error
: public std::runtime_error
{
37 parse_error(const std::string
& message
)
38 : std::runtime_error(message
) {}
42 class SettingsParser
{
44 SettingsParser(const char* text
, int length
)
45 : p(text
), end(text
+ length
) {}
49 void parse_setting(std::string setting_name
, std::string value_token
);
55 std::string
next_token();
56 bool is_identifier(std::string token
) { return isalpha(token
[0]); }
57 std::string
unquote_string(std::string token
);
58 uint32_t parse_uint32(std::string token
);
59 float parse_float(std::string token
);
60 bool parse_bool(std::string token
);
64 void SettingsParser::parse_setting(std::string setting_name
, std::string value_token
)
66 if (setting_name
== "font")
67 settings
.font_spec
= unquote_string(value_token
);
68 else if (setting_name
== "line_drawing_font")
69 settings
.line_drawing_font_spec
= unquote_string(value_token
);
70 else if (setting_name
== "monospace_font")
71 settings
.monospace_font_spec
= unquote_string(value_token
);
72 else if (setting_name
== "term_name")
73 settings
.term_name
= unquote_string(value_token
);
74 else if (setting_name
== "default_foreground_color")
75 settings
.default_foreground_color
= parse_uint32(value_token
);
76 else if (setting_name
== "default_background_color")
77 settings
.default_background_color
= parse_uint32(value_token
);
78 else if (setting_name
== "average_character_width")
79 settings
.average_character_width
= parse_float(value_token
);
80 else if (setting_name
== "double_click_ms")
81 settings
.double_click_ms
= parse_uint32(value_token
);
82 else if (setting_name
== "word_separator_characters")
83 settings
.word_separator_characters
= unquote_string(value_token
);
84 else if (setting_name
== "additional_word_separator_characters")
85 settings
.additional_word_separator_characters
= unquote_string(value_token
);
86 else if (setting_name
== "window_title")
87 settings
.window_title
= unquote_string(value_token
);
88 else if (setting_name
== "tab_width")
89 settings
.tab_width
= parse_uint32(value_token
);
90 else if (setting_name
== "column_separation")
91 settings
.column_separation
= parse_uint32(value_token
);
92 else if (setting_name
== "synthetic_tab_spaces")
93 settings
.synthetic_tab_spaces
= parse_uint32(value_token
);
94 else if (setting_name
== "geometry")
95 settings
.geometry
= unquote_string(value_token
);
96 else if (setting_name
== "border")
97 settings
.border
= parse_uint32(value_token
);
98 else if (setting_name
== "default_auto_wrap")
99 settings
.default_auto_wrap
= parse_bool(value_token
);
100 else if (setting_name
== "font_size_increment")
101 settings
.font_size_increment
= parse_float(value_token
);
103 fprintf(stderr
, "Unknown setting: %s.\n", setting_name
.c_str());
107 void SettingsParser::parse()
111 std::string token
= next_token();
114 if (token
== "," || token
== ";") {
115 // We allow commas and semicolons between settings.
119 // Settings have the form "<name> = <value>".
120 if (!is_identifier(token
)) {
121 std::stringstream message
;
122 message
<< "Not a setting name: \"" << token
<< '"';
123 throw parse_error(message
.str());
125 std::string setting_name
= token
;
126 if (next_token() != "=") {
127 std::stringstream message
;
128 message
<< "Missing '=' for \"" << setting_name
<< "\" setting.";
129 throw parse_error(message
.str());
131 std::string value_token
= next_token();
132 if (value_token
.empty()) {
133 std::stringstream message
;
134 message
<< "Missing value for \"" << setting_name
<< "\" setting.";
135 throw parse_error(message
.str());
140 parse_setting(setting_name
, value_token
);
142 catch (parse_error
& e
) {
143 fprintf(stderr
, "Invalid \"%s\" setting: %s\n", setting_name
.c_str(), e
.what());
147 catch (parse_error
& e
) {
148 fprintf(stderr
, "Error in settings file: %s\n", e
.what());
153 std::string
SettingsParser::next_token()
157 // Skip whitespace and comments.
162 if (c
== ' ' || c
== '\t' || c
== '\r' || c
== '\n')
165 // Skip to end of line.
171 if (c
== '\r' || c
== '\n')
179 const char* token_start
= p
;
182 if (c
== '"' || c
== '\'') {
187 throw parse_error("Unterminated string.");
192 // Consume the next character.
194 throw parse_error("Unterminated string.");
200 else if (isalpha(c
)) {
204 if (!isalnum(c
) && c
!= '_')
210 else if (isdigit(c
)) {
212 if (c
== '0' && p
< end
&& (*p
== 'x' || *p
== 'X')) {
214 p
+= 1; // Skip the "x".
215 if (p
>= end
|| !isxdigit(*p
))
216 throw parse_error("Incomplete hex number.");
224 // Decimal number (potentially a float).
227 if (!isdigit(c
) && c
!= '.')
234 else if (c
== '=' || c
== ',' || c
== ';') {
235 // These are single-character tokens.
240 std::stringstream message
;
241 message
<< "Invalid character: '" << c
<< "'";
242 throw parse_error(message
.str());
245 return std::string(token_start
, p
- token_start
);
249 std::string
SettingsParser::unquote_string(std::string token
)
251 if (token
.empty() || (token
[0] != '"' && token
[0] != '\''))
252 throw parse_error("Not a string: " + token
);
254 token
= token
.substr(1, token
.length() - 2);
255 std::stringstream result
;
256 for (auto p
= token
.begin(); p
< token
.end(); ++p
) {
266 uint32_t SettingsParser::parse_uint32(std::string token
)
268 char* end_ptr
= nullptr;
269 uint32_t result
= strtoul(token
.c_str(), &end_ptr
, 0);
271 throw parse_error("Not a number: " + token
);
276 float SettingsParser::parse_float(std::string token
)
278 char* end_ptr
= nullptr;
279 float result
= strtof(token
.c_str(), &end_ptr
);
281 throw parse_error("Not a floating-point number: " + token
);
286 bool SettingsParser::parse_bool(std::string token
)
290 else if (token
== "false")
292 throw parse_error("Not a boolean: " + token
);
297 void Settings::read_settings_files()
299 // $XDG_CONFIG_HOME/spft/settings is actually the only settings file we
302 std::string config_dir
;
303 const char* env_dir
= getenv("XDG_CONFIG_HOME");
305 config_dir
= env_dir
;
307 // Default to ~/.config.
308 std::string home_dir
= home_path();
309 if (!home_dir
.empty())
310 config_dir
= home_dir
+ "/.config";
312 if (!config_dir
.empty())
313 read_settings_file(config_dir
+ "/spft/settings");
317 void Settings::read_settings_file(std::string path
)
319 std::fstream
file(path
);
320 if (!file
.is_open()) {
321 // All settings files are optional, so don't complain if it doesn't
325 std::string
contents(
326 (std::istreambuf_iterator
<char>(file
)),
327 std::istreambuf_iterator
<char>());
328 SettingsParser
parser(contents
.data(), contents
.size());
333 std::string
Settings::home_path()
335 const char* home_dir
= getenv("HOME");
336 if (home_dir
== nullptr) {
337 const struct passwd
* user_info
= getpwuid(getuid());
338 if (user_info
&& user_info
->pw_dir
[0])
339 home_dir
= user_info
->pw_dir
;
341 if (home_dir
== nullptr)