Index: app/helpers/wiki_helper.rb
===================================================================
--- app/helpers/wiki_helper.rb	(revision 3357)
+++ app/helpers/wiki_helper.rb	(working copy)
@@ -66,4 +66,56 @@
     end
     simple_format_without_paragraph(words.join(' '))
   end
+  
+  # Manuel Studer: parse text to find sections and numerate them
+  def parse_sections(text)
+
+    # scan text and replace line
+    section_index = 1
+    doc = Nokogiri::HTML.parse(text,nil,'utf-8')
+
+    doc.xpath('//h1 | //h2 | //h3').each do |node|
+
+      span_node = Nokogiri::XML::Node.new('span',doc)
+      span_node['class'] = 'edit-section'
+      bracket_open = Nokogiri::XML::Text.new('[',doc)
+      bracket_close = Nokogiri::XML::Text.new(']',doc)
+      span_node.add_child(bracket_open)
+
+      link_node = Nokogiri::XML::Node.new('a',doc)
+      #link_node['href'] = "#{@page.title}/edit?section=#{section_index}"
+      # ok, this is not very nice, I know.
+      link_node['href'] = @controller.send(:url_for,{:action => "edit", :page => @page.title}) << "?section=#{section_index}"
+      #link_node['class'] = 'icon icon-edit'
+      link_node.content = l(:button_edit)
+      #link_node['title'] = l(:button_edit) << ": #{node.text.chop.chop}"
+
+      span_node.add_child(link_node)
+      span_node.add_child(bracket_close)
+
+      node.children.first.add_next_sibling(span_node)
+
+      section_index += 1
+    end
+
+    return doc.xpath('//body/.').to_xhtml(:encoding => 'utf8')
+  end
+
+  # Manuel Studer: Collect content between two sections
+  # section start included, section end removed
+  def collect_between(first,last)
+    result = ''
+    until first == last
+      result << first.to_s
+      first = first.next_sibling
+    end
+    result
+  end
+
+  def get_section(html_text,section_number)
+    doc = Nokogiri::HTML.parse(html_text)
+    sections = doc.xpath('//h1 | //h2 | //h3')
+    return collect_between(sections[section_number-1],sections[section_number])
+  end
+
 end
Index: app/helpers/application_helper.rb
===================================================================
--- app/helpers/application_helper.rb	(revision 3357)
+++ app/helpers/application_helper.rb	(working copy)
@@ -402,7 +402,19 @@
     end
     return '' if text.blank?
 
+    textile_text = text
+
     text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
+
+    # Manuel Studer: wiki edit links for sections h1, h2, and h3
+    # small hack, so that the content is being parsed for editing sections if allowed.
+    # Do not apply the hack if there is an "include" which means externally added content from other wikis
+
+    if @content.type == WikiContent && @editable && !textile_text.include?("{{include(")
+      if authorize_for(:wiki, :edit) && @content.version == @page.content.version
+        text = parse_sections(text)
+      end
+    end
     
     only_path = options.delete(:only_path) == false ? false : true
 
Index: app/controllers/application_controller.rb
===================================================================
--- app/controllers/application_controller.rb	(revision 3357)
+++ app/controllers/application_controller.rb	(working copy)
@@ -17,6 +17,7 @@
 
 require 'uri'
 require 'cgi'
+require 'nokogiri'
 
 class ApplicationController < ActionController::Base
   include Redmine::I18n
Index: app/controllers/wiki_controller.rb
===================================================================
--- app/controllers/wiki_controller.rb	(revision 3357)
+++ app/controllers/wiki_controller.rb	(working copy)
@@ -61,32 +61,102 @@
   
   # edit an existing page or a new one
   def edit
-    @page = @wiki.find_or_new_page(params[:page])    
-    return render_403 unless editable?
-    @page.content = WikiContent.new(:page => @page) if @page.new_record?
-    
-    @content = @page.content_for_version(params[:version])
-    @content.text = initial_page_content(@page) if @content.text.blank?
-    # don't keep previous comment
-    @content.comments = nil
-    if request.get?
+    # Manuel Studer: check if it contains a section number as parameter
+    if params[:section]
+      # do special treatment to edit section
+      @page = @wiki.find_or_new_page(params[:page])
+      return render_403 unless editable?
+      @page.content = WikiContent.new(:page => @page) if @page.new_record?
+
+      @content = @page.content_for_version(params[:version])
+      @content.text = initial_page_content(@page) if @content.text.blank?
+      # don't keep previous comment
+      @content.comments = nil
+      @section_id = params[:section]
+      # change text here according to section
+      @content.text = get_textsection(@content.text,@section_id.to_i)
       # To prevent StaleObjectError exception when reverting to a previous version
       @content.version = @page.content.version
-    else
-      if !@page.new_record? && @content.text == params[:content][:text]
+      # Manuel Studer: this is for saving a section which has been edited
+    elsif request.post? && params[:content][:section_edit].to_i != 0
+      section_id = params[:content][:section_edit].to_i
+      new_section_text = params[:content][:text]
+
+      # get the original content from the db
+      @page = @wiki.find_or_new_page(params[:page])
+      return render_403 unless editable?
+      @content = @page.content_for_version(params[:version])
+
+      # don't keep previous comment
+      @content.comments = nil
+
+      text_original = @content.text
+      modify_text_endchars(new_section_text)
+
+      # replace the section
+      rebuilded_text = text_original.sub(get_textsection(text_original, section_id), new_section_text)
+
+      # create anchor so that after saving a page the browser jumps to the
+      # edited section
+      anchor = generate_anchor(rebuilded_text, section_id)
+
+      if !@page.new_record? && @content.text == rebuilded_text
         # don't save if text wasn't changed
-        redirect_to :action => 'index', :id => @project, :page => @page.title
+        redirect_to :action => 'index', :id => @project, :page => @page.title, :anchor => anchor
         return
       end
       #@content.text = params[:content][:text]
       #@content.comments = params[:content][:comments]
-      @content.attributes = params[:content]
+      #@content.attributes = params[:content]
+
+      # manually set the content
+
+      @content.text = rebuilded_text
+      @content.comments = params[:content][:comments]
+      @content.version = params[:content][:version]
       @content.author = User.current
       # if page is new @page.save will also save content, but not if page isn't a new record
       if (@page.new_record? ? @page.save : @content.save)
         call_hook(:controller_wiki_edit_after_save, { :params => params, :page => @page})
-        redirect_to :action => 'index', :id => @project, :page => @page.title
+        #redirect_to :action => 'index', :id => @project, :page => @page.title
+        redirect_to :action => 'index', :id => @project, :page => @page.title, :anchor => anchor
       end
+
+      # this is invoked if no section has been chosen
+    else
+      # Manuel Studer: section id 0 means no section has been chosen, this means the whole
+      # page is selected
+      @section_id = 0
+
+      @page = @wiki.find_or_new_page(params[:page])
+      return render_403 unless editable?
+      @page.content = WikiContent.new(:page => @page) if @page.new_record?
+
+      @content = @page.content_for_version(params[:version])
+      @content.text = initial_page_content(@page) if @content.text.blank?
+      # don't keep previous comment
+      @content.comments = nil
+      if request.get?
+        # To prevent StaleObjectError exception when reverting to a previous version
+        @content.version = @page.content.version
+      else
+        if !@page.new_record? && @content.text == params[:content][:text]
+          # don't save if text wasn't changed
+          redirect_to :action => 'index', :id => @project, :page => @page.title
+          return
+        end
+        # Manuel Studer: manually set the content
+        #@content.attributes = params[:content]
+        @content.text = params[:content][:text]
+        @content.comments = params[:content][:comments]
+        @content.version = params[:content][:version]
+
+        @content.author = User.current
+        # if page is new @page.save will also save content, but not if page isn't a new record
+        if (@page.new_record? ? @page.save : @content.save)
+          redirect_to :action => 'index', :id => @project, :page => @page.title
+        end
+      end
     end
   rescue ActiveRecord::StaleObjectError
     # Optimistic locking exception
@@ -234,4 +304,103 @@
     extend helper unless self.instance_of?(helper)
     helper.instance_method(:initial_page_content).bind(self).call(page)
   end
+
+  
+  # Manuel Studer: get the text of a given section
+  def get_textsection(textile_text, section_number)
+    current_section_number = 1
+    offset = 0
+    current_index = 0
+
+    # the index where the found section starts
+    start_section_index = 0
+    # the level of the found section
+    section_level = 0
+
+    while !current_index.nil? do
+      current_index =  textile_text.index(/h[1|2|3]\. ./i,offset)
+      if !current_index.nil? then
+
+        # we have found our start section
+        if current_section_number == section_number
+          start_section_index = current_index
+          section_level = textile_text[current_index+1..current_index+1].to_i
+        end
+
+        # now find the next section which has a smaller OR equal level than
+        # the found section
+        if current_section_number > section_number
+          current_section_level = textile_text[current_index+1..current_index+1].to_i
+          if current_section_level <= section_level
+            return textile_text[start_section_index..current_index-1]
+          end
+        end
+        offset = current_index + 3
+        current_section_number += 1
+      end
+    end
+    # return all up to the final char if it was the last h tag
+    return textile_text[start_section_index..textile_text.size-1]
+  end
+
+
+  # Manuel Studer: check if there is whole empty line at the end: which means
+  # 2x enter key: one enter key is 13 (\r) + 10 (\n)
+  def modify_text_endchars(text)
+    nst_last_char = text[text.size-1]
+    nst_2last_char = text[text.size-2]
+    nst_3last_char = text[text.size-3]
+    nst_4last_char = text[text.size-4]
+
+    if (nst_last_char == 10 && nst_2last_char == 13) &&
+        (nst_3last_char == 10 && nst_4last_char == 13)
+      # all ok do nothing
+    elsif nst_last_char == 10 && nst_2last_char == 13
+      # add another one
+      text << "\r\n"
+    else
+      text << "\r\n\r\n"
+    end
+    return text
+  end
+
+
+  # generate the anchor from a given section
+  def generate_anchor(textile_text, section_number)
+    current_section_number = 1
+    offset = 0
+    current_index = 0
+
+    anchor = ''
+
+    while !current_index.nil? do
+      current_index =  textile_text.index(/h[1|2|3]\. ./i,offset)
+      if !current_index.nil? then
+
+        # we have found our start section
+        if current_section_number == section_number
+          # find index of next newline
+          index_next_newline = textile_text.index("\n",current_index)
+
+          if !index_next_newline.nil? then
+            anchor = textile_text[current_index+3..index_next_newline].strip
+            # replaces non word caracters by dashes
+            anchor = anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
+            return anchor
+          else
+            anchor = textile_text[current_index+3..textile_text.size-1].strip
+            # replaces non word caracters by dashes
+            anchor = anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
+            return anchor
+          end
+
+        end
+        offset = current_index + 3
+        current_section_number += 1
+
+      else return anchor
+      end
+    end
+  end
 end
+
Index: app/views/wiki/edit.rhtml
===================================================================
--- app/views/wiki/edit.rhtml	(revision 3357)
+++ app/views/wiki/edit.rhtml	(working copy)
@@ -2,6 +2,7 @@
 
 <% form_for :content, @content, :url => {:action => 'edit', :page => @page.title}, :html => {:id => 'wiki_form'} do |f| %>
 <%= f.hidden_field :version %>
+<%= f.hidden_field :section_edit,:value => @section_id%>
 <%= error_messages_for 'content' %>
 
 <p><%= f.text_area :text, :cols => 100, :rows => 25, :class => 'wiki-edit', :accesskey => accesskey(:edit) %></p>
Index: public/stylesheets/application.css
===================================================================
--- public/stylesheets/application.css	(revision 3357)
+++ public/stylesheets/application.css	(working copy)
@@ -670,8 +670,10 @@
 }
 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
 
-a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
-a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
+/* a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
+ * a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
+ */
+a.wiki-anchor {color:white; margin-left: 6px; text-decoration: none; }
 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
 
 div.wiki img { vertical-align: middle; } 
@@ -708,6 +710,12 @@
 background-image:url('../images/close_hl.png');
 }
 
+span.edit-section{
+    margin-left: 5px;
+    font-size:11px;
+    font-weight: normal;
+}
+
 /***** Gantt chart *****/
 .gantt_hdr {
   position:absolute;
