/* * Module for handling variables in QEmacs * * Copyright (c) 2000-2026 Charlie Gordon. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qe.h" #include "variables.h" static QVarType qe_variable_set_value_offset(EditState *s, VarDef *vp, void *ptr, const char *value, int num); static QVarType qe_variable_set_value_generic(EditState *s, VarDef *vp, void *ptr, const char *value, int num); static QVarType qe_variable_set_value_style(EditState *s, VarDef *vp, void *ptr, const char *value, int num); const char * const var_domain[] = { "global", /* VAR_GLOBAL */ "state", /* VAR_STATE */ "buffer", /* VAR_BUFFER */ "window", /* VAR_WINDOW */ "mode", /* VAR_MODE */ "self", /* VAR_SELF */ }; static const char * const var_domain_desc[] = { "Global", /* VAR_GLOBAL */ "State", /* VAR_STATE */ "Buffer", /* VAR_BUFFER */ "Window", /* VAR_WINDOW */ "Mode", /* VAR_MODE */ "Other", /* VAR_SELF */ }; static int use_full_version = 1; static VarDef var_table[] = { S_VAR( "screen-width", width, VAR_NUMBER, VAR_RO, "Number of columns available for display on screen." ) S_VAR( "screen-height", height, VAR_NUMBER, VAR_RO, "Number of lines available for display on screen." ) S_VAR( "is-full-screen", is_full_screen, VAR_NUMBER, VAR_RO, "Set if this window is displayed in full screen (without borders)." ) S_VAR( "flag-split-window-change-focus", flag_split_window_change_focus, VAR_NUMBER, VAR_RW_SAVE, "Set if `split-window` should set focus to the new window." ) S_VAR( "shell-buffer-read-only", shell_buffer_read_only, VAR_NUMBER, VAR_RW_SAVE, "Set if shell buffers should become read-only." ) S_VAR( "shell-mode-auto-interactive", shell_mode_auto_interactive, VAR_NUMBER, VAR_RW_SAVE, "Set if moving to end of buffer switches to interactive mode." ) S_VAR( "shell-command-other-window", shell_command_other_window, VAR_NUMBER, VAR_RW_SAVE, "Set if shell command should use another window." ) // XXX: need set_value function to perform side effect S_VAR( "backspace-is-control-h", backspace_is_control_h, VAR_NUMBER, VAR_RW_SAVE, "Set if the Delete key sends a control-H." ) S_VAR( "ungot-key", ungot_key, VAR_NUMBER, VAR_RW, NULL ) // XXX: need set_value function S_VAR( "QEPATH", res_path, VAR_CHARS, VAR_RO, "List of directories to search for standard files to load." ) //S_VAR( "it", it, VAR_NUMBER, VAR_RW, NULL ) S_VAR( "ignore-spaces", ignore_spaces, VAR_NUMBER, VAR_RW_SAVE, "Set to ignore spaces in compare-windows." ) S_VAR( "ignore-comments", ignore_comments, VAR_NUMBER, VAR_RW_SAVE, "Set to ignore comments in compare-windows." ) S_VAR( "ignore-case", ignore_case, VAR_NUMBER, VAR_RW_SAVE, "Set to ignore case in compare-windows." ) S_VAR( "ignore-preproc", ignore_preproc, VAR_NUMBER, VAR_RW_SAVE, "Set to ignore preprocessing directives in compare-windows." ) S_VAR( "ignore-equivalent", ignore_equivalent, VAR_NUMBER, VAR_RW_SAVE, "Set to ignore equivalent strings defined by define-equivalent." ) S_VAR( "hilite-region", hilite_region, VAR_NUMBER, VAR_RW_SAVE, "Set to highlight the region after setting the mark." ) S_VAR( "mmap-threshold", mmap_threshold, VAR_NUMBER, VAR_RW_SAVE, // XXX: need set_value function "Size from which files are mmapped instead of loaded in memory." ) S_VAR( "max-load-size", max_load_size, VAR_NUMBER, VAR_RW_SAVE, // XXX: need set_value function "Maximum size for files to be loaded or mmapped into a buffer." ) S_VAR( "show-unicode", show_unicode, VAR_NUMBER, VAR_RW_SAVE, // XXX: need set_value function "Set to show non-ASCII characters as unicode escape sequences." ) S_VAR( "default-tab-width", default_tab_width, VAR_NUMBER, VAR_RW_SAVE, // XXX: need set_value function "Default value of `tab-width` for buffers that do not override it." ) S_VAR( "default-fill-column", default_fill_column, VAR_NUMBER, VAR_RW_SAVE, // XXX: need set_value function "Default value of `fill-column` for buffers that do not override it" ) S_VAR( "backup-inhibited", backup_inhibited, VAR_NUMBER, VAR_RW_SAVE, "Set to prevent automatic backups of modified files" ) S_VAR( "c-label-indent", c_label_indent, VAR_NUMBER, VAR_RW_SAVE, "Number of columns to adjust indentation of C labels." ) S_VAR( "macro-counter", macro_counter, VAR_NUMBER, VAR_RW_SAVE, "Macro counter: insert with C-x C-k TAB, set with C-x C-k C-c." ) //B_VAR( "screen-charset", charset, VAR_NUMBER, VAR_RW, NULL ) B_VAR_F( "mark", mark, VAR_NUMBER, VAR_RW, qe_variable_set_value_offset, "The position of the beginning of the current region." ) B_VAR( "bufsize", total_size, VAR_NUMBER, VAR_RO, "The number of bytes in the current buffer." ) B_VAR( "bufname", name, VAR_CHARS, VAR_RO, "The name of the current buffer." ) B_VAR( "filename", filename, VAR_CHARS, VAR_RO, "The name of the file associated with the current buffer." ) B_VAR( "tab-width", tab_width, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Distance between tab stops (for display of tab characters), in columns." ) B_VAR( "fill-column", fill_column, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Column beyond which automatic line-wrapping should happen." ) W_VAR_F( "point", offset, VAR_NUMBER, VAR_RW, qe_variable_set_value_offset, /* should be window-point */ "Current value of point in this window." ) W_VAR( "indent-width", indent_width, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Number of columns to indent by for a syntactic level." ) W_VAR( "indent-tabs-mode", indent_tabs_mode, VAR_NUMBER, VAR_RW, "Set if indentation can insert tabs." ) W_VAR_F( "default-style", default_style, VAR_STYLE, VAR_RW, qe_variable_set_value_style, "Default text style for this window." ) W_VAR_F( "region-style", region_style, VAR_STYLE, VAR_RW, qe_variable_set_value_style, "Text style for the current region in this window." ) W_VAR_F( "curline-style", curline_style, VAR_STYLE, VAR_RW, qe_variable_set_value_style, "Text style for the current line in this window." ) W_VAR( "window-width", width, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Number of display columns in this window." ) W_VAR( "window-height", height, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Number of display lines in this window." ) W_VAR( "window-left", xleft, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Display column of the left edge of this window." ) W_VAR( "window-top", ytop, VAR_NUMBER, VAR_RW, // XXX: need set_value function "Display line of the top edge of this window." ) W_VAR( "window-prompt", prompt, VAR_STRING, VAR_RW, "Prompt string to show for this window." ) W_VAR( "dump-width", dump_width, VAR_NUMBER, VAR_RW, NULL ) // XXX: need set_value function M_VAR( "mode-name", name, VAR_STRING, VAR_RO, "Name of the current major mode." ) M_VAR( "auto-indent", auto_indent, VAR_NUMBER, VAR_RW, "Set for automatic indentation on new lines." ) #ifdef CONFIG_SESSION G_VAR( "use-session-file", use_session_file, VAR_NUMBER, VAR_RW, NULL ) #endif G_VAR( "force-tty", force_tty, VAR_NUMBER, VAR_RW, "Set to prevent graphics display." ) G_VAR( "disable-crc", disable_crc, VAR_NUMBER, VAR_RW_SAVE, "Set to prevent CRC based display cache." ) G_VAR( "use-html", use_html, VAR_NUMBER, VAR_RW, NULL ) G_VAR( "is-player", is_player, VAR_NUMBER, VAR_RW, NULL ) G_VAR( "full-version", use_full_version, VAR_NUMBER, VAR_RW, NULL ) /* more buffer fields: modified, readonly, binary, charset */ /* more window fields: mode_line, color, input_method... */ //G_VAR( "text-mode-line", text_mode.mode_line, VAR_STRING, VAR_RW, NULL ) //G_VAR( "binary-mode-line", binary_mode.mode_line, VAR_STRING, VAR_RW, NULL ) //G_VAR( "hex-mode-line", hex_mode.mode_line, VAR_STRING, VAR_RW, NULL ) //G_VAR( "unicode-mode-line", unihex_mode.mode_line, VAR_STRING, VAR_RW, NULL ) //Dispatch these to the appropriate modules //G_VAR( "c-mode-extensions", c_mode.extensions, VAR_STRING, VAR_RW, NULL ) //G_VAR( "c-mode-keywords", c_mode.keywords, VAR_STRING, VAR_RW, NULL ) //G_VAR( "c-mode-types", c_mode.types, VAR_STRING, VAR_RW, NULL ) //G_VAR( "html-src-mode-extensions", htmlsrc_mode.extensions, VAR_STRING, VAR_RW, NULL ) //G_VAR( "html-mode-extensions", html_mode.extensions, VAR_STRING, VAR_RW, NULL ) //G_VAR( "perl-mode-extensions", perl_mode.extensions, VAR_STRING, VAR_RW, NULL ) }; static VarDef *qe_find_variable(QEmacsState *qs, const char *name) { VarDef *vp; for (vp = qs->first_variable; vp; vp = vp->next) { if (strequal(vp->name, name)) return vp; } /* Should have a list of local variables for buffer/window/mode * instances */ return NULL; } void variable_complete(CompleteState *cp, CompleteFunc enumerate) { QEmacsState *qs = cp->s->qs; const VarDef *vp; for (vp = qs->first_variable; vp; vp = vp->next) { (*enumerate)(cp, vp->name, CT_STRX); } } QVarType qe_get_variable(EditState *s, const char *name, char *buf, int size, int *pnum, int as_source) { const VarDef *vp; int num = 0; QETermStyle style = 0; const char *str = NULL; const void *ptr; const char *endp; /* find standard variable and user variables */ vp = qe_find_variable(s->qs, name); if (!vp) { /* Try environment */ str = getenv(name); if (!str) { if (size > 0) *buf = '\0'; return VAR_UNKNOWN; } num = strtol_c(str, &endp, 0); if (pnum && endp != str && *endp == '\0') { *pnum = num; return VAR_NUMBER; } if (as_source) strquote(buf, size, str, -1, SQB_USE_EXTENDED); else pstrcpy(buf, size, str); return VAR_STRING; } switch (vp->domain) { case VAR_SELF: ptr = &vp->value; break; case VAR_GLOBAL: ptr = vp->value.ptr; break; case VAR_STATE: ptr = (const u8*)s->qs + vp->value.offset; break; case VAR_BUFFER: ptr = (const u8*)s->b + vp->value.offset; break; case VAR_WINDOW: ptr = (const u8*)s + vp->value.offset; break; case VAR_MODE: ptr = (const u8*)s->mode + vp->value.offset; break; default: if (size > 0) *buf = '\0'; return VAR_UNKNOWN; } switch (vp->type) { case VAR_STRING: memcpy(&str, ptr, sizeof(str)); if (as_source) strquote(buf, size, str, -1, SQB_USE_EXTENDED); else pstrcpy(buf, size, str ? str : ""); break; case VAR_CHARS: str = (const char*)ptr; if (as_source) strquote(buf, size, str, -1, SQB_USE_EXTENDED); else pstrcpy(buf, size, str); break; case VAR_NUMBER: memcpy(&num, ptr, sizeof(num)); if (pnum) { *pnum = num; } else if (num < 10) { snprintf(buf, size, "%d", num); } else if (num % (1 << 20) == 0) { snprintf(buf, size, "%d (0x%x, %d MB)", num, (unsigned)num, num >> 20); } else if (num % (1 << 10) == 0) { snprintf(buf, size, "%d (0x%x, %d KB)", num, (unsigned)num, num >> 10); } else { snprintf(buf, size, "%d (0x%x)", num, (unsigned)num); } break; case VAR_STYLE: memcpy(&style, ptr, sizeof(style)); if (pnum) { *pnum = (int)style; } else { int len = snprintf(buf, size, "0x%x (", style); len += qe_term_style_string(buf + len, size - len, num); snprintf(buf + len, size - len, ")"); } break; default: if (size > 0) *buf = '\0'; return VAR_UNKNOWN; } return vp->type; } static QVarType qe_variable_set_value_offset(EditState *s, VarDef *vp, void *ptr, const char *value, int num) { if (value) { /* XXX: should have default, min and max values */ return VAR_INVALID; } else { int *pnum = (int *)ptr; *pnum = clamp_offset(num, 0, s->b->total_size); return VAR_NUMBER; } } static QVarType qe_variable_set_value_style(EditState *s, VarDef *vp, void *ptr, const char *value, int num) { QETermStyle style = num; if (value && qe_term_get_style(&style, value)) { const char *p; style = strtol_c(value, &p, 0); if (*p) return VAR_INVALID; } memcpy(ptr, &style, sizeof(style)); return VAR_STYLE; } static QVarType qe_variable_set_value_generic(EditState *s, VarDef *vp, void *ptr, const char *value, int num) { char buf[32]; char **pstr; int *pnum; switch (vp->type) { case VAR_STRING: if (!value) { snprintf(buf, sizeof(buf), "%d", num); value = buf; } pstr = (char **)ptr; if (!strequal(*pstr, value)) { if (vp->str_alloc) qe_free(pstr); *pstr = qe_strdup(value); vp->str_alloc = 1; vp->modified = 1; } break; case VAR_CHARS: if (!value) { snprintf(buf, sizeof(buf), "%d", num); value = buf; } if (!strequal(ptr, value)) { pstrcpy(ptr, vp->size, value); vp->modified = 1; } break; case VAR_NUMBER: case VAR_STYLE: /* XXX: should have default, min and max values? */ if (value) { const char *p; num = strtol_c(value, &p, 0); if (*p) { return VAR_INVALID; } } pnum = (int *)ptr; if (*pnum != num) { *pnum = num; vp->modified = 1; } break; default: return VAR_UNKNOWN; } return vp->type; } QVarType qe_set_variable(EditState *s, const char *name, const char *value, int num) { QEmacsState *qs = s->qs; void *ptr; VarDef *vp; vp = qe_find_variable(qs, name); if (!vp) { /* Create user variable (global/buffer/window/mode?) */ vp = qe_mallocz(VarDef); vp->name = qe_strdup(name); vp->var_alloc = 1; vp->modified = 1; vp->domain = VAR_SELF; vp->rw = VAR_RW_SAVE; if (value) { vp->str_alloc = 1; vp->value.str = qe_strdup(value); vp->type = VAR_STRING; } else { vp->value.num = num; vp->type = VAR_NUMBER; } qe_register_variables(qs, vp, 1); return vp->type; } else if (vp->rw == VAR_RO) { return VAR_READONLY; } else { switch (vp->domain) { case VAR_SELF: ptr = &vp->value; break; case VAR_GLOBAL: ptr = vp->value.ptr; break; case VAR_STATE: ptr = (u8*)qs + vp->value.offset; break; case VAR_BUFFER: ptr = (u8*)s->b + vp->value.offset; break; case VAR_WINDOW: ptr = (u8*)s + vp->value.offset; break; case VAR_MODE: ptr = (u8*)s->mode + vp->value.offset; break; default: return VAR_UNKNOWN; } return vp->set_value(s, vp, ptr, value, num); } } void do_show_variable(EditState *s, const char *name) { char buf[MAX_FILENAME_SIZE]; switch (qe_get_variable(s, name, buf, sizeof(buf), NULL, 1)) { case VAR_UNKNOWN: put_error(s, "No variable %s", name); break; default: put_status(s, "%s -> %s", name, buf); break; } } void do_set_variable(EditState *s, const char *name, const char *value) { switch (qe_set_variable(s, name, value, 0)) { case VAR_UNKNOWN: put_error(s, "Variable %s is invalid", name); break; case VAR_READONLY: put_error(s, "Variable %s is read-only", name); break; case VAR_INVALID: put_error(s, "Invalid value for variable %s: %s", name, value); break; default: do_show_variable(s, name); break; } } static void do_describe_variable(EditState *s, const char *name) { EditBuffer *b; VarDef *vp; if ((vp = qe_find_variable(s->qs, name)) == NULL) { put_error(s, "No variable %s", name); return; } b = new_help_buffer(s); if (!b) return; eb_putc(b, '\n'); /* print name, class, current value and short description */ eb_variable_print_entry(b, vp, s, 1); // XXX: should look up markdown documentation show_popup(s, b, "Help"); } static void do_show_all_variables(EditState *s) { EditBuffer *b; VarDef *vp; int i; b = new_help_buffer(s); if (!b) return; // XXX: should create links to markdown documentation for (i = 0; i < countof(var_domain); i++) { eb_print_style(b, DESCRIBE_STYLE_HEAD, "%s variables:\n", var_domain_desc[i]); for (vp = s->qs->first_variable; vp; vp = vp->next) { if (vp->domain == i) { /* print name, class, current value and short description */ eb_variable_print_entry(b, vp, s, 1); } } eb_putc(b, '\n'); } show_popup(s, b, "Variables"); } void qe_register_variables(QEmacsState *qs, VarDef *vars, int count) { VarDef *vp; for (vp = vars; vp < vars + count; vp++) { if (!vp->set_value) vp->set_value = qe_variable_set_value_generic; vp->next = vp + 1; } vp[-1].next = qs->first_variable; qs->first_variable = vars; } /* should register this as help function */ void qe_list_variables(EditState *s, EditBuffer *b) { char buf[MAX_FILENAME_SIZE]; char typebuf[32]; const char *type; const VarDef *vp; eb_puts(b, "\n variables:\n\n"); for (vp = s->qs->first_variable; vp; vp = vp->next) { /* XXX: merge with variable_print_entry() */ switch (vp->type) { case VAR_NUMBER: type = "int"; break; case VAR_STYLE: type = "style"; break; case VAR_STRING: type = "string"; break; case VAR_CHARS: type = typebuf; snprintf(typebuf, sizeof(typebuf), "char[%d]", vp->size); break; default: type = "var"; break; } qe_get_variable(s, vp->name, buf, sizeof(buf), NULL, 1); eb_printf(b, " %s %s %s%s -> %s\n", var_domain[vp->domain], type, vp->rw ? "" : "read-only ", vp->name, buf); } } void qe_save_variables(EditState *s, EditBuffer *b) { char buf[MAX_FILENAME_SIZE]; char varname[32], *p; const VarDef *vp; eb_puts(b, "// variables:\n"); /* Only save customized variables */ for (vp = s->qs->first_variable; vp; vp = vp->next) { if (vp->rw != VAR_RW_SAVE || !vp->modified) continue; pstrcpy(varname, countof(varname), vp->name); for (p = varname; *p; p++) { if (*p == '-') *p = '_'; } qe_get_variable(s, vp->name, buf, sizeof(buf), NULL, 1); eb_printf(b, "%s = %s;\n", varname, buf); } eb_putc(b, '\n'); } int eb_variable_print_entry(EditBuffer *b, VarDef *vp, EditState *s, int long_format) { char buf[256]; char typebuf[32]; const char *type = typebuf; int len = 0; if (!vp) return 0; *typebuf = '\0'; switch (vp->type) { case VAR_NUMBER: type = "int"; break; case VAR_STYLE: type = "style"; break; case VAR_STRING: type = "string"; break; case VAR_CHARS: type = "char"; snprintf(typebuf, sizeof(typebuf), "[%d]", vp->size); break; default: type = "var"; break; } if (long_format) { len += eb_puts(b, " "); if (!vp->rw) { len += eb_print_style(b, QE_STYLE_KEYWORD, "const "); } len += eb_print_style(b, QE_STYLE_TYPE, "%s ", type); } len += eb_print_style(b, QE_STYLE_VARIABLE, "%s", vp->name); if (long_format) len += eb_puts(b, typebuf); len += eb_puts(b, " = "); qe_get_variable(s, vp->name, buf, sizeof(buf), NULL, 1); len += eb_print_style(b, *buf == '\"' ? QE_STYLE_STRING : QE_STYLE_NUMBER, "%s", buf); if (long_format) { eb_putc(b, '\n'); if (vp->desc && *vp->desc) { /* print short description */ eb_print_style(b, QE_STYLE_COMMENT, " %s\n", vp->desc); } } else { if (len + 2 <= 40) { b->tab_width = max_int(len + 2, b->tab_width); len += eb_putc(b, '\t'); } else { b->tab_width = 40; } len += eb_print_style(b, QE_STYLE_COMMENT, " %s%s", vp->rw ? "" : "read-only ", var_domain[vp->domain]); len += eb_print_style(b, QE_STYLE_TYPE, " %s%s", type, typebuf); } return len; } int variable_print_entry(CompleteState *cp, EditState *s, const char *name) { VarDef *vp = qe_find_variable(s->qs, name); if (vp) { // XXX: should pass the target window return eb_variable_print_entry(s->b, vp, s, 0); } else { return eb_puts(s->b, name); } } static CompletionDef variable_completion = { .name = "variable", .enumerate = variable_complete, .print_entry = variable_print_entry, .get_entry = command_get_entry, }; static void value_complete(CompleteState *cp, CompleteFunc enumerate) { style_complete(cp, enumerate); color_complete(cp, enumerate); } static int value_print_entry(CompleteState *cp, EditState *s, const char *name) { if (find_style(name)) return style_print_entry(cp, s, name); else return color_print_entry(cp, s, name); } static CompletionDef value_completion = { .name = "value", .enumerate = value_complete, .print_entry = value_print_entry, .flags = CF_SPACE_OK, }; /*---------------- commands ----------------*/ static const CmdDef var_commands[] = { CMD2( "show-variable", "", "Show the value of a given variable", do_show_variable, ESs, "s{Show variable: }[variable]|variable|") CMD2( "set-variable", "f8", "Set the value of a variable", do_set_variable, ESss, "s{Set variable: }[variable]|variable|s{to value: }[value]|value|") CMD2( "describe-variable", "C-h v", "Show information for a variable", do_describe_variable, ESs, "s{Describe variable: }[variable]|variable|") CMD0( "show-all-variables", "C-h C-v", "Show value and information for all variables", do_show_all_variables) }; static int variables_init(QEmacsState *qs) { qe_register_variables(qs, var_table, countof(var_table)); qe_register_commands(qs, NULL, var_commands, countof(var_commands)); qe_register_completion(qs, &variable_completion); qe_register_completion(qs, &value_completion); return 0; } qe_module_init(variables_init);