This week, I changed my newsreader from tin to slrn. I've not totally finished the switch yet, but I've just implemented a useful functionality.

slrn, like in tin, lacks a repeat last search functionality. When you perform a search, you type your search in a prompt. And when you want to repeat the same search, you are prompted again. Last searched string is proposed as a default choice in the prompt, but you still have to validate it, and in the long run, that's quite inconvenient. Unlike tin, it's possible to write macros for slrn, so I could implement this.

Slrn is built around the S-Lang library. This library can be used as an alternative to ncurses, to build text-based user interfaces. Also, It allows embedding a small scripting language. Thanks to that, it's possible to write macros in slrn.

There are 2 modes in slrn: display groups or display articles. In article mode, there are 3 types of search: search by subject, search by author, and search within an article. Each search can be performed backward or forward. In order to implement repeat last search, I had to override each those 6 methods with methods storing searched string and search method before performing the search. Then, I added a new function which could repeat last search.

Currently, it only works in article mode. In group mode here is an api to search within group (group_search), but backward search is not exposed trough the api. So, it was not possible to implement a coherent repeat last search behavior.

Here is the script. To use it, save it in a file named, for example, .slrn.search.sl and in your .slrnrc add following line:

interpret ".slrn.search.sl"

repeat last search functionality is bound to g key. If that doesn't suit you, edit last line of the script.

%%%
% 
% These script allows to repeat last search in slrn
%
%  In slrn, there is no "repeat last search" function. Moreover, there are
%  three types of search in article mode: 
%    - search an article by subject
%    - search an article by author
%    - search within an article
%  Each of these searchs have two variants: search forward and search backward
%
% So, to implement "repeat last search", I needed to overwrite those 6 native
% search functions with custom macros. Those macros replicate the native
% behavior and store last searched string, and last search action. Then, an
% additional macro can check those informations to perform "repeat last search"
% action.
%
% This script bind "repeat last search" action to "g" key (think, "go on"). If
% you want to change that, edit last line of this file.

variable Search_Method = "";
    % suf: subject forward
    % sub: subject backward
    % auf: author forward
    % aub: author backward
    % arf: article forward
    % arb: article backward

variable Search_Str = "";

define do_search(method) {
    variable dir, ret;

    if (method == "") {
        method = Search_Method;
        if (method == "") { % no search has been done yet
            return;
        }
    } else {
        variable prompt;
        if (method == "suf") {
            prompt = "Search on subject: ";
        } else if (method == "sub") {
            prompt = "Search on subject (backward): ";
        } else if (method == "auf") {
            prompt = "Search on author: ";
        } else if (method == "aub") {
            prompt = "Search on author (backward): ";
        } else if (method == "arf") {
            prompt = "Search: ";
        } else if (method == "arb") {
            prompt = "Search (backward): ";
        } else { % should not happen
            error("unknow method " + method);
        }
        Search_Str =  read_mini(prompt, Search_Method, "");
        Search_Method = method;
    }
    if (Search_Str == "") {
        return;
    }

    if (method == "suf" || method == "auf") {
        dir = 1;
    } else if (method == "sub" || method == "aub") {
        dir = -1;
    } else {
        dir = 0;
    }

    % header search methods search from current position; So, we need first to
    % move to next and previous header, search from here, and move back in case
    % nothing was found
    if (dir == 1) {
        !if (header_down(1)) {
            return;
        }
    } else if (dir == -1) {
        !if (header_up(1)) {
        }
    }

    if (method == "suf") {
        ret = re_fsearch_subject(Search_Str);
    }
    if (method == "sub") {
        ret = re_bsearch_subject(Search_Str);
    }
    if (method == "auf") {
        ret = re_bsearch_author(Search_Str);
    }
    if (method == "aub") {
        ret = re_bsearch_author(Search_Str);
    }
    if (method == "arf") {
        ret = re_search_article(Search_Str);
    }
    if (method == "arb") {
        ret = re_bsearch_article(Search_Str);
    }

    if (not ret) {
        if (dir == 1) {
            header_up(1);
        } else if (dir == -1) {
            header_up(1);
        }
        error("Not found.");
    }
}

define my_search_subject_forward() {
    do_search("suf");
}

define my_search_subject_backward() {
    do_search( "sub");
}

define my_search_author_forward() {
    do_search("auf");
}

define my_search_author_backward() {
    do_search("aub");
}

define my_search_article_forward() {
    do_search("arf");
}

define my_search_article_backward() {
    do_search("arb");
}

define my_search_repeat() {
    do_search("");
}

definekey("my_search_subject_forward", "s", "article");
definekey("my_search_subject_backward", "S", "article");
definekey("my_search_author_forward", "a", "article");
definekey("my_search_author_backward", "A", "article");
definekey("my_search_article_forward", "/", "article");
definekey("my_search_article_backward", "?", "article");

definekey("my_search_repeat", "g", "article");