whole_func_string: function(walk) {
      if (!walk.at('@ '))
        return false;
      // Find the end of the block, checking for adjacent blocks
      var startIdx = walk.position;
      var found = false;
      while (!found && walk.hasCurrent()) {
        walk.scrollToTerm().skipBlankLines();
        if (walk.atSpaces(this.blockIndent)) {
          var i = walk.position;
          walk.skipWhitespaces();
          if (!walk.at('@') && !walk.at('*')) {
            found = true;
            walk.startFrom(i);
          }
        } else found = true;
      }
      var g = this.stripSelector(new SubWalker(walk, startIdx, walk.position));
      this.emitAssociativeGroup(g);
      return true;
    }
func_code_string: function(walk) {
      if (!walk.at('@ '))
        return false;
      // Find the end of the block, checking for adjacent blocks
      var startIdx = walk.position;
      var found = false;
      while (!found && walk.hasCurrent()) {
        walk.scrollToTerm().skipBlankLines();
        if (walk.atSpaces(this.blockIndent)) {
          var i = walk.position;
          walk.skipWhitespaces();
          if (!walk.at('@') && !walk.at('*')) {
            found = true;
            walk.startFrom(i);
          }
        } else found = true;
      }
      var g = this.stripSelector(new SubWalker(walk, startIdx, walk.position));
      this.emitAssociativeGroup(g);
      return true;
    }
func_documentation_string: Associative groups enable users to drag select items into correct categories.
  
  The exact dragging functionality must be implemented with client-side scripts.
  
  Each category starts with marker `@ ` and must contain one or more select items,
  which start with marker `* ` and are indented beyond category marker.
  
  Example:
  
  ```
  @ Category 1
  
  * one
  * two
  * three
  
  @ Category 2
  
  * five
  * six
  
  ```