Backspace sends DEL instead of ^H.
[spft.git] / Settings.cpp
blob4daa9bbd14fb7b3f609926394f2c38c55dbfcb26
1 #include "Settings.h"
2 #include <string>
3 #include <stdexcept>
4 #include <sstream>
5 #include <fstream>
6 #include <ctype.h>
7 #include <sys/types.h>
8 #include <pwd.h>
9 #include <unistd.h>
12 Settings settings = {
13 .font_spec = "Helvetica Neue,sans\\-serif-16.5:style=Light",
14 .line_drawing_font_spec = "",
15 .monospace_font_spec = "monospace-16.5",
16 .term_name = "xterm",
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 = "",
25 .tab_width = 24,
26 .column_separation = 20,
27 .synthetic_tab_spaces = 0,
28 .geometry = "80x24",
29 .border = 0,
30 .default_auto_wrap = true,
31 .font_size_increment = 0.5,
35 class parse_error : public std::runtime_error {
36 public:
37 parse_error(const std::string& message)
38 : std::runtime_error(message) {}
42 class SettingsParser {
43 public:
44 SettingsParser(const char* text, int length)
45 : p(text), end(text + length) {}
47 void parse();
49 void parse_setting(std::string setting_name, std::string value_token);
51 protected:
52 const char* p;
53 const char* end;
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);
102 else
103 fprintf(stderr, "Unknown setting: %s.\n", setting_name.c_str());
107 void SettingsParser::parse()
109 try {
110 while (true) {
111 std::string token = next_token();
112 if (token.empty())
113 break;
114 if (token == "," || token == ";") {
115 // We allow commas and semicolons between settings.
116 continue;
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());
138 // Set the setting.
139 try {
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()
155 char c;
157 // Skip whitespace and comments.
158 while (true) {
159 if (p >= end)
160 return "";
161 c = *p;
162 if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
163 p += 1;
164 else if (c == '#') {
165 // Skip to end of line.
166 p += 1;
167 while (true) {
168 if (p >= end)
169 return "";
170 c = *p++;
171 if (c == '\r' || c == '\n')
172 break;
175 else
176 break;
179 const char* token_start = p;
180 c = *p++;
182 if (c == '"' || c == '\'') {
183 // String.
184 char quote_char = c;
185 while (true) {
186 if (p >= end)
187 throw parse_error("Unterminated string.");
188 c = *p++;
189 if (c == quote_char)
190 break;
191 if (c == '\\') {
192 // Consume the next character.
193 if (p >= end)
194 throw parse_error("Unterminated string.");
195 p += 1;
200 else if (isalpha(c)) {
201 // Identifier.
202 while (p < end) {
203 c = *p;
204 if (!isalnum(c) && c != '_')
205 break;
206 p += 1;
210 else if (isdigit(c)) {
211 // Number.
212 if (c == '0' && p < end && (*p == 'x' || *p == 'X')) {
213 // Hex number.
214 p += 1; // Skip the "x".
215 if (p >= end || !isxdigit(*p))
216 throw parse_error("Incomplete hex number.");
217 while (p < end) {
218 if (!isxdigit(*p))
219 break;
220 p += 1;
223 else {
224 // Decimal number (potentially a float).
225 while (p < end) {
226 c = *p;
227 if (!isdigit(c) && c != '.')
228 break;
229 p += 1;
234 else if (c == '=' || c == ',' || c == ';') {
235 // These are single-character tokens.
238 else {
239 // Unknown.
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) {
257 char c = *p;
258 if (c == '\\')
259 p += 1;
260 result << c;
262 return result.str();
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);
270 if (*end_ptr != 0)
271 throw parse_error("Not a number: " + token);
272 return result;
276 float SettingsParser::parse_float(std::string token)
278 char* end_ptr = nullptr;
279 float result = strtof(token.c_str(), &end_ptr);
280 if (*end_ptr != 0)
281 throw parse_error("Not a floating-point number: " + token);
282 return result;
286 bool SettingsParser::parse_bool(std::string token)
288 if (token == "true")
289 return true;
290 else if (token == "false")
291 return 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
300 // read.
302 std::string config_dir;
303 const char* env_dir = getenv("XDG_CONFIG_HOME");
304 if (env_dir)
305 config_dir = env_dir;
306 else {
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
322 // exist.
323 return;
325 std::string contents(
326 (std::istreambuf_iterator<char>(file)),
327 std::istreambuf_iterator<char>());
328 SettingsParser parser(contents.data(), contents.size());
329 parser.parse();
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)
342 return "";
343 return home_dir;