diff --git a/CMakeLists.txt b/CMakeLists.txt index 950b6b94..60450c72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,122 +1,122 @@ cmake_minimum_required(VERSION 3.0) -set(KF5_VERSION "5.41.0") # handled by release scripts -set(KF5_DEP_VERSION "5.40.0") # handled by release scripts +set(KF5_VERSION "5.42.0") # handled by release scripts +set(KF5_DEP_VERSION "5.41.0") # handled by release scripts project(KTextEditor VERSION ${KF5_VERSION}) # ECM setup include(FeatureSummary) -find_package(ECM 5.40.0 NO_MODULE) +find_package(ECM 5.41.0 NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) # add own modules to search path, too set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CMakePackageConfigHelpers) include(CheckFunctionExists) include(CheckSymbolExists) include(KDEInstallDirs) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) include(KDEPackageAppTemplates) include(GenerateExportHeader) include(ECMAddQch) option(BUILD_QCH "Build API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)" OFF) add_feature_info(QCH ${BUILD_QCH} "API documentation in QCH format (for e.g. Qt Assistant, Qt Creator & KDevelop)") ecm_setup_version( PROJECT VARIABLE_PREFIX KTEXTEDITOR VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" SOVERSION 5 ) # Dependencies set(REQUIRED_QT_VERSION 5.7.0) # Required Qt5 components to build this framework find_package(Qt5 ${REQUIRED_QT_VERSION} NO_MODULE REQUIRED Core Widgets Qml PrintSupport Xml XmlPatterns) find_package(KF5Archive ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5GuiAddons ${KF5_DEP_VERSION} REQUIRED) find_package(KF5I18n ${KF5_DEP_VERSION} REQUIRED) find_package(KF5KIO ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Parts ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Sonnet ${KF5_DEP_VERSION} REQUIRED) find_package(KF5IconThemes ${KF5_DEP_VERSION} REQUIRED) find_package(KF5SyntaxHighlighting ${KF5_DEP_VERSION} REQUIRED) # libgit2 integration, at least 0.22 with proper git_libgit2_init() find_package(LibGit2 "0.22.0") # EditorConfig integration find_package(EditorConfig) # vi mode on per default option (BUILD_VIMODE "Build vimode in" ON) # Subdirectories add_definitions(-DTRANSLATION_DOMAIN=\"ktexteditor5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(autotests) endif() add_subdirectory(templates) # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF5TextEditor") if (BUILD_QCH) ecm_install_qch_export( TARGETS KF5TextEditor_QCH FILE KF5TextEditorQchTargets.cmake DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KF5TextEditorQchTargets.cmake\")") endif() configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KF5TextEditorConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5TextEditorConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_DIR}" COMPONENT Devel ) install(EXPORT KF5TextEditorTargets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE KF5TextEditorTargets.cmake NAMESPACE KF5:: ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ktexteditor_version.h" DESTINATION "${KDE_INSTALL_INCLUDEDIR_KF5}" COMPONENT Devel ) # config.h check_symbol_exists (fdatasync unistd.h HAVE_FDATASYNC) configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) # let our config.h be found first in any case include_directories (BEFORE ${CMAKE_CURRENT_BINARY_DIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/autotests/input/syntax/c/results/preprocessor-bug363280.c.reference.html b/autotests/input/syntax/c/results/preprocessor-bug363280.c.reference.html index a83fecc9..37a07109 100644 --- a/autotests/input/syntax/c/results/preprocessor-bug363280.c.reference.html +++ b/autotests/input/syntax/c/results/preprocessor-bug363280.c.reference.html @@ -1,22 +1,22 @@ preprocessor-bug363280.c -
+
 #if 1
 int x; // variable shall not be grey
 #endif
 #if defined (A)
 int y; // variable shall not be grey
 #elif defined (B)
 int z; // variable shall not be grey
 #endif
 
diff --git a/autotests/input/syntax/carto-css/results/test.mss.reference.html b/autotests/input/syntax/carto-css/results/test.mss.reference.html index 0902fddf..e7601b9e 100644 --- a/autotests/input/syntax/carto-css/results/test.mss.reference.html +++ b/autotests/input/syntax/carto-css/results/test.mss.reference.html @@ -1,229 +1,229 @@ test.mss -
+
 /* kate: hl CartoCSS
    This file contains some content coming from
    https://github.com/gravitystorm/openstreetmap-carto
    with CC0 license. This file is just for testing
    katepart highlighting engine.
    */
 
 #world {
 // this syntax
 polygon-opacity: 50%;
 
 // is equivalent to
 polygon-opacity: 0.5;
 }
 
 @admin-boundaries: #ac46ac;
 
 /* For performance reasons, the admin border layers are split into three groups
 for low, middle and high zoom levels.
 For each zoomlevel, all borders come from a single attachment, to handle
 overlapping borders correctly.
 */
 
 #admin-low-zoom[zoom < 11],  // test
 #admin-mid-zoom[zoom >= 11][zoom < 13],
 #admin-high-zoom[zoom >= 13] {
   [admin_level = '2'],
   [admin_level = '3'] {
     [zoom >= 4] {
       background/line-color: white;
       background/line-width: 0.6;
       line-color: @admin-boundaries;
       line-width: 0.6;
     }
     [zoom >= 7] {
       background/line-width: 2;
       line-width: 2;
     }
     [zoom >= 10] {
       [admin_level = '2'] {
         background/line-width: 6;
         line-width: 6;
       }
       [admin_level = '3'] {
         background/line-width: 5;
         line-width: 5;
         line-dasharray: 4,2;
         line-clip: false;
       }
     }
   }
   [admin_level = '4'] {
     [zoom >= 4] {
       background/line-color: white;
       background/line-width: 0.6;
       line-color: @admin-boundaries;
       line-width: 0.6;
       line-dasharray: 4,3;
       line-clip: false;
     }
     [zoom >= 7] {
       background/line-width: 1;
       line-width: 1;
     }
     [zoom >= 11] {
       background/line-width: 3;
       line-width: 3;
     }
   }
   /*
   The following code prevents admin boundaries from being rendered on top of
   each other. Comp-op works on the entire attachment, not on the individual
   border. Therefore, this code generates an attachment containing a set of
   @admin-boundaries/white dashed lines (of which only the top one is visible),
   and with `comp-op: darken` the white part is ignored, while the
   @admin-boundaries colored part is rendered (as long as the background is not
   darker than @admin-boundaries).
   The SQL has `ORDER BY admin_level`, so the boundary with the lowest
   admin_level is rendered on top, and therefore the only visible boundary.
   */
   opacity: 0.4;
   comp-op: darken;
 }
 
 #admin-mid-zoom[zoom >= 11][zoom < 13],
 #admin-high-zoom[zoom >= 13] {
   [admin_level = '5'][zoom >= 11] {
     background/line-color: white;
     background/line-width: 2;
     line-color: @admin-boundaries;
     line-width: 2;
     line-dasharray: 6,3,2,3,2,3;
     line-clip: false;
   }
   [admin_level = '6'][zoom >= 11] {
     background/line-color: white;
     background/line-width: 2;
     line-color: @admin-boundaries;
     line-width: 2;
     line-dasharray: 6,3,2,3;
     line-clip: false;
   }
   [admin_level = '7'],
   [admin_level = '8'] {
     [zoom >= 12] {
       background/line-color: white;
       background/line-width: 1.5;
       line-color: @admin-boundaries;
       line-width: 1.5;
       line-dasharray: 5,2;
       line-clip: false;
     }
   }
   opacity: 0.5;
   comp-op: darken;
 }
 
 #admin-high-zoom[zoom >= 13] {
   [admin_level = '9'],
   [admin_level = '10'] {
     [zoom >= 13] {
       background/line-color: white;
       background/line-width: 2;
       line-color: @admin-boundaries;
       line-width: 2;
       line-dasharray: 2,3;
       line-clip: false;
     }
   }
   opacity: 0.5;
   comp-op: darken;
 }
 
 
 
 #nature-reserve-boundaries {
   [way_pixels > 100][zoom >= 7] {
     [zoom < 10] {
       ::fill {
         opacity: 0.05;
         polygon-fill: green;
       }
     }
     a/line-width: 1;
     a/line-offset: -0.5;
     a/line-color: green;
     a/line-opacity: 0.15;
     a/line-join: round;
     a/line-cap: round;
     b/line-width: 2;
     b/line-offset: -1;
     b/line-color: green;
     b/line-opacity: 0.15;
     b/line-join: round;
     b/line-cap: round;
     [zoom >= 10] {
       a/line-width: 2;
       a/line-offset: -1;
       b/line-width: 4;
       b/line-offset: -2;
     }
     [zoom >= 14] {
       b/line-width: 6;
       b/line-offset: -3;
     }
   }
 }
 
 #building-text {
 [zoom >= 14][way_pixels > 3000],
 [zoom >= 17] {
 text-name: "[name]";
 text-size: 11;
 text-fill: #444;
 text-face-name: @book-fonts;
 text-halo-radius: 1;
 text-wrap-width: 20;
 text-halo-fill: rgba(255,255,255,0.5);
 text-placement: interior;
 }
 }
 
 @marina-text: #576ddf; // also swimming_pool
 @landcover-face-name: @oblique-fonts;
 @standard-wrap-width: 30;
 
 .points {
   [feature = 'tourism_alpine_hut'][zoom >= 13] {
     point-file: url('symbols/alpinehut.p.16.png');
     point-placement: interior;
   }
   }
 
   [feature = 'highway_bus_stop'] {
     [zoom >= 16] {
       marker-file: url('symbols/square.svg');
       marker-fill: @transportation-icon;
       marker-placement: interior;
       marker-width: 6;
     }
     [zoom >= 17] {
       marker-file: url('symbols/bus_stop.p.12.png');
       marker-width: 12;
     }
   }
 
 [feature = 'highway_primary'] {
 [zoom >= 7][zoom < 12] {
 line-width: 0.5;
 line-color: @primary-fill;
 [zoom >= 9] { line-width: 1.2; }
 [zoom >= 10] { line-width: 2; }
 [zoom >= 11] { line-width: .5; }
 }
 }
 
diff --git a/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html b/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html index 5c5e31de..9b25c373 100644 --- a/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html +++ b/autotests/input/syntax/cpp/results/preprocessor-bug363280.cpp.reference.html @@ -1,22 +1,22 @@ preprocessor-bug363280.cpp -
+
 #if 1
 int x; // variable shall not be grey
 #endif
 #if defined (A)
 int y; // variable shall not be grey
 #elif defined (B)
 int z; // variable shall not be grey
 #endif
 
diff --git a/autotests/input/syntax/dockerfile/results/Dockerfile.reference.html b/autotests/input/syntax/dockerfile/results/Dockerfile.reference.html index 712faa7b..24cbcce8 100644 --- a/autotests/input/syntax/dockerfile/results/Dockerfile.reference.html +++ b/autotests/input/syntax/dockerfile/results/Dockerfile.reference.html @@ -1,33 +1,33 @@ Dockerfile -
+
 # LGPLv2+ example file
 
 # This is a comment
 FROM ubuntu:14.04
 MAINTAINER James Turnbull <james@example.com> # comment
 ENV REFRESHED_AT 2014-06-01
 
 RUN apt-get -yqq update
 RUN apt-get install -yqq software-properties-common python-software-properties
 RUN add-apt-repository ppa:chris-lea/redis-server
 RUN apt-get -yqq update
 RUN apt-get -yqq install redis-server redis-tools
 RUN apt-get -yqq update # comment
 
 VOLUME [ "/var/lib/redis", "/var/log/redis/" ]
 
 EXPOSE 6379
 
 CMD []
 
diff --git a/autotests/input/syntax/elixir/results/hello.exs.reference.html b/autotests/input/syntax/elixir/results/hello.exs.reference.html index 7d011b0a..ddab4c03 100644 --- a/autotests/input/syntax/elixir/results/hello.exs.reference.html +++ b/autotests/input/syntax/elixir/results/hello.exs.reference.html @@ -1,25 +1,25 @@ hello.exs -
+
 parent = self()
 
 # Spawns an Elixir process (not an operating system one!)
 spawn_link(fn ->
   send parent, {:msg, "hello world"}
 end)
 
 # Block until the message is received
 receive do
   {:msg, contents} -> IO.puts contents
 end
 
diff --git a/autotests/input/syntax/elixir/results/learnelixir.exs.reference.html b/autotests/input/syntax/elixir/results/learnelixir.exs.reference.html index 8e3329ac..97e3ccec 100644 --- a/autotests/input/syntax/elixir/results/learnelixir.exs.reference.html +++ b/autotests/input/syntax/elixir/results/learnelixir.exs.reference.html @@ -1,411 +1,411 @@ learnelixir.exs -
+
 # Original: https://learnxinyminutes.com/docs/elixir/
 
 # Single line comments start with a number symbol.
 
 # There's no multi-line comment,
 # but you can stack multiple comments.
 
 # To use the elixir shell use the `iex` command.
 # Compile your modules with the `elixirc` command.
 
 # Both should be in your path if you installed elixir correctly.
 
 ## ---------------------------
 ## -- Basic types
 ## ---------------------------
 
 # There are numbers
 3    # integer
 0x1F # integer
 3.0  # float
 
 # Atoms, that are literals, a constant with name. They start with `:`.
 :hello # atom
 
 # Tuples that are stored contiguously in memory.
 {1,2,3} # tuple
 
 # We can access a tuple element with the `elem` function:
 elem({1, 2, 3}, 0) #=> 1
 
 # Lists that are implemented as linked lists.
 [1,2,3] # list
 
 # We can access the head and tail of a list as follows:
 [head | tail] = [1,2,3]
 head #=> 1
 tail #=> [2,3]
 
 # In elixir, just like in Erlang, the `=` denotes pattern matching and
 # not an assignment.
 #
 # This means that the left-hand side (pattern) is matched against a
 # right-hand side.
 #
 # This is how the above example of accessing the head and tail of a list works.
 
 # A pattern match will error when the sides don't match, in this example
 # the tuples have different sizes.
 # {a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2}
 
 # There are also binaries
 <<1,2,3>> # binary
 
 # Strings and char lists
 "hello" # string
 'hello' # char list
 
 # Multi-line strings
 """
 I'm a multi-line
 string.
 """
 #=> "I'm a multi-line\nstring.\n"
 
 # Strings are all encoded in UTF-8:
 "héllò" #=> "héllò"
 
 # Strings are really just binaries, and char lists are just lists.
 <<?a, ?b, ?c>> #=> "abc"
 [?a, ?b, ?c]   #=> 'abc'
 
 # `?a` in elixir returns the ASCII integer for the letter `a`
 ?a #=> 97
 
 # To concatenate lists use `++`, for binaries use `<>`
 [1,2,3] ++ [4,5]     #=> [1,2,3,4,5]
 'hello ' ++ 'world'  #=> 'hello world'
 
 <<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>
 "hello " <> "world"  #=> "hello world"
 
 # Ranges are represented as `start..end` (both inclusive)
 1..10 #=> 1..10
 lower..upper = 1..10 # Can use pattern matching on ranges as well
 [lower, upper] #=> [1, 10]
 
 ## ---------------------------
 ## -- Operators
 ## ---------------------------
 
 # Some math
 1 + 1  #=> 2
 10 - 5 #=> 5
 5 * 2  #=> 10
 10 / 2 #=> 5.0
 
 # In elixir the operator `/` always returns a float.
 
 # To do integer division use `div`
 div(10, 2) #=> 5
 
 # To get the division remainder use `rem`
 rem(10, 3) #=> 1
 
 # There are also boolean operators: `or`, `and` and `not`.
 # These operators expect a boolean as their first argument.
 true and true #=> true
 false or true #=> true
 # 1 and true    #=> ** (ArgumentError) argument error
 
 # Elixir also provides `||`, `&&` and `!` which accept arguments of any type.
 # All values except `false` and `nil` will evaluate to true.
 1 || true  #=> 1
 false && 1 #=> false
 nil && 20  #=> nil
 !true #=> false
 
 # For comparisons we have: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<` and `>`
 1 == 1 #=> true
 1 != 1 #=> false
 1 < 2  #=> true
 
 # `===` and `!==` are more strict when comparing integers and floats:
 1 == 1.0  #=> true
 1 === 1.0 #=> false
 
 # We can also compare two different data types:
 1 < :hello #=> true
 
 # The overall sorting order is defined below:
 # number < atom < reference < functions < port < pid < tuple < list < bit string
 
 # To quote Joe Armstrong on this: "The actual order is not important,
 # but that a total ordering is well defined is important."
 
 ## ---------------------------
 ## -- Control Flow
 ## ---------------------------
 
 # `if` expression
 if false do
   "This will never be seen"
 else
   "This will"
 end
 
 # There's also `unless`
 unless true do
   "This will never be seen"
 else
   "This will"
 end
 
 # Remember pattern matching? Many control-flow structures in elixir rely on it.
 
 # `case` allows us to compare a value against many patterns:
 case {:one, :two} do
   {:four, :five} ->
     "This won't match"
   {:one, x} ->
     "This will match and bind `x` to `:two`"
   _ ->
     "This will match any value"
 end
 
 # It's common to bind the value to `_` if we don't need it.
 # For example, if only the head of a list matters to us:
 [head | _] = [1,2,3]
 head #=> 1
 
 # For better readability we can do the following:
 [head | _tail] = [:a, :b, :c]
 head #=> :a
 
 # `cond` lets us check for many conditions at the same time.
 # Use `cond` instead of nesting many `if` expressions.
 cond do
   1 + 1 == 3 ->
     "I will never be seen"
   2 * 5 == 12 ->
     "Me neither"
   1 + 2 == 3 ->
     "But I will"
 end
 
 # It is common to set the last condition equal to `true`, which will always match.
 cond do
   1 + 1 == 3 ->
     "I will never be seen"
   2 * 5 == 12 ->
     "Me neither"
   true ->
     "But I will (this is essentially an else)"
 end
 
 # `try/catch` is used to catch values that are thrown, it also supports an
 # `after` clause that is invoked whether or not a value is caught.
 try do
   throw(:hello)
 catch
   message -> "Got #{message}."
 after
   IO.puts("I'm the after clause.")
 end
 #=> I'm the after clause
 # "Got :hello"
 
 ## ---------------------------
 ## -- Modules and Functions
 ## ---------------------------
 
 # Anonymous functions (notice the dot)
 square = fn(x) -> x * x end
 square.(5) #=> 25
 
 # They also accept many clauses and guards.
 # Guards let you fine tune pattern matching,
 # they are indicated by the `when` keyword:
 f = fn
   x, y when x > 0 -> x + y
   x, y -> x * y
 end
 
 f.(1, 3)  #=> 4
 f.(-1, 3) #=> -3
 
 # Elixir also provides many built-in functions.
 # These are available in the current scope.
 is_number(10)    #=> true
 is_list("hello") #=> false
 elem({1,2,3}, 0) #=> 1
 
 # You can group several functions into a module. Inside a module use `def`
 # to define your functions.
 defmodule Math do
   def sum(a, b) do
     a + b
   end
 
   def square(x) do
     x * x
   end
 end
 
 Math.sum(1, 2)  #=> 3
 Math.square(3) #=> 9
 
 # To compile our simple Math module save it as `math.ex` and use `elixirc`
 # in your terminal: elixirc math.ex
 
 # Inside a module we can define functions with `def` and private functions with `defp`.
 # A function defined with `def` is available to be invoked from other modules,
 # a private function can only be invoked locally.
 defmodule PrivateMath do
   def sum(a, b) do
     do_sum(a, b)
   end
 
   defp do_sum(a, b) do
     a + b
   end
 end
 
 PrivateMath.sum(1, 2)    #=> 3
 # PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)
 
 # Function declarations also support guards and multiple clauses:
 defmodule Geometry do
   def area({:rectangle, w, h}) do
     w * h
   end
 
   def area({:circle, r}) when is_number(r) do
     3.14 * r * r
   end
 end
 
 Geometry.area({:rectangle, 2, 3}) #=> 6
 Geometry.area({:circle, 3})       #=> 28.25999999999999801048
 # Geometry.area({:circle, "not_a_number"})
 #=> ** (FunctionClauseError) no function clause matching in Geometry.area/1
 
 # Due to immutability, recursion is a big part of elixir
 defmodule Recursion do
   def sum_list([head | tail], acc) do
     sum_list(tail, acc + head)
   end
 
   def sum_list([], acc) do
     acc
   end
 end
 
 Recursion.sum_list([1,2,3], 0) #=> 6
 
 # Elixir modules support attributes, there are built-in attributes and you
 # may also add custom ones.
 defmodule MyMod do
   @moduledoc """
   This is a built-in attribute on a example module.
   """
 
   @my_data 100 # This is a custom attribute.
   IO.inspect(@my_data) #=> 100
 end
 
 ## ---------------------------
 ## -- Structs and Exceptions
 ## ---------------------------
 
 # Structs are extensions on top of maps that bring default values,
 # compile-time guarantees and polymorphism into Elixir.
 defmodule Person do
   defstruct name: nil, age: 0, height: 0
 end
 
 joe_info = %Person{ name: "Joe", age: 30, height: 180 }
 #=> %Person{age: 30, height: 180, name: "Joe"}
 
 # Access the value of name
 joe_info.name #=> "Joe"
 
 # Update the value of age
 older_joe_info = %{ joe_info | age: 31 }
 #=> %Person{age: 31, height: 180, name: "Joe"}
 
 # The `try` block with the `rescue` keyword is used to handle exceptions
 try do
   raise "some error"
 rescue
   RuntimeError -> "rescued a runtime error"
   _error -> "this will rescue any error"
 end
 #=> "rescued a runtime error"
 
 # All exceptions have a message
 try do
   raise "some error"
 rescue
   x in [RuntimeError] ->
     x.message
 end
 #=> "some error"
 
 ## ---------------------------
 ## -- Concurrency
 ## ---------------------------
 
 # Elixir relies on the actor model for concurrency. All we need to write
 # concurrent programs in elixir are three primitives: spawning processes,
 # sending messages and receiving messages.
 
 # To start a new process we use the `spawn` function, which takes a function
 # as argument.
 f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>
 spawn(f) #=> #PID<0.40.0>
 
 # `spawn` returns a pid (process identifier), you can use this pid to send
 # messages to the process. To do message passing we use the `send` operator.
 # For all of this to be useful we need to be able to receive messages. This is
 # achieved with the `receive` mechanism:
 
 # The `receive do` block is used to listen for messages and process
 # them when they are received. A `receive do` block will only
 # process one received message. In order to process multiple
 # messages, a function with a `receive do` block must recursively
 # call itself to get into the `receive do` block again.
 
 defmodule Geometry do
   def area_loop do
     receive do
       {:rectangle, w, h} ->
         IO.puts("Area = #{w * h}")
         area_loop()
       {:circle, r} ->
         IO.puts("Area = #{3.14 * r * r}")
         area_loop()
     end
   end
 end
 
 # Compile the module and create a process that evaluates `area_loop` in the shell
 pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0>
 # Alternatively
 pid = spawn(Geometry, :area_loop, [])
 
 # Send a message to `pid` that will match a pattern in the receive statement
 send pid, {:rectangle, 2, 3}
 #=> Area = 6
 #   {:rectangle,2,3}
 
 send pid, {:circle, 2}
 #=> Area = 12.56000000000000049738
 #   {:circle,2}
 
 # The shell is also a process, you can use `self` to get the current pid
 self() #=> #PID<0.27.0>
 
diff --git a/autotests/input/syntax/j/results/test.ijs.reference.html b/autotests/input/syntax/j/results/test.ijs.reference.html index 2954cd67..c766d44b 100644 --- a/autotests/input/syntax/j/results/test.ijs.reference.html +++ b/autotests/input/syntax/j/results/test.ijs.reference.html @@ -1,75 +1,75 @@ test.ijs -
+
 Comment
 NB. This is a single line comment, check regions overlapping priority: 1 2.3  +/ &. 'quoted text'
 NB.(
 NB. This is a foldable multi line comment
 NB.)
 
 String literal
 ''
 ''''
 'This is quoted text'
 'And this '' and this NB. and this' 'Yet another quoted text'
 
 Numeric literal
 12 34.56 _7.89 1e2 1.2e3 _. _ __ 123j_456 2b111.111 1r23 123456x 1.2e3j4.5e6 _j_ __j_ _.j_. _j3p4j_.
 
 Parenthesis
 ( )
 
 Adverb
 ~ / \ /. \. } b. f. M. t. t:
 
 Conjuction
 ^: . .. .: :  :. :: ;. !. !: " ` `: @ @. @: & &. &: &.: d. D. D: H. L: S: T.
 
 Control
 assert. break. case. catch. catchd. catcht. continue. do. else. elseif. end. fcase. for. for_abc. goto_abc. if. label_abc. return. select. throw. try. while. whilst.
 
 Copula
 a =: 123
 (a) =: 123
 (a;b;c) =: 123
 'a b c' =: 123
 'a b c' =. '123'
 '`a b c' =. '123'
 'a' =: 123
 ('a';'b';'c') =: 123
 
 Explicit argument
 x y m n u v  x. y. m. n. u. v.
 
 Noun
 a. a:
 
 Verb
 = < <. <: > >. >: _: + +. +: * *. *: - -. -: % %. %: ^ ^. $ $. $: ~. ~: | |. |: , ,. ,: ; ;: # #. #: ! /: \: [ [: ] { {. {: {:: }. }: ". ": ? ?. A. C. e. E. i. i: I. j. L. o. p. p.. p: q: r. s: u: x: _9: _8: _7: _6: _5: _4: _3: _2: _1: 0: 1: 2: 3: 4: 5: 6: 7: 8: 9:
 
 Combined
 for_abc. i. 5 do. 55 return. end.
 a=. b =: 123 +/ # i. 10
 123 'qwerty'
 a_b =: '123' [ c__d =. 4 : 0  NB. foldable definition begins
   x +/ y
 )
 e__12 =: define  NB. foldable definition begins
   if. x do.  NB. inner foldable region begins
     y=. y + x  NB. comment after code
     x +/ y
   else.  NB. inner foldable region begins
     y +/ y
   end.
 )
 
diff --git a/autotests/input/syntax/meson/results/meson.build.reference.html b/autotests/input/syntax/meson/results/meson.build.reference.html index 759ba53b..b79afe26 100644 --- a/autotests/input/syntax/meson/results/meson.build.reference.html +++ b/autotests/input/syntax/meson/results/meson.build.reference.html @@ -1,35 +1,35 @@ meson.build -
+
 # Unit test for Meson syntax highlight. License: LGPL
 project('projectname', 'cpp')
 
 
 
 sourcefiles = ['a.cpp', 'b.cpp']
 
 foreach sourcefile : sourcefiles
     message('this is a source file: ' + sourcefile)
 endforeach
 
 x=1
 if x+1 == 2 and x-1 == 0
     message('I can work in this universe!')
 endif
 
 subprojectresult = subproject('mysubprojectdir')
 
 mysharedlib = shared_library('libraryname', sourcefiles, linkwith: subprojectresult.staticlib)
 
 executable('myprogram', ['test.cpp'], linkwith: mysharedlib)
 
diff --git a/autotests/input/syntax/rmarkdown/results/example.rmd.reference.html b/autotests/input/syntax/rmarkdown/results/example.rmd.reference.html index ced6208c..e1b8e564 100644 --- a/autotests/input/syntax/rmarkdown/results/example.rmd.reference.html +++ b/autotests/input/syntax/rmarkdown/results/example.rmd.reference.html @@ -1,53 +1,53 @@ example.rmd -
+
 ---
 title: "test"
 author: "me"
 date: "07.10.2014"
 output: html_document
 ---
 
 This is a simple test document. It shows syntax highlighting switches between YAML (above), R blocks
 ```{r, echo=FALSE}
 for (i in 1:10) {
   if(i>=10) print(i)
 }
 # two blank lines below
 
 sessionInfo()
 ```
 
 LaTeX equations,
 $$
 h_{i}(t \mid q,C) = h_{0}(t) e^{\beta_{1}quality_{i} + \beta_{2}C_{iq}}
 $$
 
 and Markdown. A [link](http://example.com) in Markdown.
 
 Inline `r y <- 5 + x - sin(3)` R code.
 Inline `y <- 5 + x - sin(3)` code.
 
 
  Heading
  =======
  
  Sub-heading
  -----------
  A list of editors:
    * kate
    * vim
    * emacs
 
 *italic*, **bold**, `monospace`
 
diff --git a/autotests/input/syntax/sql-oracle/results/test_syntax.sql.reference.html b/autotests/input/syntax/sql-oracle/results/test_syntax.sql.reference.html index 155a976a..d75a924c 100644 --- a/autotests/input/syntax/sql-oracle/results/test_syntax.sql.reference.html +++ b/autotests/input/syntax/sql-oracle/results/test_syntax.sql.reference.html @@ -1,54 +1,54 @@ test_syntax.sql -
+
 -- kate: hl SQL (Oracle)
 -- test case shipped with highlighting in bugzilla, LGPL
 
 begin
 	q'{adfasdf'sadfasdf j}'{' hkjhkhkjhkjh khlkhklj'fghdfghdfgh'hkjh'jkhkh'a'
 	Q'(asldflahsl;'dkjfhklsdfh)'
 	q'[asdasd;'asdasd'a]sd'asd'asasd]';11111111[1']; asdasdasdasd'errrrrrrrrrrrrrr-p
 
 	q'agdfgsdfgfhfghjfgh'f'f'sdfg'sdfg'dfg#a' dafdfadasfasdf;
 
 	Q'#gdfgsdfgsdfgsdfgsdfg#' afgasasdfasdfasfasdfasdfasdfasdfsdf
 
 	if sldfjsdj then
 		case
 			when 1=1 then
 				aslfjsldkfj;
 			when 2=2 then
 				asdfg;
 			else
 				null;
 		end case;
 		
 		if sdfjh then
 			for i in 1 .. 2
 			LOOP
 				dbms_output.put_line(q';ololo;');
 			END Loop;
 			
 			while true
 			loop
 				dbms_output.put_line('1111');
 			end loop;
 		end if;
 		ksjfklasjd;
 		fklj;
 	elsif
 		sdklfjsdklfj;
 	else
 		sdfdfsdf;
 	end if;
 end;
diff --git a/autotests/input/syntax/verilog/results/or1200_dc_fsm.v.reference.html b/autotests/input/syntax/verilog/results/or1200_dc_fsm.v.reference.html index 5c15f923..25f2053e 100644 --- a/autotests/input/syntax/verilog/results/or1200_dc_fsm.v.reference.html +++ b/autotests/input/syntax/verilog/results/or1200_dc_fsm.v.reference.html @@ -1,577 +1,577 @@ or1200_dc_fsm.v -
+
 //////////////////////////////////////////////////////////////////////
 ////                                                              ////
 ////  OR1200's DC FSM                                             ////
 ////                                                              ////
 ////  This file is part of the OpenRISC 1200 project              ////
 ////  http://opencores.org/project,or1k                           ////
 ////                                                              ////
 ////  Description                                                 ////
 ////  Data cache state machine                                    ////
 ////                                                              ////
 ////  To Do:                                                      ////
 ////   - Test error during line read or write                     ////
 ////                                                              ////
 ////  Author(s):                                                  ////
 ////      - Damjan Lampret, lampret@opencores.org                 ////
 ////      - Julius Baxter, julius@opencores.org                   ////
 ////                                                              ////
 //////////////////////////////////////////////////////////////////////
 ////                                                              ////
 //// Copyright (C) 2000, 2010 Authors and OPENCORES.ORG           ////
 ////                                                              ////
 //// This source file may be used and distributed without         ////
 //// restriction provided that this copyright statement is not    ////
 //// removed from the file and that any derivative work contains  ////
 //// the original copyright notice and the associated disclaimer. ////
 ////                                                              ////
 //// This source file is free software; you can redistribute it   ////
 //// and/or modify it under the terms of the GNU Lesser General   ////
 //// Public License as published by the Free Software Foundation; ////
 //// either version 2.1 of the License, or (at your option) any   ////
 //// later version.                                               ////
 ////                                                              ////
 //// This source is distributed in the hope that it will be       ////
 //// useful, but WITHOUT ANY WARRANTY; without even the implied   ////
 //// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      ////
 //// PURPOSE.  See the GNU Lesser General Public License for more ////
 //// details.                                                     ////
 ////                                                              ////
 //// You should have received a copy of the GNU Lesser General    ////
 //// Public License along with this source; if not, download it   ////
 //// from http://www.opencores.org/lgpl.shtml                     ////
 ////                                                              ////
 //////////////////////////////////////////////////////////////////////
 //
 // $Log: or1200_dc_fsm.v,v $
 // Revision 2.0  2010/06/30 11:00:00  ORSoC
 // Minor update: 
 // Bugs fixed. 
 //
 
 // synopsys translate_off
 `include "timescale.v"
 // synopsys translate_on
 `include "or1200_defines.v"
 
 `define OR1200_DCFSM_IDLE	3'd0
 `define OR1200_DCFSM_CLOADSTORE	3'd1
 `define OR1200_DCFSM_LOOP2	3'd2
 `define OR1200_DCFSM_LOOP3	3'd3
 `define OR1200_DCFSM_LOOP4	3'd4
 `define OR1200_DCFSM_FLUSH5	3'd5
 `define OR1200_DCFSM_INV6	3'd6
 `define OR1200_DCFSM_WAITSPRCS7	3'd7
 
 
 
 //
 // Data cache FSM for cache line of 16 bytes (4x singleword)
 //
 
 module or1200_dc_fsm
   (
    // Clock and reset
    clk, rst,
    
    // Internal i/f to top level DC
    dc_en, dcqmem_cycstb_i, dcqmem_ci_i, dcqmem_we_i, dcqmem_sel_i,
    tagcomp_miss, biudata_valid, biudata_error, lsu_addr,
    dcram_we, biu_read, biu_write, biu_do_sel, dcram_di_sel, first_hit_ack, 
    first_miss_ack, first_miss_err, burst, tag_we, tag_valid, dc_addr, 
    dc_no_writethrough, tag_dirty, dirty, tag, tag_v, dc_block_flush, 
    dc_block_writeback, spr_dat_i, mtspr_dc_done, spr_cswe
    );
 
    //
    // I/O
    //
    input				clk;
    input				rst;
    input				dc_en;
    input				dcqmem_cycstb_i;
    input				dcqmem_ci_i;
    input				dcqmem_we_i;
    input [3:0] 				dcqmem_sel_i;
    input				tagcomp_miss;
    input				biudata_valid;
    input				biudata_error;
    input [31:0] 			lsu_addr;
    output [3:0] 			dcram_we;
    output				biu_read;
    output				biu_write;
    output 				dcram_di_sel;
    output 				biu_do_sel;
    output				first_hit_ack;
    output				first_miss_ack;
    output				first_miss_err;
    output				burst;
    output				tag_we;
    output 				tag_valid;
    output [31:0] 			dc_addr;
    input 				dc_no_writethrough;
    output 				tag_dirty;
    input 				dirty;
    input [`OR1200_DCTAG_W-2:0] 		tag;
    input 				tag_v;   
    input 				dc_block_flush;
    input 				dc_block_writeback;
    input [31:0] 			spr_dat_i;
    output 				mtspr_dc_done;
    input 				spr_cswe;
    
    
    //
    // Internal wires and regs
    //
    reg [31:0] 				addr_r;
    reg [2:0] 				state;
    reg [`OR1200_DCLS-1:0] 		cnt;
    reg 					hitmiss_eval;
    reg 					store;
    reg 					load;
    reg 					cache_inhibit;
    reg 					cache_miss;
    reg 					cache_dirty_needs_writeback;
    reg                                  did_early_load_ack;
    reg 					cache_spr_block_flush;
    reg 					cache_spr_block_writeback;
    reg 					cache_wb;   
    wire 				load_hit_ack;
    wire 				load_miss_ack;
    wire 				load_inhibit_ack;   
    wire 				store_hit_ack;
    wire 				store_hit_writethrough_ack;   
    wire 				store_miss_writethrough_ack;   
    wire 				store_inhibit_ack;
    wire 				store_miss_ack;
    wire 				dcram_we_after_line_load;
    wire 				dcram_we_during_line_load;
    wire 				tagram_we_end_of_loadstore_loop;
    wire 				tagram_dirty_bit_set;   
    wire 				writethrough;
    wire 				cache_inhibit_with_eval;
    wire [(`OR1200_DCLS-1)-2:0]		next_addr_word;
 
    //
    // Cache inhibit
    //
    
    // Indicates whether cache is inhibited, during hitmiss_eval and after
    assign cache_inhibit_with_eval = (hitmiss_eval & dcqmem_ci_i) |
 				    (!hitmiss_eval & cache_inhibit);
    
    //
    // Generate of DCRAM write enables
    //
 
    // WE when non-writethrough, and had to wait for a line to load.
    assign dcram_we_after_line_load = (state == `OR1200_DCFSM_LOOP3) &
 				    dcqmem_we_i & !cache_dirty_needs_writeback &
 				     !did_early_load_ack;
 
    // WE when receiving the data cache line
    assign dcram_we_during_line_load = (state == `OR1200_DCFSM_LOOP2) & load & 
 				      biudata_valid;   
    
    assign dcram_we =(// Write when hit - make sure it is only when hit - could
 		     // maybe be doing write through and don't want to corrupt
 		     // cache lines corresponding to the writethrough addr_r.
 		     ({4{store_hit_ack | store_hit_writethrough_ack}} |
 		     // Write after load of line
 		     {4{dcram_we_after_line_load}}) & 
 		     dcqmem_sel_i		 ) |
 		    // Write during load
 		    {4{dcram_we_during_line_load}};
 
    //
    // Tag RAM signals
    //
    
    // WE to tag RAM when we finish loading a line.
    assign tagram_we_end_of_loadstore_loop = ((state==`OR1200_DCFSM_LOOP2) & 
 					     biudata_valid & !(|cnt));
    
 `ifndef OR1200_DC_WRITETHROUGH
    // No writethrough, so mark a line dirty whenever we write to it
    assign tagram_dirty_bit_set = store_hit_ack | store_miss_ack;
 
    // Generate done signal for MTSPR instructions that may block execution
    assign mtspr_dc_done = // Either DC disabled or we're not selected, or
 			  !dc_en | !spr_cswe |
 			  // Requested address not valid or writeback and !dirty
 			  ((state==`OR1200_DCFSM_FLUSH5) & 
 			   (!tag_v | (cache_spr_block_writeback & !dirty))) |
 			  // Writeback or flush is finished
 			  ((state==`OR1200_DCFSM_LOOP3) & 
 			   (cache_spr_block_flush | cache_spr_block_writeback))|
 			  // Invalidate of clean line finished
 			  ((state==`OR1200_DCFSM_INV6) & cache_spr_block_flush);
    
    
 `else
  `ifdef OR1200_DC_NOSTACKWRITETHROUGH   
    // For dirty bit setting when having writethrough but not for stack
    assign tagram_dirty_bit_set = store_hit_ack | store_miss_ack;
  `else
    // Lines will never be dirty if always writethrough
    assign tagram_dirty_bit_set = 0;
  `endif
    
    assign mtspr_dc_done = 1'b1;
    
 `endif
 
    assign tag_dirty = tagram_dirty_bit_set;
    
    // WE to tag RAM
    assign tag_we = tagram_we_end_of_loadstore_loop |		    
                     tagram_dirty_bit_set | (state == `OR1200_DCFSM_INV6);
    
 
    // Valid bit
    // Set valid when end of line load, or marking dirty (is still valid)
    assign tag_valid = ( tagram_we_end_of_loadstore_loop & 
 			(load | (store & cache_spr_block_writeback)) ) |
 		      tagram_dirty_bit_set;
 
 
    
    //
    // BIU read and write
    //
 
    assign biu_read = // Bus read request when:
 		     // 1) Have a miss and not dirty or a load with inhibit
 		     ((state == `OR1200_DCFSM_CLOADSTORE) &
 		      (((hitmiss_eval & tagcomp_miss & !dirty & 
 			 !(store & writethrough)) | 
 			(load & cache_inhibit_with_eval)) & dcqmem_cycstb_i)) |
 		     // 2) In the loop and loading
 		     ((state == `OR1200_DCFSM_LOOP2) & load);
    
 
    assign biu_write = // Bus write request when:
 		      // 1) Have a miss and dirty or store with inhibit
 		      ((state == `OR1200_DCFSM_CLOADSTORE) & 
 		       (((hitmiss_eval & tagcomp_miss & dirty) | 
 			 (store & writethrough)) | 
 			(store & cache_inhibit_with_eval)) & dcqmem_cycstb_i) |
 		      // 2) In the loop and storing
 		      ((state == `OR1200_DCFSM_LOOP2) & store);
    
    //
    // Select for data to actual cache RAM (from LSU or BIU)
    //
    // Data to DCRAM - from external bus when loading (from IU when store)
    assign dcram_di_sel = load;
    // Data to external bus - always from IU except in case of bursting back
    //                        the line to memory. (1 selects DCRAM)
    assign biu_do_sel = (state == `OR1200_DCFSM_LOOP2) & store;
 
    // 3-bit wire for calculating next word of burst write, depending on
    // line size of data cache.
    assign next_addr_word =  addr_r[`OR1200_DCLS-1:2] + 1;
    
    // Address to cache RAM (tag address also derived from this)   
    assign dc_addr =
 		   // First check if we've got a block flush or WB op
 		   ((dc_block_flush & !cache_spr_block_flush) | 
 		   (dc_block_writeback & !cache_spr_block_writeback)) ? 
 		   spr_dat_i :
 		   (state==`OR1200_DCFSM_FLUSH5) ? addr_r:
 		    // If no SPR action, then always put out address from LSU
 		    (state==`OR1200_DCFSM_IDLE | hitmiss_eval) ? lsu_addr :
 		    // Next, if in writeback loop, when ACKed must immediately
 		    // output next word address (the RAM address takes a cycle
 		    // to increment, but it's needed immediately for burst)
 		    // otherwise, output our registered address.
 		    (state==`OR1200_DCFSM_LOOP2 & biudata_valid & store ) ? 
 		    {addr_r[31:`OR1200_DCLS], next_addr_word, 2'b00} : addr_r;
    
 `ifdef OR1200_DC_WRITETHROUGH
  `ifdef OR1200_DC_NOSTACKWRITETHROUGH   
    assign writethrough = !dc_no_writethrough;
  `else
    assign writethrough = 1;
  `endif
 `else
    assign writethrough = 0;
 `endif
    
    //
    // ACK generation for LSU
    //
    
    // ACK for when it's a cache hit
    assign first_hit_ack = load_hit_ack | store_hit_ack | 
 			  store_hit_writethrough_ack | 
 			  store_miss_writethrough_ack |
 			  store_inhibit_ack | store_miss_ack ;
 
    // ACK for when it's a cache miss - load only, is used in MUX for data back
    //                                  LSU straight off external data bus. In
    //                                  this was is also used for cache inhibit
    //                                  loads.
    // first_hit_ack takes precedence over first_miss_ack
    assign first_miss_ack = ~first_hit_ack & (load_miss_ack | load_inhibit_ack);
    
    // ACK cache hit on load
    assign load_hit_ack = (state == `OR1200_DCFSM_CLOADSTORE) & 
 			 hitmiss_eval & !tagcomp_miss & !dcqmem_ci_i & load;
    
    // ACK cache hit on store, no writethrough
    assign store_hit_ack = (state == `OR1200_DCFSM_CLOADSTORE) & 
 			  hitmiss_eval  & !tagcomp_miss & !dcqmem_ci_i &
 			  store & !writethrough;
    
    // ACK cache hit on store with writethrough
    assign store_hit_writethrough_ack = (state == `OR1200_DCFSM_CLOADSTORE) & 
 				       !cache_miss & !cache_inhibit &
 				       store & writethrough & biudata_valid;
    
    // ACK cache miss on store with writethrough
    assign store_miss_writethrough_ack = (state == `OR1200_DCFSM_CLOADSTORE) & 
 					cache_miss & !cache_inhibit &
 					store & writethrough & biudata_valid;
       
    // ACK store when cacheinhibit
    assign store_inhibit_ack = (state == `OR1200_DCFSM_CLOADSTORE) &
 			      store & cache_inhibit & biudata_valid;
    
    
    // Get the _early_ ack on first ACK back from wishbone during load only
    // Condition is that we're in the loop - that it's the first ack we get (can
    // tell from value of cnt), and we're loading a line to read from it (not
    // loading to write to it, in the case of a write without writethrough.)
    assign load_miss_ack =  ((state== `OR1200_DCFSM_LOOP2) & load &
 			    (cnt==((1 << `OR1200_DCLS) - 4)) & biudata_valid & 
 			    !(dcqmem_we_i & !writethrough));
    
    assign load_inhibit_ack = (state == `OR1200_DCFSM_CLOADSTORE) &
 			     load & cache_inhibit & biudata_valid;   
    
    // This will be case of write through disabled, and had to load a line.
    assign store_miss_ack = dcram_we_after_line_load;
             
    assign first_miss_err = biudata_error & dcqmem_cycstb_i;
 
    // Signal burst when in the load/store loop. We will always try to burst.
    assign burst = (state == `OR1200_DCFSM_LOOP2);
 
    //
    // Main DC FSM
    //
    always @(posedge clk or `OR1200_RST_EVENT rst) begin
       if (rst == `OR1200_RST_VALUE) begin
 	 state <=  `OR1200_DCFSM_IDLE;
 	 addr_r <=  32'd0;
 	 hitmiss_eval <=  1'b0;
 	 store <=  1'b0;
 	 load <=  1'b0;
 	 cnt <=  `OR1200_DCLS'd0;
          cache_miss <=  1'b0;
 	 cache_dirty_needs_writeback <= 1'b0;
 	 cache_inhibit <=  1'b0;
 	 did_early_load_ack <= 1'b0;
 	 cache_spr_block_flush <= 1'b0;
 	 cache_spr_block_writeback <= 1'b0;
       end
       else
 	case (state)	// synopsys parallel_case
 	  
           `OR1200_DCFSM_IDLE : begin
 	     if (dc_en & (dc_block_flush | dc_block_writeback))
 	       begin
 		  cache_spr_block_flush <= dc_block_flush;
 		  cache_spr_block_writeback <= dc_block_writeback;
 		  hitmiss_eval <= 1'b1;
 		  state <= `OR1200_DCFSM_FLUSH5;
 		  addr_r <=  spr_dat_i;
 	       end
 	     else if (dc_en & dcqmem_cycstb_i)
 	       begin
 		  state <= `OR1200_DCFSM_CLOADSTORE;
 		  hitmiss_eval <=  1'b1;
 		  store <=  dcqmem_we_i;
 		  load <=  !dcqmem_we_i;
 	       end
 	     
 	     
           end // case: `OR1200_DCFSM_IDLE
 	  
           `OR1200_DCFSM_CLOADSTORE: begin
 	     hitmiss_eval <=  1'b0;
 	     if (hitmiss_eval) begin
                 cache_inhibit <=  dcqmem_ci_i; // Check for cache inhibit here
                 cache_miss <=  tagcomp_miss;
 		cache_dirty_needs_writeback <= dirty;
 		addr_r <=  lsu_addr;
              end
 
 	     // Evaluate any cache line load/stores in first cycle:
 			     //
 	     if (hitmiss_eval & tagcomp_miss & !(store & writethrough) &
 		 !dcqmem_ci_i)
 	       begin
 		  // Miss - first either:
 		  //  1) write back dirty line 
 		  if (dirty) begin
 		     // Address for writeback
 		     addr_r <=  {tag, lsu_addr[`OR1200_DCINDXH:2],2'd0};
 		     load <= 1'b0;
 		     store <= 1'b1;
 `ifdef OR1200_VERBOSE		     
 		     $display("%t: dcache miss and dirty", $time);
 `endif
 		  end
 		  //  2) load requested line
 		  else begin
 		     addr_r <=  lsu_addr;
 		     load <= 1'b1;
 		     store <= 1'b0;
 		  end // else: !if(dirty)
 		  state <= `OR1200_DCFSM_LOOP2;		  
 		  // Set the counter for the burst accesses
 		  cnt <=  ((1 << `OR1200_DCLS) - 4);
 	       end
              else if (// Strobe goes low
 		      !dcqmem_cycstb_i |
 		      // Cycle finishes
 		      (!hitmiss_eval & (biudata_valid | biudata_error)) |
 		      // Cache hit in first cycle....
 		      (hitmiss_eval & !tagcomp_miss & !dcqmem_ci_i &
 		      // .. and you're not doing a writethrough store..
 		      !(store & writethrough))) begin
                 state <=  `OR1200_DCFSM_IDLE;
                 load <=  1'b0;
 		store <= 1'b0;
 		cache_inhibit <= 1'b0;
 		cache_dirty_needs_writeback <= 1'b0;
              end	     
           end // case: `OR1200_DCFSM_CLOADSTORE	  
 	  
           `OR1200_DCFSM_LOOP2 : begin // loop/abort	     
              if (!dc_en| biudata_error) begin
                 state <=  `OR1200_DCFSM_IDLE;
                 load <=  1'b0;
 		store <= 1'b0;
 		cnt <= `OR1200_DCLS'd0;
              end
              if (biudata_valid & (|cnt)) begin
                 cnt <=  cnt - 4;
                 addr_r[`OR1200_DCLS-1:2] <=  addr_r[`OR1200_DCLS-1:2] + 1;
              end
 	     else if (biudata_valid & !(|cnt)) begin
 		state <= `OR1200_DCFSM_LOOP3;
 		addr_r <=  lsu_addr;
 		load <= 1'b0;
 		store <= 1'b0;
 	     end
 
 	     // Track if we did an early ack during a load
 	     if (load_miss_ack)
 	       did_early_load_ack <= 1'b1;
 	     
 
           end // case: `OR1200_DCFSM_LOOP2
 	  
 	  `OR1200_DCFSM_LOOP3: begin // figure out next step
 	     if (cache_dirty_needs_writeback) begin
 		// Just did store of the dirty line so now load new one
 		load <= 1'b1;
 		// Set the counter for the burst accesses
 		cnt <=  ((1 << `OR1200_DCLS) - 4);
 		// Address of line to be loaded
 		addr_r <=  lsu_addr;
 		cache_dirty_needs_writeback <= 1'b0;
 		state <= `OR1200_DCFSM_LOOP2;
 	     end // if (cache_dirty_needs_writeback)
 	     else if (cache_spr_block_flush | cache_spr_block_writeback) begin
 		// Just wrote back the line to memory, we're finished.
 		cache_spr_block_flush <= 1'b0;
 		cache_spr_block_writeback <= 1'b0;
 		state <= `OR1200_DCFSM_WAITSPRCS7;
 	     end
 	     else begin
 		// Just loaded a new line, finish up
 		did_early_load_ack <= 1'b0;
 		state <= `OR1200_DCFSM_LOOP4;
 	     end
 	  end // case: `OR1200_DCFSM_LOOP3
 
 	  `OR1200_DCFSM_LOOP4: begin
 	     state <=  `OR1200_DCFSM_IDLE;
 	  end
 
 	  `OR1200_DCFSM_FLUSH5: begin
 	     hitmiss_eval <= 1'b0;
 	     if (hitmiss_eval & !tag_v)
 	       begin
 		  // Not even cached, just ignore
 		  cache_spr_block_flush <= 1'b0;
 		  cache_spr_block_writeback <= 1'b0;
 		  state <=  `OR1200_DCFSM_WAITSPRCS7;
 	       end
 	     else if (hitmiss_eval & tag_v)
 	       begin
 		  // Tag is valid - what do we do?
 		  if ((cache_spr_block_flush | cache_spr_block_writeback) & 
 		      dirty) begin
 		     // Need to writeback
 		     // Address for writeback (spr_dat_i has already changed so
 		     // use line number from addr_r)
 		     addr_r <=  {tag, addr_r[`OR1200_DCINDXH:2],2'd0};
 		     load <= 1'b0;
 		     store <= 1'b1;
 `ifdef OR1200_VERBOSE		     
 		     $display("%t: block flush: dirty block", $time);
 `endif
 		     state <= `OR1200_DCFSM_LOOP2;		  
 		     // Set the counter for the burst accesses
 		     cnt <=  ((1 << `OR1200_DCLS) - 4);
 		  end
 		  else if (cache_spr_block_flush & !dirty)
 		    begin
 		       // Line not dirty, just need to invalidate
 		       state <=  `OR1200_DCFSM_INV6;
 		    end // else: !if(dirty)
 		  else if (cache_spr_block_writeback & !dirty)
 		    begin
 		       // Nothing to do - line is valid but not dirty
 		       cache_spr_block_writeback <= 1'b0;
 		       state <=  `OR1200_DCFSM_WAITSPRCS7;
 		    end
 	  end // if (hitmiss_eval & tag_v)
 	  end
 	  `OR1200_DCFSM_INV6: begin
 	     cache_spr_block_flush <= 1'b0;
 	     // Wait until SPR CS goes low before going back to idle
 	     if (!spr_cswe)
 	       state <=  `OR1200_DCFSM_IDLE;
 	  end
 	  `OR1200_DCFSM_WAITSPRCS7: begin
 	     // Wait until SPR CS goes low before going back to idle
 	     if (!spr_cswe)
 	       state <=  `OR1200_DCFSM_IDLE;
 	  end
 
 	endcase // case (state)
       
    end // always @ (posedge clk or `OR1200_RST_EVENT rst)
    
 
 endmodule
 
diff --git a/autotests/input/syntax/verilog/results/or1200_du.v.reference.html b/autotests/input/syntax/verilog/results/or1200_du.v.reference.html index e319fffb..7fb1cf57 100644 --- a/autotests/input/syntax/verilog/results/or1200_du.v.reference.html +++ b/autotests/input/syntax/verilog/results/or1200_du.v.reference.html @@ -1,1817 +1,1817 @@ or1200_du.v -
+
 //////////////////////////////////////////////////////////////////////
 ////                                                              ////
 ////  OR1200's Debug Unit                                         ////
 ////                                                              ////
 ////  This file is part of the OpenRISC 1200 project              ////
 ////  http://www.opencores.org/project,or1k                       ////
 ////                                                              ////
 ////  Description                                                 ////
 ////  Basic OR1200 debug unit.                                    ////
 ////                                                              ////
 ////  To Do:                                                      ////
 ////   - make it smaller and faster                               ////
 ////                                                              ////
 ////  Author(s):                                                  ////
 ////      - Damjan Lampret, lampret@opencores.org                 ////
 ////                                                              ////
 //////////////////////////////////////////////////////////////////////
 ////                                                              ////
 //// Copyright (C) 2000 Authors and OPENCORES.ORG                 ////
 ////                                                              ////
 //// This source file may be used and distributed without         ////
 //// restriction provided that this copyright statement is not    ////
 //// removed from the file and that any derivative work contains  ////
 //// the original copyright notice and the associated disclaimer. ////
 ////                                                              ////
 //// This source file is free software; you can redistribute it   ////
 //// and/or modify it under the terms of the GNU Lesser General   ////
 //// Public License as published by the Free Software Foundation; ////
 //// either version 2.1 of the License, or (at your option) any   ////
 //// later version.                                               ////
 ////                                                              ////
 //// This source is distributed in the hope that it will be       ////
 //// useful, but WITHOUT ANY WARRANTY; without even the implied   ////
 //// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      ////
 //// PURPOSE.  See the GNU Lesser General Public License for more ////
 //// details.                                                     ////
 ////                                                              ////
 //// You should have received a copy of the GNU Lesser General    ////
 //// Public License along with this source; if not, download it   ////
 //// from http://www.opencores.org/lgpl.shtml                     ////
 ////                                                              ////
 //////////////////////////////////////////////////////////////////////
 //
 //
 // $Log: or1200_du.v,v $
 // Revision 2.0  2010/06/30 11:00:00  ORSoC
 // Minor update: 
 // Bugs fixed. 
 
 // synopsys translate_off
 `include "timescale.v"
 // synopsys translate_on
 `include "or1200_defines.v"
 
 //
 // Debug unit
 //
 
 module or1200_du(
 	// RISC Internal Interface
 	clk, rst,
 	dcpu_cycstb_i, dcpu_we_i, dcpu_adr_i, dcpu_dat_lsu,
 	dcpu_dat_dc, icpu_cycstb_i,
 	ex_freeze, branch_op, ex_insn, id_pc,
 	spr_dat_npc, rf_dataw,
 	du_dsr, du_dmr1, du_stall, du_addr, du_dat_i, du_dat_o,
 	du_read, du_write, du_except_stop, du_hwbkpt, du_flush_pipe,
 	spr_cs, spr_write, spr_addr, spr_dat_i, spr_dat_o,
 
 	// External Debug Interface
 	dbg_stall_i, dbg_ewt_i,	dbg_lss_o, dbg_is_o, dbg_wp_o, dbg_bp_o,
 	dbg_stb_i, dbg_we_i, dbg_adr_i, dbg_dat_i, dbg_dat_o, dbg_ack_o
 );
 
 parameter dw = `OR1200_OPERAND_WIDTH;
 parameter aw = `OR1200_OPERAND_WIDTH;
 
 //
 // I/O
 //
 
 //
 // RISC Internal Interface
 //
 input				clk;		// Clock
 input				rst;		// Reset
 input				dcpu_cycstb_i;	// LSU status
 input				dcpu_we_i;	// LSU status
 input	[31:0]			dcpu_adr_i;	// LSU addr
 input	[31:0]			dcpu_dat_lsu;	// LSU store data
 input	[31:0]			dcpu_dat_dc;	// LSU load data
 input	[`OR1200_FETCHOP_WIDTH-1:0]	icpu_cycstb_i;	// IFETCH unit status
 input				ex_freeze;	// EX stage freeze
 input	[`OR1200_BRANCHOP_WIDTH-1:0]	branch_op;	// Branch op
 input	[dw-1:0]		ex_insn;	// EX insn
 input	[31:0]			id_pc;		// insn fetch EA
 input	[31:0]			spr_dat_npc;	// Next PC (for trace)
 input	[31:0]			rf_dataw;	// ALU result (for trace)
 output	[`OR1200_DU_DSR_WIDTH-1:0]     du_dsr;		// DSR
 output	[24: 0]			du_dmr1;
 output				du_stall;	// Debug Unit Stall
 output	[aw-1:0]		du_addr;	// Debug Unit Address
 input	[dw-1:0]		du_dat_i;	// Debug Unit Data In
 output	[dw-1:0]		du_dat_o;	// Debug Unit Data Out
 output				du_read;	// Debug Unit Read Enable
 output				du_write;	// Debug Unit Write Enable
 input	[13:0]			du_except_stop;	// Exception masked by DSR
 output				du_hwbkpt;	// Cause trap exception (HW Breakpoints)
 output				du_flush_pipe;	// Cause pipeline flush and pc<-npc
 input				spr_cs;		// SPR Chip Select
 input				spr_write;	// SPR Read/Write
 input	[aw-1:0]		spr_addr;	// SPR Address
 input	[dw-1:0]		spr_dat_i;	// SPR Data Input
 output	[dw-1:0]		spr_dat_o;	// SPR Data Output
 
 //
 // External Debug Interface
 //
 input			dbg_stall_i;	// External Stall Input
 input			dbg_ewt_i;	// External Watchpoint Trigger Input
 output	[3:0]		dbg_lss_o;	// External Load/Store Unit Status
 output	[1:0]		dbg_is_o;	// External Insn Fetch Status
 output	[10:0]		dbg_wp_o;	// Watchpoints Outputs
 output			dbg_bp_o;	// Breakpoint Output
 input			dbg_stb_i;      // External Address/Data Strobe
 input			dbg_we_i;       // External Write Enable
 input	[aw-1:0]	dbg_adr_i;	// External Address Input
 input	[dw-1:0]	dbg_dat_i;	// External Data Input
 output	[dw-1:0]	dbg_dat_o;	// External Data Output
 output			dbg_ack_o;	// External Data Acknowledge (not WB compatible)
 reg	[dw-1:0]	dbg_dat_o;	// External Data Output
 reg			dbg_ack_o;	// External Data Acknowledge (not WB compatible)
 
 
 //
 // Some connections go directly from the CPU through DU to Debug I/F
 //
 `ifdef OR1200_DU_STATUS_UNIMPLEMENTED
 assign dbg_lss_o = 4'b0000;
 
 reg	[1:0]			dbg_is_o;
 //
 // Show insn activity (temp, must be removed)
 //
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dbg_is_o <=  2'b00;
 	else if (!ex_freeze & ~((ex_insn[31:26] == `OR1200_OR32_NOP) & ex_insn[16]))
 		dbg_is_o <=  ~dbg_is_o;
 `ifdef UNUSED
 assign dbg_is_o = 2'b00;
 `endif
 `else
 assign dbg_lss_o = dcpu_cycstb_i ? {dcpu_we_i, 3'b000} : 4'b0000;
 assign dbg_is_o = {1'b0, icpu_cycstb_i};
 `endif
 assign dbg_wp_o = 11'b000_0000_0000;
 
 //
 // Some connections go directly from Debug I/F through DU to the CPU
 //
 assign du_stall = dbg_stall_i;
 assign du_addr = dbg_adr_i;
 assign du_dat_o = dbg_dat_i;
 assign du_read = dbg_stb_i && !dbg_we_i;
 assign du_write = dbg_stb_i && dbg_we_i;
 
 //
 // After a sw breakpoint, the replaced instruction need to be executed.
 // We flush the entire pipeline and set the pc to the current address
 // to execute the restored address.
 //
 
 reg du_flush_pipe_r;
 reg dbg_stall_i_r;
 
 assign du_flush_pipe = du_flush_pipe_r;
 
 //
 // Register du_flush_pipe
 //
 always @(posedge clk or `OR1200_RST_EVENT rst) begin
 	if (rst == `OR1200_RST_VALUE) begin
 		du_flush_pipe_r   <=  1'b0;
 	end
 	else begin
 		du_flush_pipe_r   <=  (dbg_stall_i_r && !dbg_stall_i && |du_except_stop);
 	end
 end
 
 //
 // Detect dbg_stall falling edge
 //
 always @(posedge clk or `OR1200_RST_EVENT rst) begin
 	if (rst == `OR1200_RST_VALUE) begin
 		dbg_stall_i_r   <=  1'b0;
 	end
 	else begin
 		dbg_stall_i_r   <=  dbg_stall_i;
 	end
 end
 
 reg				dbg_ack;
 //
 // Generate acknowledge -- just delay stb signal
 //
 always @(posedge clk or `OR1200_RST_EVENT rst) begin
 	if (rst == `OR1200_RST_VALUE) begin
 		dbg_ack   <=  1'b0;
 		dbg_ack_o <=  1'b0;
 	end
 	else begin
 		dbg_ack   <=  dbg_stb_i;		// valid when du_dat_i 
 		dbg_ack_o <=  dbg_ack & dbg_stb_i;	// valid when dbg_dat_o 
 	end
 end
 
 // 
 // Register data output
 //
 always @(posedge clk)
     dbg_dat_o <=  du_dat_i;
 
 `ifdef OR1200_DU_IMPLEMENTED
 
 //
 // Debug Mode Register 1
 //
 `ifdef OR1200_DU_DMR1
 reg	[24:0]			dmr1;		// DMR1 implemented
 `else
 wire	[24:0]			dmr1;		// DMR1 not implemented
 `endif
 assign du_dmr1 = dmr1;
 
 //
 // Debug Mode Register 2
 //
 `ifdef OR1200_DU_DMR2
 reg	[23:0]			dmr2;		// DMR2 implemented
 `else
 wire	[23:0]			dmr2;		// DMR2 not implemented
 `endif
 
 //
 // Debug Stop Register
 //
 `ifdef OR1200_DU_DSR
 reg	[`OR1200_DU_DSR_WIDTH-1:0]	dsr;		// DSR implemented
 `else
 wire	[`OR1200_DU_DSR_WIDTH-1:0]	dsr;		// DSR not implemented
 `endif
 
 //
 // Debug Reason Register
 //
 `ifdef OR1200_DU_DRR
 reg	[13:0]			drr;		// DRR implemented
 `else
 wire	[13:0]			drr;		// DRR not implemented
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR0
 reg	[31:0]			dvr0;
 `else
 wire	[31:0]			dvr0;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR1
 reg	[31:0]			dvr1;
 `else
 wire	[31:0]			dvr1;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR2
 reg	[31:0]			dvr2;
 `else
 wire	[31:0]			dvr2;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR3
 reg	[31:0]			dvr3;
 `else
 wire	[31:0]			dvr3;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR4
 reg	[31:0]			dvr4;
 `else
 wire	[31:0]			dvr4;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR5
 reg	[31:0]			dvr5;
 `else
 wire	[31:0]			dvr5;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR6
 reg	[31:0]			dvr6;
 `else
 wire	[31:0]			dvr6;
 `endif
 
 //
 // Debug Value Register N
 //
 `ifdef OR1200_DU_DVR7
 reg	[31:0]			dvr7;
 `else
 wire	[31:0]			dvr7;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR0
 reg	[7:0]			dcr0;
 `else
 wire	[7:0]			dcr0;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR1
 reg	[7:0]			dcr1;
 `else
 wire	[7:0]			dcr1;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR2
 reg	[7:0]			dcr2;
 `else
 wire	[7:0]			dcr2;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR3
 reg	[7:0]			dcr3;
 `else
 wire	[7:0]			dcr3;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR4
 reg	[7:0]			dcr4;
 `else
 wire	[7:0]			dcr4;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR5
 reg	[7:0]			dcr5;
 `else
 wire	[7:0]			dcr5;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR6
 reg	[7:0]			dcr6;
 `else
 wire	[7:0]			dcr6;
 `endif
 
 //
 // Debug Control Register N
 //
 `ifdef OR1200_DU_DCR7
 reg	[7:0]			dcr7;
 `else
 wire	[7:0]			dcr7;
 `endif
 
 //
 // Debug Watchpoint Counter Register 0
 //
 `ifdef OR1200_DU_DWCR0
 reg	[31:0]			dwcr0;
 `else
 wire	[31:0]			dwcr0;
 `endif
 
 //
 // Debug Watchpoint Counter Register 1
 //
 `ifdef OR1200_DU_DWCR1
 reg	[31:0]			dwcr1;
 `else
 wire	[31:0]			dwcr1;
 `endif
 
 //
 // Internal wires
 //
 wire				dmr1_sel; 	// DMR1 select
 wire				dmr2_sel; 	// DMR2 select
 wire				dsr_sel; 	// DSR select
 wire				drr_sel; 	// DRR select
 wire				dvr0_sel,
 				dvr1_sel,
 				dvr2_sel,
 				dvr3_sel,
 				dvr4_sel,
 				dvr5_sel,
 				dvr6_sel,
 				dvr7_sel; 	// DVR selects
 wire				dcr0_sel,
 				dcr1_sel,
 				dcr2_sel,
 				dcr3_sel,
 				dcr4_sel,
 				dcr5_sel,
 				dcr6_sel,
 				dcr7_sel; 	// DCR selects
 wire				dwcr0_sel,
 				dwcr1_sel; 	// DWCR selects
 reg				dbg_bp_r;
 reg 				ex_freeze_q;
 `ifdef OR1200_DU_HWBKPTS
 reg	[31:0]			match_cond0_ct;
 reg	[31:0]			match_cond1_ct;
 reg	[31:0]			match_cond2_ct;
 reg	[31:0]			match_cond3_ct;
 reg	[31:0]			match_cond4_ct;
 reg	[31:0]			match_cond5_ct;
 reg	[31:0]			match_cond6_ct;
 reg	[31:0]			match_cond7_ct;
 reg				match_cond0_stb;
 reg				match_cond1_stb;
 reg				match_cond2_stb;
 reg				match_cond3_stb;
 reg				match_cond4_stb;
 reg				match_cond5_stb;
 reg				match_cond6_stb;
 reg				match_cond7_stb;
 reg				match0;
 reg				match1;
 reg				match2;
 reg				match3;
 reg				match4;
 reg				match5;
 reg				match6;
 reg				match7;
 reg				wpcntr0_match;
 reg				wpcntr1_match;
 reg				incr_wpcntr0;
 reg				incr_wpcntr1;
 reg	[10:0]			wp;
 `endif
 wire				du_hwbkpt;
 reg				du_hwbkpt_hold;
 `ifdef OR1200_DU_READREGS
 reg	[31:0]			spr_dat_o;
 `endif
 reg	[13:0]			except_stop;	// Exceptions that stop because of DSR
 `ifdef OR1200_DU_TB_IMPLEMENTED
 wire				tb_enw;
 reg	[7:0]			tb_wadr;
 reg [31:0]			tb_timstmp;
 `endif
 wire	[31:0]			tbia_dat_o;
 wire	[31:0]			tbim_dat_o;
 wire	[31:0]			tbar_dat_o;
 wire	[31:0]			tbts_dat_o;
 
 //
 // DU registers address decoder
 //
 `ifdef OR1200_DU_DMR1
 assign dmr1_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DMR1));
 `endif
 `ifdef OR1200_DU_DMR2
 assign dmr2_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DMR2));
 `endif
 `ifdef OR1200_DU_DSR
 assign dsr_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DSR));
 `endif
 `ifdef OR1200_DU_DRR
 assign drr_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DRR));
 `endif
 `ifdef OR1200_DU_DVR0
 assign dvr0_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR0));
 `endif
 `ifdef OR1200_DU_DVR1
 assign dvr1_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR1));
 `endif
 `ifdef OR1200_DU_DVR2
 assign dvr2_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR2));
 `endif
 `ifdef OR1200_DU_DVR3
 assign dvr3_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR3));
 `endif
 `ifdef OR1200_DU_DVR4
 assign dvr4_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR4));
 `endif
 `ifdef OR1200_DU_DVR5
 assign dvr5_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR5));
 `endif
 `ifdef OR1200_DU_DVR6
 assign dvr6_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR6));
 `endif
 `ifdef OR1200_DU_DVR7
 assign dvr7_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DVR7));
 `endif
 `ifdef OR1200_DU_DCR0
 assign dcr0_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR0));
 `endif
 `ifdef OR1200_DU_DCR1
 assign dcr1_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR1));
 `endif
 `ifdef OR1200_DU_DCR2
 assign dcr2_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR2));
 `endif
 `ifdef OR1200_DU_DCR3
 assign dcr3_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR3));
 `endif
 `ifdef OR1200_DU_DCR4
 assign dcr4_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR4));
 `endif
 `ifdef OR1200_DU_DCR5
 assign dcr5_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR5));
 `endif
 `ifdef OR1200_DU_DCR6
 assign dcr6_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR6));
 `endif
 `ifdef OR1200_DU_DCR7
 assign dcr7_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DCR7));
 `endif
 `ifdef OR1200_DU_DWCR0
 assign dwcr0_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DWCR0));
 `endif
 `ifdef OR1200_DU_DWCR1
 assign dwcr1_sel = (spr_cs && (spr_addr[`OR1200_DUOFS_BITS] == `OR1200_DU_DWCR1));
 `endif
 
 // Track previous ex_freeze to detect when signals are updated
 always @(posedge clk)
   ex_freeze_q <= ex_freeze;
 
 //
 // Decode started exception
 //
 // du_except_stop comes from or1200_except
 //   
 always @(du_except_stop or ex_freeze_q) begin
 	except_stop = 14'b00_0000_0000_0000;
 	casez (du_except_stop)
 	        14'b1?_????_????_????:
 			except_stop[`OR1200_DU_DRR_TTE] = 1'b1;
 		14'b01_????_????_????: begin
 			except_stop[`OR1200_DU_DRR_IE] = 1'b1;
 		end
 		14'b00_1???_????_????: begin
 			except_stop[`OR1200_DU_DRR_IME] = 1'b1;
 		end
 		14'b00_01??_????_????:
 			except_stop[`OR1200_DU_DRR_IPFE] = 1'b1;
 		14'b00_001?_????_????: begin
 			except_stop[`OR1200_DU_DRR_BUSEE] = 1'b1;
 		end
 		14'b00_0001_????_????:
 			except_stop[`OR1200_DU_DRR_IIE] = 1'b1;
 		14'b00_0000_1???_????: begin
 			except_stop[`OR1200_DU_DRR_AE] = 1'b1;
 		end
 		14'b00_0000_01??_????: begin
 			except_stop[`OR1200_DU_DRR_DME] = 1'b1;
 		end
 		14'b00_0000_001?_????:
 			except_stop[`OR1200_DU_DRR_DPFE] = 1'b1;
 		14'b00_0000_0001_????:
 			except_stop[`OR1200_DU_DRR_BUSEE] = 1'b1;
 		14'b00_0000_0000_1???: begin
 			except_stop[`OR1200_DU_DRR_RE] = 1'b1;
 		end
 		14'b00_0000_0000_01??: begin
 			except_stop[`OR1200_DU_DRR_TE] = 1'b1 & ~ex_freeze_q;
 		end
 		14'b00_0000_0000_001?: begin
 		        except_stop[`OR1200_DU_DRR_FPE] = 1'b1;
 		end	  
 		14'b00_0000_0000_0001:
 			except_stop[`OR1200_DU_DRR_SCE] = 1'b1 & ~ex_freeze_q;
 		default:
 			except_stop = 14'b00_0000_0000_0000;
 	endcase // casez (du_except_stop)
 end
 
 //
 // dbg_bp_o is registered
 //
 assign dbg_bp_o = dbg_bp_r;
 
 //
 // Breakpoint activation register
 //
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dbg_bp_r <=  1'b0;
 	else if (!ex_freeze)
 		dbg_bp_r <=  |except_stop
 `ifdef OR1200_DU_DMR1_ST
                         | ~((ex_insn[31:26] == `OR1200_OR32_NOP) & ex_insn[16]) & dmr1[`OR1200_DU_DMR1_ST]
 `endif
 `ifdef OR1200_DU_DMR1_BT
                         | (branch_op != `OR1200_BRANCHOP_NOP) & (branch_op != `OR1200_BRANCHOP_RFE) & dmr1[`OR1200_DU_DMR1_BT]
 `endif
 			;
         else
                 dbg_bp_r <=  |except_stop;
 
 //
 // Write to DMR1
 //
 `ifdef OR1200_DU_DMR1
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dmr1 <= 25'h000_0000;
 	else if (dmr1_sel && spr_write)
 `ifdef OR1200_DU_HWBKPTS
 		dmr1 <=  spr_dat_i[24:0];
 `else
 		dmr1 <=  {1'b0, spr_dat_i[23:22], 22'h00_0000};
 `endif
 `else
 assign dmr1 = 25'h000_0000;
 `endif
 
 //
 // Write to DMR2
 //
 `ifdef OR1200_DU_DMR2
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dmr2 <= 24'h00_0000;
 	else if (dmr2_sel && spr_write)
 		dmr2 <=  spr_dat_i[23:0];
 `else
 assign dmr2 = 24'h00_0000;
 `endif
 
 //
 // Write to DSR
 //
 `ifdef OR1200_DU_DSR
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dsr <= {`OR1200_DU_DSR_WIDTH{1'b0}};
 	else if (dsr_sel && spr_write)
 		dsr <=  spr_dat_i[`OR1200_DU_DSR_WIDTH-1:0];
 `else
 assign dsr = {`OR1200_DU_DSR_WIDTH{1'b0}};
 `endif
 
 //
 // Write to DRR
 //
 `ifdef OR1200_DU_DRR
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		drr <= 14'b0;
 	else if (drr_sel && spr_write)
 		drr <=  spr_dat_i[13:0];
 	else
 		drr <=  drr | except_stop;
 `else
 assign drr = 14'b0;
 `endif
 
 //
 // Write to DVR0
 //
 `ifdef OR1200_DU_DVR0
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr0 <= 32'h0000_0000;
 	else if (dvr0_sel && spr_write)
 		dvr0 <=  spr_dat_i[31:0];
 `else
 assign dvr0 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR1
 //
 `ifdef OR1200_DU_DVR1
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr1 <= 32'h0000_0000;
 	else if (dvr1_sel && spr_write)
 		dvr1 <=  spr_dat_i[31:0];
 `else
 assign dvr1 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR2
 //
 `ifdef OR1200_DU_DVR2
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr2 <= 32'h0000_0000;
 	else if (dvr2_sel && spr_write)
 		dvr2 <=  spr_dat_i[31:0];
 `else
 assign dvr2 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR3
 //
 `ifdef OR1200_DU_DVR3
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr3 <= 32'h0000_0000;
 	else if (dvr3_sel && spr_write)
 		dvr3 <=  spr_dat_i[31:0];
 `else
 assign dvr3 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR4
 //
 `ifdef OR1200_DU_DVR4
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr4 <= 32'h0000_0000;
 	else if (dvr4_sel && spr_write)
 		dvr4 <=  spr_dat_i[31:0];
 `else
 assign dvr4 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR5
 //
 `ifdef OR1200_DU_DVR5
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr5 <= 32'h0000_0000;
 	else if (dvr5_sel && spr_write)
 		dvr5 <=  spr_dat_i[31:0];
 `else
 assign dvr5 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR6
 //
 `ifdef OR1200_DU_DVR6
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr6 <= 32'h0000_0000;
 	else if (dvr6_sel && spr_write)
 		dvr6 <=  spr_dat_i[31:0];
 `else
 assign dvr6 = 32'h0000_0000;
 `endif
 
 //
 // Write to DVR7
 //
 `ifdef OR1200_DU_DVR7
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dvr7 <= 32'h0000_0000;
 	else if (dvr7_sel && spr_write)
 		dvr7 <=  spr_dat_i[31:0];
 `else
 assign dvr7 = 32'h0000_0000;
 `endif
 
 //
 // Write to DCR0
 //
 `ifdef OR1200_DU_DCR0
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr0 <= 8'h00;
 	else if (dcr0_sel && spr_write)
 		dcr0 <=  spr_dat_i[7:0];
 `else
 assign dcr0 = 8'h00;
 `endif
 
 //
 // Write to DCR1
 //
 `ifdef OR1200_DU_DCR1
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr1 <= 8'h00;
 	else if (dcr1_sel && spr_write)
 		dcr1 <=  spr_dat_i[7:0];
 `else
 assign dcr1 = 8'h00;
 `endif
 
 //
 // Write to DCR2
 //
 `ifdef OR1200_DU_DCR2
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr2 <= 8'h00;
 	else if (dcr2_sel && spr_write)
 		dcr2 <=  spr_dat_i[7:0];
 `else
 assign dcr2 = 8'h00;
 `endif
 
 //
 // Write to DCR3
 //
 `ifdef OR1200_DU_DCR3
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr3 <= 8'h00;
 	else if (dcr3_sel && spr_write)
 		dcr3 <=  spr_dat_i[7:0];
 `else
 assign dcr3 = 8'h00;
 `endif
 
 //
 // Write to DCR4
 //
 `ifdef OR1200_DU_DCR4
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr4 <= 8'h00;
 	else if (dcr4_sel && spr_write)
 		dcr4 <=  spr_dat_i[7:0];
 `else
 assign dcr4 = 8'h00;
 `endif
 
 //
 // Write to DCR5
 //
 `ifdef OR1200_DU_DCR5
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr5 <= 8'h00;
 	else if (dcr5_sel && spr_write)
 		dcr5 <=  spr_dat_i[7:0];
 `else
 assign dcr5 = 8'h00;
 `endif
 
 //
 // Write to DCR6
 //
 `ifdef OR1200_DU_DCR6
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr6 <= 8'h00;
 	else if (dcr6_sel && spr_write)
 		dcr6 <=  spr_dat_i[7:0];
 `else
 assign dcr6 = 8'h00;
 `endif
 
 //
 // Write to DCR7
 //
 `ifdef OR1200_DU_DCR7
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dcr7 <= 8'h00;
 	else if (dcr7_sel && spr_write)
 		dcr7 <=  spr_dat_i[7:0];
 `else
 assign dcr7 = 8'h00;
 `endif
 
 //
 // Write to DWCR0
 //
 `ifdef OR1200_DU_DWCR0
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dwcr0 <= 32'h0000_0000;
 	else if (dwcr0_sel && spr_write)
 		dwcr0 <=  spr_dat_i[31:0];
 	else if (incr_wpcntr0)
 		dwcr0[`OR1200_DU_DWCR_COUNT] <=  dwcr0[`OR1200_DU_DWCR_COUNT] + 16'h0001;
 `else
 assign dwcr0 = 32'h0000_0000;
 `endif
 
 //
 // Write to DWCR1
 //
 `ifdef OR1200_DU_DWCR1
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		dwcr1 <= 32'h0000_0000;
 	else if (dwcr1_sel && spr_write)
 		dwcr1 <=  spr_dat_i[31:0];
 	else if (incr_wpcntr1)
 		dwcr1[`OR1200_DU_DWCR_COUNT] <=  dwcr1[`OR1200_DU_DWCR_COUNT] + 16'h0001;
 `else
 assign dwcr1 = 32'h0000_0000;
 `endif
 
 //
 // Read DU registers
 //
 `ifdef OR1200_DU_READREGS
 always @(spr_addr or dsr or drr or dmr1 or dmr2
 	or dvr0 or dvr1 or dvr2 or dvr3 or dvr4
 	or dvr5 or dvr6 or dvr7
 	or dcr0 or dcr1 or dcr2 or dcr3 or dcr4
 	or dcr5 or dcr6 or dcr7
 	or dwcr0 or dwcr1
 `ifdef OR1200_DU_TB_IMPLEMENTED
 	or tb_wadr or tbia_dat_o or tbim_dat_o
 	or tbar_dat_o or tbts_dat_o
 `endif
 	)
 	casez (spr_addr[`OR1200_DUOFS_BITS]) // synopsys parallel_case
 `ifdef OR1200_DU_DVR0
 		`OR1200_DU_DVR0:
 			spr_dat_o = dvr0;
 `endif
 `ifdef OR1200_DU_DVR1
 		`OR1200_DU_DVR1:
 			spr_dat_o = dvr1;
 `endif
 `ifdef OR1200_DU_DVR2
 		`OR1200_DU_DVR2:
 			spr_dat_o = dvr2;
 `endif
 `ifdef OR1200_DU_DVR3
 		`OR1200_DU_DVR3:
 			spr_dat_o = dvr3;
 `endif
 `ifdef OR1200_DU_DVR4
 		`OR1200_DU_DVR4:
 			spr_dat_o = dvr4;
 `endif
 `ifdef OR1200_DU_DVR5
 		`OR1200_DU_DVR5:
 			spr_dat_o = dvr5;
 `endif
 `ifdef OR1200_DU_DVR6
 		`OR1200_DU_DVR6:
 			spr_dat_o = dvr6;
 `endif
 `ifdef OR1200_DU_DVR7
 		`OR1200_DU_DVR7:
 			spr_dat_o = dvr7;
 `endif
 `ifdef OR1200_DU_DCR0
 		`OR1200_DU_DCR0:
 			spr_dat_o = {24'h00_0000, dcr0};
 `endif
 `ifdef OR1200_DU_DCR1
 		`OR1200_DU_DCR1:
 			spr_dat_o = {24'h00_0000, dcr1};
 `endif
 `ifdef OR1200_DU_DCR2
 		`OR1200_DU_DCR2:
 			spr_dat_o = {24'h00_0000, dcr2};
 `endif
 `ifdef OR1200_DU_DCR3
 		`OR1200_DU_DCR3:
 			spr_dat_o = {24'h00_0000, dcr3};
 `endif
 `ifdef OR1200_DU_DCR4
 		`OR1200_DU_DCR4:
 			spr_dat_o = {24'h00_0000, dcr4};
 `endif
 `ifdef OR1200_DU_DCR5
 		`OR1200_DU_DCR5:
 			spr_dat_o = {24'h00_0000, dcr5};
 `endif
 `ifdef OR1200_DU_DCR6
 		`OR1200_DU_DCR6:
 			spr_dat_o = {24'h00_0000, dcr6};
 `endif
 `ifdef OR1200_DU_DCR7
 		`OR1200_DU_DCR7:
 			spr_dat_o = {24'h00_0000, dcr7};
 `endif
 `ifdef OR1200_DU_DMR1
 		`OR1200_DU_DMR1:
 			spr_dat_o = {7'h00, dmr1};
 `endif
 `ifdef OR1200_DU_DMR2
 		`OR1200_DU_DMR2:
 			spr_dat_o = {8'h00, dmr2};
 `endif
 `ifdef OR1200_DU_DWCR0
 		`OR1200_DU_DWCR0:
 			spr_dat_o = dwcr0;
 `endif
 `ifdef OR1200_DU_DWCR1
 		`OR1200_DU_DWCR1:
 			spr_dat_o = dwcr1;
 `endif
 `ifdef OR1200_DU_DSR
 		`OR1200_DU_DSR:
 			spr_dat_o = {18'b0, dsr};
 `endif
 `ifdef OR1200_DU_DRR
 		`OR1200_DU_DRR:
 			spr_dat_o = {18'b0, drr};
 `endif
 `ifdef OR1200_DU_TB_IMPLEMENTED
 		`OR1200_DU_TBADR:
 			spr_dat_o = {24'h000000, tb_wadr};
 		`OR1200_DU_TBIA:
 			spr_dat_o = tbia_dat_o;
 		`OR1200_DU_TBIM:
 			spr_dat_o = tbim_dat_o;
 		`OR1200_DU_TBAR:
 			spr_dat_o = tbar_dat_o;
 		`OR1200_DU_TBTS:
 			spr_dat_o = tbts_dat_o;
 `endif
 		default:
 			spr_dat_o = 32'h0000_0000;
 	endcase
 `endif
 
 //
 // DSR alias
 //
 assign du_dsr = dsr;
 
 `ifdef OR1200_DU_HWBKPTS
 
 //
 // Compare To What (Match Condition 0)
 //
 always @(dcr0 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr0[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond0_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond0_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond0_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond0_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond0_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond0_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond0_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 0)
 //
 always @(dcr0 or dcpu_cycstb_i)
 	case (dcr0[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond0_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond0_stb = 1'b1;		// insn fetch EA
 		default:match_cond0_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 0
 //
 always @(match_cond0_stb or dcr0 or dvr0 or match_cond0_ct)
 	casex ({match_cond0_stb, dcr0[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match0 = 1'b0;
 		4'b1_001: match0 =
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} ==
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 		4'b1_010: match0 = 
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} <
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 		4'b1_011: match0 = 
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} <=
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 		4'b1_100: match0 = 
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} >
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 		4'b1_101: match0 = 
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} >=
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 		4'b1_110: match0 = 
 			({(match_cond0_ct[31] ^ dcr0[`OR1200_DU_DCR_SC]), match_cond0_ct[30:0]} !=
 			 {(dvr0[31] ^ dcr0[`OR1200_DU_DCR_SC]), dvr0[30:0]});
 	endcase
 
 //
 // Watchpoint 0
 //
 always @(dmr1 or match0)
 	case (dmr1[`OR1200_DU_DMR1_CW0])
 		2'b00: wp[0] = match0;
 		2'b01: wp[0] = match0;
 		2'b10: wp[0] = match0;
 		2'b11: wp[0] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 1)
 //
 always @(dcr1 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr1[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond1_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond1_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond1_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond1_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond1_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond1_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond1_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 1)
 //
 always @(dcr1 or dcpu_cycstb_i)
 	case (dcr1[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond1_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond1_stb = 1'b1;		// insn fetch EA
 		default:match_cond1_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 1
 //
 always @(match_cond1_stb or dcr1 or dvr1 or match_cond1_ct)
 	casex ({match_cond1_stb, dcr1[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match1 = 1'b0;
 		4'b1_001: match1 =
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} ==
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 		4'b1_010: match1 = 
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} <
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 		4'b1_011: match1 = 
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} <=
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 		4'b1_100: match1 = 
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} >
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 		4'b1_101: match1 = 
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} >=
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 		4'b1_110: match1 = 
 			({(match_cond1_ct[31] ^ dcr1[`OR1200_DU_DCR_SC]), match_cond1_ct[30:0]} !=
 			 {(dvr1[31] ^ dcr1[`OR1200_DU_DCR_SC]), dvr1[30:0]});
 	endcase
 
 //
 // Watchpoint 1
 //
 always @(dmr1 or match1 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW1])
 		2'b00: wp[1] = match1;
 		2'b01: wp[1] = match1 & wp[0];
 		2'b10: wp[1] = match1 | wp[0];
 		2'b11: wp[1] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 2)
 //
 always @(dcr2 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr2[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond2_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond2_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond2_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond2_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond2_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond2_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond2_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 2)
 //
 always @(dcr2 or dcpu_cycstb_i)
 	case (dcr2[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond2_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond2_stb = 1'b1;		// insn fetch EA
 		default:match_cond2_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 2
 //
 always @(match_cond2_stb or dcr2 or dvr2 or match_cond2_ct)
 	casex ({match_cond2_stb, dcr2[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match2 = 1'b0;
 		4'b1_001: match2 =
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} ==
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 		4'b1_010: match2 = 
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} <
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 		4'b1_011: match2 = 
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} <=
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 		4'b1_100: match2 = 
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} >
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 		4'b1_101: match2 = 
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} >=
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 		4'b1_110: match2 = 
 			({(match_cond2_ct[31] ^ dcr2[`OR1200_DU_DCR_SC]), match_cond2_ct[30:0]} !=
 			 {(dvr2[31] ^ dcr2[`OR1200_DU_DCR_SC]), dvr2[30:0]});
 	endcase
 
 //
 // Watchpoint 2
 //
 always @(dmr1 or match2 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW2])
 		2'b00: wp[2] = match2;
 		2'b01: wp[2] = match2 & wp[1];
 		2'b10: wp[2] = match2 | wp[1];
 		2'b11: wp[2] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 3)
 //
 always @(dcr3 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr3[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond3_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond3_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond3_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond3_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond3_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond3_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond3_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 3)
 //
 always @(dcr3 or dcpu_cycstb_i)
 	case (dcr3[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond3_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond3_stb = 1'b1;		// insn fetch EA
 		default:match_cond3_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 3
 //
 always @(match_cond3_stb or dcr3 or dvr3 or match_cond3_ct)
 	casex ({match_cond3_stb, dcr3[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match3 = 1'b0;
 		4'b1_001: match3 =
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} ==
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 		4'b1_010: match3 = 
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} <
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 		4'b1_011: match3 = 
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} <=
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 		4'b1_100: match3 = 
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} >
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 		4'b1_101: match3 = 
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} >=
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 		4'b1_110: match3 = 
 			({(match_cond3_ct[31] ^ dcr3[`OR1200_DU_DCR_SC]), match_cond3_ct[30:0]} !=
 			 {(dvr3[31] ^ dcr3[`OR1200_DU_DCR_SC]), dvr3[30:0]});
 	endcase
 
 //
 // Watchpoint 3
 //
 always @(dmr1 or match3 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW3])
 		2'b00: wp[3] = match3;
 		2'b01: wp[3] = match3 & wp[2];
 		2'b10: wp[3] = match3 | wp[2];
 		2'b11: wp[3] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 4)
 //
 always @(dcr4 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr4[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond4_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond4_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond4_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond4_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond4_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond4_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond4_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 4)
 //
 always @(dcr4 or dcpu_cycstb_i)
 	case (dcr4[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond4_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond4_stb = 1'b1;		// insn fetch EA
 		default:match_cond4_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 4
 //
 always @(match_cond4_stb or dcr4 or dvr4 or match_cond4_ct)
 	casex ({match_cond4_stb, dcr4[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match4 = 1'b0;
 		4'b1_001: match4 =
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} ==
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 		4'b1_010: match4 = 
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} <
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 		4'b1_011: match4 = 
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} <=
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 		4'b1_100: match4 = 
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} >
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 		4'b1_101: match4 = 
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} >=
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 		4'b1_110: match4 = 
 			({(match_cond4_ct[31] ^ dcr4[`OR1200_DU_DCR_SC]), match_cond4_ct[30:0]} !=
 			 {(dvr4[31] ^ dcr4[`OR1200_DU_DCR_SC]), dvr4[30:0]});
 	endcase
 
 //
 // Watchpoint 4
 //
 always @(dmr1 or match4 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW4])
 		2'b00: wp[4] = match4;
 		2'b01: wp[4] = match4 & wp[3];
 		2'b10: wp[4] = match4 | wp[3];
 		2'b11: wp[4] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 5)
 //
 always @(dcr5 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr5[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond5_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond5_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond5_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond5_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond5_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond5_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond5_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 5)
 //
 always @(dcr5 or dcpu_cycstb_i)
 	case (dcr5[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond5_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond5_stb = 1'b1;		// insn fetch EA
 		default:match_cond5_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 5
 //
 always @(match_cond5_stb or dcr5 or dvr5 or match_cond5_ct)
 	casex ({match_cond5_stb, dcr5[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match5 = 1'b0;
 		4'b1_001: match5 =
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} ==
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 		4'b1_010: match5 = 
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} <
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 		4'b1_011: match5 = 
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} <=
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 		4'b1_100: match5 = 
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} >
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 		4'b1_101: match5 = 
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} >=
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 		4'b1_110: match5 = 
 			({(match_cond5_ct[31] ^ dcr5[`OR1200_DU_DCR_SC]), match_cond5_ct[30:0]} !=
 			 {(dvr5[31] ^ dcr5[`OR1200_DU_DCR_SC]), dvr5[30:0]});
 	endcase
 
 //
 // Watchpoint 5
 //
 always @(dmr1 or match5 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW5])
 		2'b00: wp[5] = match5;
 		2'b01: wp[5] = match5 & wp[4];
 		2'b10: wp[5] = match5 | wp[4];
 		2'b11: wp[5] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 6)
 //
 always @(dcr6 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr6[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond6_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond6_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond6_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond6_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond6_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond6_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond6_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 6)
 //
 always @(dcr6 or dcpu_cycstb_i)
 	case (dcr6[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond6_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond6_stb = 1'b1;		// insn fetch EA
 		default:match_cond6_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 6
 //
 always @(match_cond6_stb or dcr6 or dvr6 or match_cond6_ct)
 	casex ({match_cond6_stb, dcr6[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match6 = 1'b0;
 		4'b1_001: match6 =
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} ==
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 		4'b1_010: match6 = 
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} <
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 		4'b1_011: match6 = 
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} <=
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 		4'b1_100: match6 = 
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} >
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 		4'b1_101: match6 = 
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} >=
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 		4'b1_110: match6 = 
 			({(match_cond6_ct[31] ^ dcr6[`OR1200_DU_DCR_SC]), match_cond6_ct[30:0]} !=
 			 {(dvr6[31] ^ dcr6[`OR1200_DU_DCR_SC]), dvr6[30:0]});
 	endcase
 
 //
 // Watchpoint 6
 //
 always @(dmr1 or match6 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW6])
 		2'b00: wp[6] = match6;
 		2'b01: wp[6] = match6 & wp[5];
 		2'b10: wp[6] = match6 | wp[5];
 		2'b11: wp[6] = 1'b0;
 	endcase
 
 //
 // Compare To What (Match Condition 7)
 //
 always @(dcr7 or id_pc or dcpu_adr_i or dcpu_dat_dc
 	or dcpu_dat_lsu or dcpu_we_i)
 	case (dcr7[`OR1200_DU_DCR_CT])		// synopsys parallel_case
 		3'b001:	match_cond7_ct = id_pc;		// insn fetch EA
 		3'b010:	match_cond7_ct = dcpu_adr_i;	// load EA
 		3'b011:	match_cond7_ct = dcpu_adr_i;	// store EA
 		3'b100:	match_cond7_ct = dcpu_dat_dc;	// load data
 		3'b101:	match_cond7_ct = dcpu_dat_lsu;	// store data
 		3'b110:	match_cond7_ct = dcpu_adr_i;	// load/store EA
 		default:match_cond7_ct = dcpu_we_i ? dcpu_dat_lsu : dcpu_dat_dc;
 	endcase
 
 //
 // When To Compare (Match Condition 7)
 //
 always @(dcr7 or dcpu_cycstb_i)
 	case (dcr7[`OR1200_DU_DCR_CT]) 		// synopsys parallel_case
 		3'b000:	match_cond7_stb = 1'b0;		//comparison disabled
 		3'b001:	match_cond7_stb = 1'b1;		// insn fetch EA
 		default:match_cond7_stb = dcpu_cycstb_i; // any load/store
 	endcase
 
 //
 // Match Condition 7
 //
 always @(match_cond7_stb or dcr7 or dvr7 or match_cond7_ct)
 	casex ({match_cond7_stb, dcr7[`OR1200_DU_DCR_CC]})
 		4'b0_xxx,
 		4'b1_000,
 		4'b1_111: match7 = 1'b0;
 		4'b1_001: match7 =
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} ==
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 		4'b1_010: match7 = 
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} <
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 		4'b1_011: match7 = 
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} <=
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 		4'b1_100: match7 = 
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} >
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 		4'b1_101: match7 = 
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} >=
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 		4'b1_110: match7 = 
 			({(match_cond7_ct[31] ^ dcr7[`OR1200_DU_DCR_SC]), match_cond7_ct[30:0]} !=
 			 {(dvr7[31] ^ dcr7[`OR1200_DU_DCR_SC]), dvr7[30:0]});
 	endcase
 
 //
 // Watchpoint 7
 //
 always @(dmr1 or match7 or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW7])
 		2'b00: wp[7] = match7;
 		2'b01: wp[7] = match7 & wp[6];
 		2'b10: wp[7] = match7 | wp[6];
 		2'b11: wp[7] = 1'b0;
 	endcase
 
 //
 // Increment Watchpoint Counter 0
 //
 always @(wp or dmr2)
 	if (dmr2[`OR1200_DU_DMR2_WCE0])
 		incr_wpcntr0 = |(wp & ~dmr2[`OR1200_DU_DMR2_AWTC]);
 	else
 		incr_wpcntr0 = 1'b0;
 
 //
 // Match Condition Watchpoint Counter 0
 //
 always @(dwcr0)
 	if (dwcr0[`OR1200_DU_DWCR_MATCH] == dwcr0[`OR1200_DU_DWCR_COUNT])
 		wpcntr0_match = 1'b1;
 	else
 		wpcntr0_match = 1'b0;
 
 
 //
 // Watchpoint 8
 //
 always @(dmr1 or wpcntr0_match or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW8])
 		2'b00: wp[8] = wpcntr0_match;
 		2'b01: wp[8] = wpcntr0_match & wp[7];
 		2'b10: wp[8] = wpcntr0_match | wp[7];
 		2'b11: wp[8] = 1'b0;
 	endcase
 
 
 //
 // Increment Watchpoint Counter 1
 //
 always @(wp or dmr2)
 	if (dmr2[`OR1200_DU_DMR2_WCE1])
 		incr_wpcntr1 = |(wp & dmr2[`OR1200_DU_DMR2_AWTC]);
 	else
 		incr_wpcntr1 = 1'b0;
 
 //
 // Match Condition Watchpoint Counter 1
 //
 always @(dwcr1)
 	if (dwcr1[`OR1200_DU_DWCR_MATCH] == dwcr1[`OR1200_DU_DWCR_COUNT])
 		wpcntr1_match = 1'b1;
 	else
 		wpcntr1_match = 1'b0;
 
 //
 // Watchpoint 9
 //
 always @(dmr1 or wpcntr1_match or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW9])
 		2'b00: wp[9] = wpcntr1_match;
 		2'b01: wp[9] = wpcntr1_match & wp[8];
 		2'b10: wp[9] = wpcntr1_match | wp[8];
 		2'b11: wp[9] = 1'b0;
 	endcase
 
 //
 // Watchpoint 10
 //
 always @(dmr1 or dbg_ewt_i or wp)
 	case (dmr1[`OR1200_DU_DMR1_CW10])
 		2'b00: wp[10] = dbg_ewt_i;
 		2'b01: wp[10] = dbg_ewt_i & wp[9];
 		2'b10: wp[10] = dbg_ewt_i | wp[9];
 		2'b11: wp[10] = 1'b0;
 	endcase
 
 `endif
 
 //
 // Watchpoints can cause trap exception
 //
 `ifdef OR1200_DU_HWBKPTS
 assign du_hwbkpt = |(wp & dmr2[`OR1200_DU_DMR2_WGB]) | du_hwbkpt_hold | (dbg_bp_r & ~dsr[`OR1200_DU_DSR_TE]);
 `else
 assign du_hwbkpt = 1'b0;
 `endif
 
 // Hold du_hwbkpt if ex_freeze is active in order to cause trap exception 
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		du_hwbkpt_hold <=  1'b0;
 	else if (du_hwbkpt & ex_freeze)
 		du_hwbkpt_hold <=  1'b1;
 	else if (!ex_freeze)
 		du_hwbkpt_hold <=  1'b0;
 
 `ifdef OR1200_DU_TB_IMPLEMENTED
 //
 // Simple trace buffer
 // (right now hardcoded for Xilinx Virtex FPGAs)
 //
 // Stores last 256 instruction addresses, instruction
 // machine words and ALU results
 //
 
 //
 // Trace buffer write enable
 //
 assign tb_enw = ~ex_freeze & ~((ex_insn[31:26] == `OR1200_OR32_NOP) & ex_insn[16]);
 
 //
 // Trace buffer write address pointer
 //
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		tb_wadr <=  8'h00;
 	else if (tb_enw)
 		tb_wadr <=  tb_wadr + 8'd1;
 
 //
 // Free running counter (time stamp)
 //
 always @(posedge clk or `OR1200_RST_EVENT rst)
 	if (rst == `OR1200_RST_VALUE)
 		tb_timstmp <=  32'h00000000;
 	else if (!dbg_bp_r)
 		tb_timstmp <=  tb_timstmp + 32'd1;
 
 //
 // Trace buffer RAMs
 //
 
 or1200_dpram_256x32 tbia_ram(
 	.clk_a(clk),
 	.rst_a(1'b0),
 	.addr_a(spr_addr[7:0]),
 	.ce_a(1'b1),
 	.oe_a(1'b1),
 	.do_a(tbia_dat_o),
 
 	.clk_b(clk),
 	.rst_b(1'b0),
 	.addr_b(tb_wadr),
 	.di_b(spr_dat_npc),
 	.ce_b(1'b1),
 	.we_b(tb_enw)
 
 );
 
 or1200_dpram_256x32 tbim_ram(
 	.clk_a(clk),
 	.rst_a(1'b0),
 	.addr_a(spr_addr[7:0]),
 	.ce_a(1'b1),
 	.oe_a(1'b1),
 	.do_a(tbim_dat_o),
 	
 	.clk_b(clk),
 	.rst_b(1'b0),
 	.addr_b(tb_wadr),
 	.di_b(ex_insn),
 	.ce_b(1'b1),
 	.we_b(tb_enw)
 );
 
 or1200_dpram_256x32 tbar_ram(
 	.clk_a(clk),
 	.rst_a(1'b0),
 	.addr_a(spr_addr[7:0]),
 	.ce_a(1'b1),
 	.oe_a(1'b1),
 	.do_a(tbar_dat_o),
 	
 	.clk_b(clk),
 	.rst_b(1'b0),
 	.addr_b(tb_wadr),
 	.di_b(rf_dataw),
 	.ce_b(1'b1),
 	.we_b(tb_enw)
 );
 
 or1200_dpram_256x32 tbts_ram(
 	.clk_a(clk),
 	.rst_a(1'b0),
 	.addr_a(spr_addr[7:0]),
 	.ce_a(1'b1),
 	.oe_a(1'b1),
 	.do_a(tbts_dat_o),
 
 	.clk_b(clk),
 	.rst_b(1'b0),
 	.addr_b(tb_wadr),
 	.di_b(tb_timstmp),
 	.ce_b(1'b1),
 	.we_b(tb_enw)
 );
 
 `else
 
 assign tbia_dat_o = 32'h0000_0000;
 assign tbim_dat_o = 32'h0000_0000;
 assign tbar_dat_o = 32'h0000_0000;
 assign tbts_dat_o = 32'h0000_0000;
 
 `endif	// OR1200_DU_TB_IMPLEMENTED
 
 `else	// OR1200_DU_IMPLEMENTED
 
 //
 // When DU is not implemented, drive all outputs as would when DU is disabled
 //
 assign dbg_bp_o = 1'b0;
 assign du_dsr = {`OR1200_DU_DSR_WIDTH{1'b0}};
 assign du_dmr1 = {25{1'b0}};
 assign du_hwbkpt = 1'b0;
 
 //
 // Read DU registers
 //
 `ifdef OR1200_DU_READREGS
 assign spr_dat_o = 32'h0000_0000;
 `ifdef OR1200_DU_UNUSED_ZERO
 `endif
 `endif
 
 `endif
 
 endmodule
 
diff --git a/autotests/input/syntax/vhdl/results/light52_muldiv.vhdl.reference.html b/autotests/input/syntax/vhdl/results/light52_muldiv.vhdl.reference.html index d0cc524e..af2fd1c7 100644 --- a/autotests/input/syntax/vhdl/results/light52_muldiv.vhdl.reference.html +++ b/autotests/input/syntax/vhdl/results/light52_muldiv.vhdl.reference.html @@ -1,253 +1,253 @@ light52_muldiv.vhdl -
+
 --------------------------------------------------------------------------------
 -- light52_muldiv.vhdl -- Simple multiplier/divider module.
 --------------------------------------------------------------------------------
 -- The 8051 mul and div instructions are both unsigned and operands are 8 bit.
 --
 -- This module implements the division as a sequential state machine which takes
 -- 8 cycles to complete. 
 -- The multiplier can be implemented as sequential or as combinational, in which
 -- case it will use a DSP block in those architectures that support it.
 -- No attempt has been made to make this module generic or reusable.
 --
 -- If you want a combinational multiplier but don't want to waste a DSP block 
 -- in this module, you need to modify this file adding whatever synthesis 
 -- pragmas your tool of choice needs.
 --
 -- Note that unlike the division state machine, the combinational product logic
 -- is always operating: when SEQUENTIAL_MULTIPLIER=true, prod_out equals 
 -- data_a * data_b with a latency of 1 clock cycle, and mul_ready is hardwired
 -- to '1'.
 --
 -- FIXME explain division algorithm.
 --------------------------------------------------------------------------------
 -- GENERICS:
 -- 
 -- SEQUENTIAL_MULTIPLIER        -- Sequential vs. combinational multiplier.
 --  When true, a sequential implementation will be used for the multiplier, 
 --  which will usually save a lot of logic or a dedicated multiplier.
 --  When false, a combinational registered multiplier will be used.
 --
 --------------------------------------------------------------------------------
 -- INTERFACE SIGNALS:
 --
 -- clk :            Clock, active rising edge.
 -- reset :          Synchronous reset. Clears only the control registers not
 --                  visible to the programmer -- not the output registers.
 -- 
 -- data_a :         Numerator input, should be connected to the ACC register.
 -- data_b :         Denominator input, should be connected to the B register.
 -- start :          Assert for 1 cycle to start the division state machine
 --                  (and the product if SEQUENTIAL_MULTIPLIER=true);
 -- 
 -- prod_out :       Product output, valid only when mul_ready='1'.
 -- quot_out :       Quotient output, valid only when div_ready='1'.
 -- rem_out :        Remainder output, valid only when div_ready='1'.
 -- div_ov_out :     Division overflow flag, valid only when div_ready='1'.
 -- mul_ov_out :     Product overflow flag, valid only when mul_ready='1'.
 -- 
 -- mul_ready :      Asserted permanently if SEQUENTIAL_MULTIPLIER=false.
 -- div_ready :      Deasserted the cycle after start is asserted.
 --                  Asserted when the division has completed.
 --
 --------------------------------------------------------------------------------
 -- Copyright (C) 2012 Jose A. Ruiz
 --                                                              
 -- This source file may be used and distributed without         
 -- restriction provided that this copyright statement is not    
 -- removed from the file and that any derivative work contains  
 -- the original copyright notice and the associated disclaimer. 
 --                                                              
 -- This source file is free software; you can redistribute it   
 -- and/or modify it under the terms of the GNU Lesser General   
 -- Public License as published by the Free Software Foundation; 
 -- either version 2.1 of the License, or (at your option) any   
 -- later version.                                               
 --                                                              
 -- This source is distributed in the hope that it will be       
 -- useful, but WITHOUT ANY WARRANTY; without even the implied   
 -- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      
 -- PURPOSE.  See the GNU Lesser General Public License for more 
 -- details.                                                     
 --                                                              
 -- You should have received a copy of the GNU Lesser General    
 -- Public License along with this source; if not, download it   
 -- from http://www.opencores.org/lgpl.shtml
 --------------------------------------------------------------------------------
 
 library ieee;
 use ieee.std_logic_1164.all;
 use ieee.numeric_std.all;
 
 use work.light52_pkg.all;
 use work.light52_ucode_pkg.all;
 
 entity light52_muldiv is
     generic (
         SEQUENTIAL_MULTIPLIER : boolean := false
     );
     port(
         clk :                   in std_logic;
         reset :                 in std_logic;
         
         data_a :                in t_byte;
         data_b :                in t_byte;
         start :                 in std_logic;
         
         prod_out :              out t_word;
         quot_out :              out t_byte;
         rem_out :               out t_byte;
         div_ov_out :            out std_logic;
         mul_ov_out :            out std_logic;
         
         mul_ready :             out std_logic;
         div_ready :             out std_logic
     );
 end entity light52_muldiv;
 
 architecture sequential of light52_muldiv is
 
 signal bit_ctr :            integer range 0 to 8;
 
 signal b_shift_reg :        t_word;
 
 signal den_ge_256 :         std_logic;
 signal num_ge_den :         std_logic;
 signal sub_num :            std_logic;
 
 signal denominator :        t_byte;
 signal rem_reg :            t_byte;
 signal quot_reg :           t_byte;
 signal prod_reg :           t_word;
 signal ready :              std_logic;
 
 signal load_regs :          std_logic;
 
 begin
 
 -- Control logic ---------------------------------------------------------------
 
 control_counter: process(clk)
 begin
     if clk'event and clk='1' then
         if reset='1' then
             bit_ctr <= 8;
         else
             if load_regs='1' then
                 bit_ctr <= 0;
             elsif bit_ctr /= 8 then
                 bit_ctr <= bit_ctr + 1;
             end if;
         end if;
     end if;
 end process control_counter;
 
 -- Internal signal ready is asserted after 8 cycles.
 -- The sequential multiplier will use this signal too, IF it takes 8 cycles.
 
 ready <= '1' when bit_ctr >= 8 else '0';
 
 
 ---- Divider logic -------------------------------------------------------------
 
 -- What we do is a simple base-2 'shift-and-subtract' algorithm that takes
 -- 8 cycles to complete. We can get away with this because we deal with unsigned
 -- numbers only.
 
 divider_registers: process(clk)
 begin
     if clk'event and clk='1' then
         -- denominator shift register
         if load_regs='1' then
             b_shift_reg <= "0" & data_b & "0000000";
             -- Division overflow can be determined upon loading B reg data.
             -- OV will be raised only on div-by-zero.
             if data_b=X"00" then
                 div_ov_out <= '1';
             else
                 div_ov_out <= '0';
             end if;
         else
             b_shift_reg <= "0" & b_shift_reg(b_shift_reg'high downto 1);
         end if;
         
         -- numerator register
         if load_regs='1' then 
             rem_reg <= data_a;
         elsif bit_ctr/=8 and sub_num='1' then 
             rem_reg <= rem_reg - denominator;
         end if;
 
         --- quotient register
         if load_regs='1' then
             quot_reg <= (others => '0');
         elsif bit_ctr/=8 then
             quot_reg <= quot_reg(quot_reg'high-1 downto 0) & sub_num;
         end if;
         
         load_regs <= start;
     end if;
 end process divider_registers;
 
 denominator <= b_shift_reg(7 downto 0);
 
 -- The 16-bit comparison between b_shift_reg (denominator) and the zero-extended 
 -- rem_reg (numerator) can be simplified by splitting it in 2: 
 -- If the shifted denominator high byte is not zero, it is >=256...
 den_ge_256 <= '1' when b_shift_reg(15 downto 8) /= X"00" else '0';
 -- ...otherwise we need to compare the low bytes.
 num_ge_den <= '1' when rem_reg >= denominator else '0';
 sub_num <= '1' when den_ge_256='0' and num_ge_den='1' else '0';
 
 
 quot_out <= quot_reg;
 prod_out <= prod_reg;
 rem_out <= rem_reg;
 
 div_ready <= ready;
 
 ---- Multiplier logic ----------------------------------------------------------
 
 ---- Combinational multiplier -----------------------------
 multiplier_combinational: if not SEQUENTIAL_MULTIPLIER generate
 
 registered_combinational_multiplier:process(clk)
 begin
     if clk'event and clk='1' then
         prod_reg <= data_a * data_b; -- t_byte is unsigned
     end if;
 end process registered_combinational_multiplier;
 
 -- The multiplier output is valid in the cycle after the operands are loaded,
 -- so by the time MUL is executed it's already done.
 mul_ready <= '1';
 
 mul_ov_out <= '1' when prod_reg(15 downto 8)/=X"00" else '0';
 prod_out <= prod_reg;
 
 end generate multiplier_combinational;
 
 ---- Sequential multiplier --------------------------------
 multiplier_sequential: if SEQUENTIAL_MULTIPLIER generate
 
 assert false
 report "Sequential multiplier implementation not done yet."&
        " Use combinational implementation."
 severity failure;
 
 end generate multiplier_sequential;
 
 end sequential;
 
diff --git a/autotests/input/syntax/vhdl/results/light52_tb.vhdl.reference.html b/autotests/input/syntax/vhdl/results/light52_tb.vhdl.reference.html index 0fac92bf..20a6209d 100644 --- a/autotests/input/syntax/vhdl/results/light52_tb.vhdl.reference.html +++ b/autotests/input/syntax/vhdl/results/light52_tb.vhdl.reference.html @@ -1,194 +1,194 @@ light52_tb.vhdl -
+
 --------------------------------------------------------------------------------
 -- light52_tb.vhdl -- 
 --------------------------------------------------------------------------------
 -- This test bench simulates the execution of some program (whose object code
 -- is in package obj_code_pkg, in the form of a memory init constant) and logs
 -- the execution to a text file called 'hw_sim_log.txt' (light52_tb_pkg.vhdl).
 --
 -- This test bench does no actual tests on the core. Instead, the simulation log
 -- is meant to be matched against the simulation log produced by running the 
 -- same program on the software simulator B51 (also included with this project).
 -- 
 -- This will catch errors in the implementation of the CPU if the simulated
 -- program has anough coverage -- the opcode tester is meant to cover all CPU
 -- opcodes in many (not all) of their corner cases.
 -- This scheme will not help in catching errors in the peripheral modules, 
 -- mainly because the current version of B51 does not simulate them.
 --
 --------------------------------------------------------------------------------
 -- Copyright (C) 2012 Jose A. Ruiz
 --                                                              
 -- This source file may be used and distributed without         
 -- restriction provided that this copyright statement is not    
 -- removed from the file and that any derivative work contains  
 -- the original copyright notice and the associated disclaimer. 
 --                                                              
 -- This source file is free software; you can redistribute it   
 -- and/or modify it under the terms of the GNU Lesser General   
 -- Public License as published by the Free Software Foundation; 
 -- either version 2.1 of the License, or (at your option) any   
 -- later version.                                               
 --                                                              
 -- This source is distributed in the hope that it will be       
 -- useful, but WITHOUT ANY WARRANTY; without even the implied   
 -- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR      
 -- PURPOSE.  See the GNU Lesser General Public License for more 
 -- details.                                                     
 --                                                              
 -- You should have received a copy of the GNU Lesser General    
 -- Public License along with this source; if not, download it   
 -- from http://www.opencores.org/lgpl.shtml
 --------------------------------------------------------------------------------
 
 
 library ieee;
 use ieee.std_logic_1164.all;
 use ieee.std_logic_arith.all;
 use ieee.std_logic_unsigned.all;
 use std.textio.all;
 
 use work.light52_pkg.all;
 use work.obj_code_pkg.all;
 use work.light52_tb_pkg.all;
 use work.txt_util.all;
 
 entity light52_tb is
 generic (BCD : boolean := true);
 end;
 
 
 architecture testbench of light52_tb is
 
 --------------------------------------------------------------------------------
 -- Simulation parameters
 -- FIXME these should be in parameter package
 
 -- Simulated clock period is the same as the usual target, the DE-1 board
 constant T : time := 20 ns; -- 50MHz
 constant SIMULATION_LENGTH : integer := 400000;
 
 --------------------------------------------------------------------------------
 -- MPU interface 
 
 signal clk :                std_logic := '0';
 signal reset :              std_logic := '1';
 
 signal p0_out :             std_logic_vector(7 downto 0);
 signal p1_out :             std_logic_vector(7 downto 0);
 signal p2_in :              std_logic_vector(7 downto 0);
 signal p3_in :              std_logic_vector(7 downto 0);
 
 signal external_irq :       std_logic_vector(7 downto 0);
 
 signal txd, rxd :           std_logic;
 
 --------------------------------------------------------------------------------
 -- Logging signals & simulation control 
 
 -- Asserted high to disable the clock and terminate the simulation.
 signal done :               std_logic := '0';
 
 -- Log file
 file log_file: TEXT open write_mode is "hw_sim_log.txt";
 -- Console output log file
 file con_file: TEXT open write_mode is "hw_sim_console_log.txt";
 -- Info record needed by the logging fuctions
 signal log_info :           t_log_info;
 
 begin
 
 ---- UUT instantiation ---------------------------------------------------------
 
 uut: entity work.light52_mcu
     generic map (
         IMPLEMENT_BCD_INSTRUCTIONS => BCD,
         CODE_ROM_SIZE =>    work.obj_code_pkg.XCODE_SIZE,
         XDATA_RAM_SIZE =>   work.obj_code_pkg.XDATA_SIZE,
         OBJ_CODE =>         work.obj_code_pkg.object_code
     )
     port map (
         clk             => clk,
         reset           => reset,
         
         txd             => txd,
         rxd             => rxd,
         
         external_irq    => external_irq,
                 
         p0_out          => p0_out,
         p1_out          => p1_out,
         p2_in           => p2_in,
         p3_in           => p3_in
     );
     
     -- UART is looped back in the test bench.
     rxd <= txd;
     
     -- I/O ports are looped back and otherwise unused.
     p2_in <= p0_out;
     p3_in <= p1_out;
     
     -- External IRQ inputs are tied to port P1 for test purposes
     external_irq <= p1_out;
 
     ---- Master clock: free running clock used as main module clock ------------
     run_master_clock: process(done, clk)
     begin
         if done = '0' then
             clk <= not clk after T/2;
         end if;
     end process run_master_clock;
 
 
     ---- Main simulation process: reset MCU and wait for fixed period ----------
 
     drive_uut: process
     begin
         -- Leave reset asserted for a few clock cycles...
         reset <= '1';
         wait for T*4;
         reset <= '0';
         
         -- ...and wait for the test to hit a termination condition (evaluated by
         -- function log_cpu_activity) or to just timeout.
         wait for T*SIMULATION_LENGTH;
 
         -- If we arrive here, the simulation timed out (termination conditions
         -- trigger a failed assertion).
         -- So print a timeout message and quit.
         print("TB timed out.");
         done <= '1';
         wait;
         
     end process drive_uut;
 
 
     -- Logging process: launch logger functions --------------------------------
     log_execution: process
     begin
         -- Log cpu activity until done='1'.
         log_cpu_activity(clk, reset, done, "/uut",
                          log_info, work.obj_code_pkg.XCODE_SIZE, "log_info", 
                          X"0000", log_file, con_file);
         
         -- Flush console log file when finished.
         log_flush_console(log_info, con_file);
         
         wait;
     end process log_execution;
 
 end architecture testbench;
 
diff --git a/autotests/src/katedocument_test.cpp b/autotests/src/katedocument_test.cpp index dd7eb535..f98254da 100644 --- a/autotests/src/katedocument_test.cpp +++ b/autotests/src/katedocument_test.cpp @@ -1,462 +1,462 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Dominik Haumann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "katedocument_test.h" #include "moc_katedocument_test.cpp" #include #include #include #include #include #include #include #include ///TODO: is there a FindValgrind cmake command we could use to /// define this automatically? // comment this out and run the test case with: // valgrind --tool=callgrind --instr-atstart=no ./katedocument_test testSetTextPerformance // or similar // // #define USE_VALGRIND #ifdef USE_VALGRIND #include #endif using namespace KTextEditor; QTEST_MAIN(KateDocumentTest) class MovingRangeInvalidator : public QObject { Q_OBJECT public: explicit MovingRangeInvalidator(QObject *parent = nullptr) : QObject(parent) { } void addRange(MovingRange *range) { m_ranges << range; } QList ranges() const { return m_ranges; } public Q_SLOTS: void aboutToInvalidateMovingInterfaceContent() { qDeleteAll(m_ranges); m_ranges.clear(); } private: QList m_ranges; }; KateDocumentTest::KateDocumentTest() : QObject() { } KateDocumentTest::~KateDocumentTest() { } void KateDocumentTest::initTestCase() { KTextEditor::EditorPrivate::enableUnitTestMode(); } // tests: // KTextEditor::DocumentPrivate::insertText with word wrap enabled. It is checked whether the // text is correctly wrapped and whether the moving cursors maintain the correct // position. // see also: http://bugs.kde.org/show_bug.cgi?id=168534 void KateDocumentTest::testWordWrap() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(true); doc.setWordWrapAt(80); const QString content = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 ........8"); // space after 7 is now kept // else we kill indentation... const QString firstWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 \n....x....8"); // space after 6 is now kept // else we kill indentation... const QString secondWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 \n....ooooooooooo....7 ....x....8"); doc.setText(content); MovingCursor *c = doc.newMovingCursor(Cursor(0, 75), MovingCursor::MoveOnInsert); QCOMPARE(doc.text(), content); QCOMPARE(c->toCursor(), Cursor(0, 75)); // type a character at (0, 75) doc.insertText(c->toCursor(), QLatin1String("x")); QCOMPARE(doc.text(), firstWrap); QCOMPARE(c->toCursor(), Cursor(1, 5)); // set cursor to (0, 65) and type "ooooooooooo" c->setPosition(Cursor(0, 65)); doc.insertText(c->toCursor(), QLatin1String("ooooooooooo")); QCOMPARE(doc.text(), secondWrap); QCOMPARE(c->toCursor(), Cursor(1, 15)); } void KateDocumentTest::testReplaceQStringList() { KTextEditor::DocumentPrivate doc(false, false); doc.setWordWrap(false); doc.setText(QLatin1String("asdf\n" "foo\n" "foo\n" "bar\n")); - doc.replaceText(Range(1, 0, 3, 0), QStringList() << "new" << "text" << "", false); + doc.replaceText(Range(1, 0, 3, 0), { "new", "text", "" }, false); QCOMPARE(doc.text(), QLatin1String("asdf\n" "new\n" "text\n" "bar\n")); } void KateDocumentTest::testMovingInterfaceSignals() { KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate; QSignalSpy aboutToDeleteSpy(doc, SIGNAL(aboutToDeleteMovingInterfaceContent(KTextEditor::Document*))); QSignalSpy aboutToInvalidateSpy(doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*))); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 0); QCOMPARE(aboutToDeleteSpy.count(), 0); QTemporaryFile f; f.open(); doc->openUrl(QUrl::fromLocalFile(f.fileName())); QCOMPARE(doc->revision(), qint64(0)); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToInvalidateSpy.count(), 2); QCOMPARE(aboutToDeleteSpy.count(), 0); doc->documentReload(); QCOMPARE(doc->revision(), qint64(0)); QCOMPARE(aboutToInvalidateSpy.count(), 4); //TODO: gets emitted once in closeFile and once in openFile - is that OK? QCOMPARE(aboutToDeleteSpy.count(), 0); delete doc; QCOMPARE(aboutToInvalidateSpy.count(), 4); QCOMPARE(aboutToDeleteSpy.count(), 1); } void KateDocumentTest::testSetTextPerformance() { const int lines = 150; const int columns = 80; const int rangeLength = 4; const int rangeGap = 1; Q_ASSERT(columns % (rangeLength + rangeGap) == 0); KTextEditor::DocumentPrivate doc; MovingRangeInvalidator invalidator; connect(&doc, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), &invalidator, SLOT(aboutToInvalidateMovingInterfaceContent())); QString text; QVector ranges; ranges.reserve(lines * columns / (rangeLength + rangeGap)); const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); for (int c = 0; c < columns; c += rangeLength + rangeGap) { ranges << Range(l, c, l, c + rangeLength); } } // replace QBENCHMARK { // init doc.setText(text); foreach (const Range &range, ranges) { invalidator.addRange(doc.newMovingRange(range)); } QCOMPARE(invalidator.ranges().size(), ranges.size()); #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.setText(text); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif QCOMPARE(doc.text(), text); QVERIFY(invalidator.ranges().isEmpty()); } } void KateDocumentTest::testRemoveTextPerformance() { const int lines = 5000; const int columns = 80; KTextEditor::DocumentPrivate doc; QString text; const QString line = QString().fill('a', columns); for (int l = 0; l < lines; ++l) { text.append(line); text.append('\n'); } doc.setText(text); // replace QBENCHMARK_ONCE { #ifdef USE_VALGRIND CALLGRIND_START_INSTRUMENTATION #endif doc.editStart(); doc.removeText(doc.documentRange()); doc.editEnd(); #ifdef USE_VALGRIND CALLGRIND_STOP_INSTRUMENTATION #endif } } void KateDocumentTest::testForgivingApiUsage() { KTextEditor::DocumentPrivate doc; QVERIFY(doc.isEmpty()); QVERIFY(doc.replaceText(Range(0, 0, 100, 100), "asdf")); QCOMPARE(doc.text(), QString("asdf")); QCOMPARE(doc.lines(), 1); QVERIFY(doc.replaceText(Range(2, 5, 2, 100), "asdf")); QCOMPARE(doc.lines(), 3); QCOMPARE(doc.text(), QLatin1String("asdf\n\n asdf")); QVERIFY(doc.removeText(Range(0, 0, 1000, 1000))); QVERIFY(doc.removeText(Range(0, 0, 0, 100))); QVERIFY(doc.isEmpty()); doc.insertText(Cursor(100, 0), "foobar"); QCOMPARE(doc.line(100), QString("foobar")); doc.setText("nY\nnYY\n"); QVERIFY(doc.removeText(Range(0, 0, 0, 1000))); } void KateDocumentTest::testReplaceTabs() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); auto reset = [&]() { doc.setText(" Hi!"); view->setCursorPosition(Cursor(0, 0)); }; doc.setHighlightingMode ("C++"); doc.config()->setTabWidth(4); doc.config()->setIndentationMode("cppstyle"); // no replace tabs, no indent pasted text doc.setConfigValue("replace-tabs", false); doc.setConfigValue("indent-pasted-text", false); reset(); doc.insertText(Cursor(0, 0), "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.paste(view, "some\ncode\n 3\nhi\n"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n 3\nhi\n Hi!")); // now, enable replace tabs doc.setConfigValue("replace-tabs", true); reset(); doc.insertText(Cursor(0, 0), "\t"); // calling insertText does not replace tabs QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); reset(); doc.typeChars(view, "\t"); // typing text replaces tabs QCOMPARE(doc.text(), QStringLiteral(" Hi!")); reset(); doc.paste(view, "\t"); // pasting text does not with indent-pasted off QCOMPARE(doc.text(), QStringLiteral("\t Hi!")); doc.setConfigValue("indent-pasted-text", true); doc.setText("int main() {\n \n}"); view->setCursorPosition(Cursor(1, 4)); doc.paste(view, "\tHi"); // ... and it still does not with indent-pasted on. // This behaviour is up to discussion. QCOMPARE(doc.text(), QStringLiteral("int main() {\n Hi\n}")); reset(); doc.paste(view, "some\ncode\n 3\nhi"); QCOMPARE(doc.text(), QStringLiteral("some\ncode\n3\nhi Hi!")); } /** * Provides slots to check data sent in specific signals. Slot names are derived from corresponding test names. */ class SignalHandler : public QObject { Q_OBJECT public Q_SLOTS: void slotMultipleLinesRemoved(KTextEditor::Document *, const KTextEditor::Range &, const QString &oldText) { QCOMPARE(oldText, QString("line2\nline3\n")); } void slotNewlineInserted(KTextEditor::Document *, const KTextEditor::Range &range) { QCOMPARE(range, Range(Cursor(1, 4), Cursor(2, 0))); } }; void KateDocumentTest::testRemoveMultipleLines() { KTextEditor::DocumentPrivate doc; doc.setText("line1\n" "line2\n" "line3\n" "line4\n"); SignalHandler handler; connect(&doc, &KTextEditor::DocumentPrivate::textRemoved, &handler, &SignalHandler::slotMultipleLinesRemoved); doc.removeText(Range(1, 0, 3, 0)); } void KateDocumentTest::testInsertNewline() { KTextEditor::DocumentPrivate doc; doc.setText("this is line\n" "this is line2\n"); SignalHandler handler; connect(&doc, SIGNAL(textInserted(KTextEditor::Document*,KTextEditor::Range)), &handler, SLOT(slotNewlineInserted(KTextEditor::Document*,KTextEditor::Range))); doc.editWrapLine(1, 4); } void KateDocumentTest::testInsertAfterEOF() { KTextEditor::DocumentPrivate doc; doc.setText("line0\n" "line1"); const QString input = QLatin1String("line3\n" "line4"); const QString expected = QLatin1String("line0\n" "line1\n" "\n" "line3\n" "line4"); doc.insertText(KTextEditor::Cursor(3, 0), input); QCOMPARE(doc.text(), expected); } // we have two different ways of creating the checksum: // in KateFileLoader and KTextEditor::DocumentPrivate::createDigest. Make // sure, these two implementations result in the same checksum. void KateDocumentTest::testDigest() { // Git hash of test file (git hash-object data/md5checksum.txt): const QByteArray gitHash = "696e6d35a5d9cc28d16e56bdcb2d2a88126b814e"; // QCryptographicHash is used, therefore we need fromHex here const QByteArray fileDigest = QByteArray::fromHex(gitHash); // make sure, Kate::TextBuffer and KTextEditor::DocumentPrivate::createDigest() equal KTextEditor::DocumentPrivate doc; doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR"md5checksum.txt"))); const QByteArray bufferDigest(doc.checksum()); QVERIFY(doc.createDigest()); const QByteArray docDigest(doc.checksum()); QCOMPARE(bufferDigest, fileDigest); QCOMPARE(docDigest, fileDigest); } void KateDocumentTest::testDefStyleNum() { KTextEditor::DocumentPrivate doc; doc.setText("foo\nbar\nasdf"); QCOMPARE(doc.defStyleNum(0, 0), 0); } void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); const uint surrogateUcs4String[] = { 0x1f346, '\n', 0x1f346, 0 }; const auto surrogateString = QString::fromUcs4(surrogateUcs4String); doc.typeChars(view, surrogateString); QCOMPARE(doc.text(), surrogateString); } void KateDocumentTest::testRemoveComposedCharacters() { KTextEditor::DocumentPrivate doc; auto view = static_cast(doc.createView(nullptr)); doc.setText(QString::fromUtf8("व्यक्तियों")); doc.del(view, Cursor(0, 0)); QCOMPARE(doc.text(), QString::fromUtf8(("क्तियों"))); doc.backspace(view, Cursor(0, 7)); QCOMPARE(doc.text(), QString::fromUtf8(("क्ति"))); } #include "katedocument_test.moc" diff --git a/autotests/src/kateencodingtest.cpp b/autotests/src/kateencodingtest.cpp index 06d7c4f5..84a86946 100644 --- a/autotests/src/kateencodingtest.cpp +++ b/autotests/src/kateencodingtest.cpp @@ -1,64 +1,63 @@ /* This file is part of the Kate project. * * Copyright (C) 2010 Christoph Cullmann * Copyright (C) 2010 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include "katetextbuffer.h" #include -#include int main(int argc, char *argv[]) { // construct core app QCoreApplication app(argc, argv); // test mode KTextEditor::EditorPrivate::enableUnitTestMode(); // get arguments QString encoding = app.arguments().at(1); QString inFile = app.arguments().at(2); QString outFile = app.arguments().at(3); Kate::TextBuffer buffer(nullptr); // set codec buffer.setFallbackTextCodec(QTextCodec::codecForName("ISO 8859-15")); buffer.setTextCodec(QTextCodec::codecForName(encoding.toLatin1())); // switch to Mac EOL, this will test eol detection, as files are normal unix or dos buffer.setEndOfLineMode(Kate::TextBuffer::eolMac); // load file bool encodingErrors = false; bool tooLongLines = false; int longestLineLoaded; if (!buffer.load(inFile, encodingErrors, tooLongLines, longestLineLoaded, false) || encodingErrors) { return 1; } // save file if (!buffer.save(outFile)) { return 1; } return 0; } diff --git a/autotests/src/kateview_test.cpp b/autotests/src/kateview_test.cpp index aab724e9..b34a2211 100644 --- a/autotests/src/kateview_test.cpp +++ b/autotests/src/kateview_test.cpp @@ -1,404 +1,395 @@ /* This file is part of the KDE libraries Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateview_test.h" #include "moc_kateview_test.cpp" #include #include #include #include #include #include #include #include #include using namespace KTextEditor; QTEST_MAIN(KateViewTest) KateViewTest::KateViewTest() : QObject() { KTextEditor::EditorPrivate::enableUnitTestMode(); } KateViewTest::~KateViewTest() { } void KateViewTest::testCoordinatesToCursor() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("Hi World!\nHi\n"); KTextEditor::View* view1 = static_cast(doc.createView(nullptr)); view1->show(); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() // random position view1->setCursorPosition(KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), KTextEditor::Cursor(0, 3)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); // end of line view1->setCursorPosition(KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), KTextEditor::Cursor(0, 9)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); // empty line view1->setCursorPosition(KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), KTextEditor::Cursor(2, 0)); QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); // same test again, but with message widget on top visible KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); doc.postMessage(message); // wait 500ms until show animation is finished, so the message widget is visible QTest::qWait(500); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); // behind end of line should give an invalid cursor QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); } void KateViewTest::testCursorToCoordinates() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("int a;"); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setDynWordWrap(true); view->show(); // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 view->cursorToCoordinate(Cursor(0, 0)); view->cursorToCoordinate(Cursor(1, 0)); view->cursorToCoordinate(Cursor(-1, 0)); } void KateViewTest::testReloadMultipleViews() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); const QString line = "const char* foo = \"asdf\"\n"; for (int i = 0; i < 200; ++i) { stream << line; } stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->show(); view2->show(); QCOMPARE(doc.views().count(), 2); QVERIFY(doc.documentReload()); } void KateViewTest::testTabCursorOnReload() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "\tfoo\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); const KTextEditor::Cursor cursor(0, 4); view->setCursorPosition(cursor); QCOMPARE(view->cursorPosition(), cursor); QVERIFY(doc.documentReload()); QCOMPARE(view->cursorPosition(), cursor); } void KateViewTest::testLowerCaseBlockSelection() { // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 KTextEditor::DocumentPrivate doc; doc.setText("nY\nnYY\n"); KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); view1->setBlockSelection(true); view1->setSelection(Range(0, 1, 1, 3)); view1->lowercase(); QCOMPARE(doc.text(), QString("ny\nnyy\n")); } void KateViewTest::testSelection() { // see also: https://bugs.kde.org/show_bug.cgi?id=277422 // wrong behavior before: // Open file with text // click at end of some line (A) and drag to right, i.e. without selecting anything // click somewhere else (B) // shift click to another place (C) // => expected: selection from B to C // => actual: selection from A to C QTemporaryFile file("XXXXXX.txt"); file.open(); QTextStream stream(&file); stream << "A\n" << "B\n" << "C"; stream << flush; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->resize(100, 200); view->show(); QObject *internalView = nullptr; foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { internalView = child; break; } } QVERIFY(internalView); const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); // click after A QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); // drag to right QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(0, 1)); QVERIFY(!view->selection()); // click after C QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier)); QCOMPARE(view->cursorPosition(), Cursor(2, 1)); // shift+click after B QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCoreApplication::sendEvent(internalView, new QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier)); QCOMPARE(view->cursorPosition(), Cursor(1, 1)); QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); } void KateViewTest::testKillline() { KTextEditor::DocumentPrivate doc; - doc.insertLines(0, QStringList() - << "foo" - << "bar" - << "baz" - ); + doc.insertLines(0, { "foo", "bar", "baz" }); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); doc.clear(); QVERIFY(doc.isEmpty()); - doc.insertLines(0, QStringList() - << "foo" - << "bar" - << "baz" - << "xxx" - ); + doc.insertLines(0, { "foo", "bar", "baz", "xxx" }); view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); view->shiftDown(); view->killLine(); QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); } void KateViewTest::testFoldFirstLine() { QTemporaryFile file("XXXXXX.cpp"); file.open(); QTextStream stream(&file); stream << "/**\n" << " * foo\n" << " */\n" << "\n" << "int main() {}\n"; file.close(); KTextEditor::DocumentPrivate doc; QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); QCOMPARE(doc.highlightingMode(), QString("C++")); KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); view->config()->setFoldFirstLine(false); view->setCursorPosition({4, 0}); // initially, nothing is folded QVERIFY(view->textFolding().isLineVisible(1)); // now change the config, and expect the header to be folded view->config()->setFoldFirstLine(true); qint64 foldedRangeId = 0; QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); // now unfold the range QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); QVERIFY(view->textFolding().isLineVisible(1)); // and save the file, we do not expect the folding to change then doc.setModified(true); doc.saveFile(); QVERIFY(view->textFolding().isLineVisible(1)); // now reload the document, nothing should change doc.setModified(false); QVERIFY(doc.documentReload()); QVERIFY(view->textFolding().isLineVisible(1)); } // test for bug https://bugs.kde.org/374163 void KateViewTest::testDragAndDrop() { KTextEditor::DocumentPrivate doc(false, false); doc.setText("line0\n" "line1\n" "line2\n" "\n" "line4"); KTextEditor::View* view = static_cast(doc.createView(nullptr)); view->show(); view->resize(400, 300); QWidget *internalView = nullptr; foreach (QObject* child, view->children()) { if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { internalView = qobject_cast(child); break; } } QVERIFY(internalView); // select "line1\n" view->setSelection(Range(1, 0, 2, 0)); QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); - QTest::qWaitForWindowExposed(view); + QVERIFY(QTest::qWaitForWindowExposed(view)); const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); // now drag and drop selected text to Cursor(3, 0) QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &pressEvent); // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to // break out of the blocking event queue, see (*) QTimer::singleShot(50, [&](){ QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent); QCoreApplication::sendEvent(internalView, &releaseEvent); }); // (*) this somehow blocks... QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QCoreApplication::sendEvent(internalView, &moveEvent1); QTest::qWait(100); // final tests of dragged text QCOMPARE(doc.text(), QString("line0\n" "line2\n" "line1\n" "\n" "line4")); QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); } // kate: indent-mode cstyle; indent-width 4; replace-tabs on; diff --git a/autotests/src/scripting_test.cpp b/autotests/src/scripting_test.cpp index 63da86a7..f54c1fd6 100644 --- a/autotests/src/scripting_test.cpp +++ b/autotests/src/scripting_test.cpp @@ -1,75 +1,74 @@ /** * This file is part of the KDE project * * Copyright (C) 2013 Gerald Senarclens de Grancy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * */ //BEGIN Includes #include "scripting_test.h" #include "kateview.h" #include "katedocument.h" #include "kateconfig.h" #include "katecmd.h" #include "kateglobal.h" #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include "script_test_base.h" #include "testutils.h" QTEST_MAIN(ScriptingTest) #define FAILURE( test, comment ) qMakePair( (test), (comment) ) void ScriptingTest::initTestCase() { ScriptTestBase::initTestCase(); m_section = "scripting"; m_script_dir = ""; } void ScriptingTest::bugs_data() { getTestData("bugs"); } void ScriptingTest::bugs() { runTest(ExpectedFailures()); } diff --git a/autotests/src/vimode/completion.cpp b/autotests/src/vimode/completion.cpp index 216b553f..30471ecc 100644 --- a/autotests/src/vimode/completion.cpp +++ b/autotests/src/vimode/completion.cpp @@ -1,610 +1,610 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "completion.h" #include "fakecodecompletiontestmodel.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KTextEditor; using KateVi::Mappings; QTEST_MAIN(CompletionTest) //BEGIN: VimCodeCompletionTestModel VimCodeCompletionTestModel::VimCodeCompletionTestModel(KTextEditor::View *parent) : KTextEditor::CodeCompletionModel(parent) { setRowCount(3); cc()->setAutomaticInvocationEnabled(true); // It would add additional items and we don't want that in tests cc()->unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); cc()->registerCompletionModel(this); } QVariant VimCodeCompletionTestModel::data(const QModelIndex &index, int role) const { // Order is important, here, as the completion widget seems to do its own sorting. const char *completions[] = { "completion1", "completion2", "completion3" }; if (role == Qt::DisplayRole) { if (index.column() == Name) { return QString(completions[index.row()]); } } return QVariant(); } CodeCompletionInterface * VimCodeCompletionTestModel::cc() const { return dynamic_cast(const_cast(QObject::parent())); } //END: VimCodeCompletionTestModel //BEGIN: CodeCompletionInterface FailTestOnInvocationModel::FailTestOnInvocationModel(KTextEditor::View *parent) : KTextEditor::CodeCompletionModel(parent) { setRowCount(3); cc()->setAutomaticInvocationEnabled(true); // It would add additional items and we don't want that in tests. cc()->unregisterCompletionModel(EditorPrivate::self()->wordCompletionModel()); cc()->registerCompletionModel(this); } QVariant FailTestOnInvocationModel::data(const QModelIndex &index, int role) const { Q_UNUSED(index); Q_UNUSED(role); failTest(); return QVariant(); } void FailTestOnInvocationModel::failTest() const { QFAIL("Shouldn't be invoking me!"); } CodeCompletionInterface * FailTestOnInvocationModel::cc() const { return dynamic_cast(const_cast(QObject::parent())); } //END: CodeCompletionInterface //BEGIN: CompletionTest void CompletionTest::FakeCodeCompletionTests() { // Test that FakeCodeCompletionTestModel behaves similar to the code-completion in e.g. KDevelop. const bool oldStealKeys = KateViewConfig::global()->viInputModeStealKeys(); KateViewConfig::global()->setViInputModeStealKeys(true); // For Ctrl-P, Ctrl-N etc ensureKateViewVisible(); // KTextEditor::ViewPrivate needs to be visible for the completion widget. FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); kate_view->registerCompletionModel(fakeCodeCompletionModel); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); DoTest("", "i\\ctrl-p\\enter", "completionC"); DoTest("", "i\\ctrl-p\\ctrl-p\\enter", "completionB"); DoTest("", "i\\ctrl-p\\ctrl-p\\ctrl-p\\enter", "completionA"); DoTest("", "i\\ctrl-p\\ctrl-p\\ctrl-p\\ctrl-p\\enter", "completionC"); // If no word before cursor, don't delete any text. BeginTest(""); clearTrackedDocumentChanges(); TestPressKey("i\\ctrl- \\enter"); QCOMPARE(m_docChanges.length(), 1); FinishTest("completionA"); // Apparently, we must delete the word before the cursor upon completion // (even if we replace it with identical text!) BeginTest("compl"); TestPressKey("ea"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 5))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 11))); QCOMPARE(m_docChanges[1].newText(), QString("completionA")); FinishTest("completionA"); // A "word" is currently alphanumeric, plus underscore. - fakeCodeCompletionModel->setCompletions(QStringList() << "w_123completion"); + fakeCodeCompletionModel->setCompletions({ "w_123completion" }); BeginTest("(w_123"); TestPressKey("ea"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); FinishTest("(w_123completion"); // "Removing tail on complete" is apparently done in three stages: // delete word up to the cursor; insert new word; then delete remainder. fakeCodeCompletionModel->setRemoveTailOnComplete(true); BeginTest("(w_123comp"); TestPressKey("6li"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 16), Cursor(0, 20))); FinishTest("(w_123completion"); // If we don't remove tail, just delete up to the cursor and insert. fakeCodeCompletionModel->setRemoveTailOnComplete(false); BeginTest("(w_123comp"); TestPressKey("6li"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 1), Cursor(0, 6))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 1), Cursor(0, 16))); QCOMPARE(m_docChanges[1].newText(), QString("w_123completion")); FinishTest("(w_123completioncomp"); // If no opening bracket after the cursor, a function taking no arguments // is added as "function()", and the cursor placed after the closing ")". // The addition of "function()" is done in two steps: first "function", then "()". BeginTest("object->"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("$a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall()X"); // If no opening bracket after the cursor, a function taking at least one argument // is added as "function()", and the cursor placed after the opening "(". // The addition of "function()" is done in two steps: first "function", then "()". qDebug() << "Fleep"; BeginTest("object->"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("$a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall(X)"); // If there is an opening bracket after the cursor, we merge the function call // with that. // Even if the function takes no arguments, we still place the cursor after the opening bracket, // in contrast to the case where there is no opening bracket after the cursor. // No brackets are added. No removals occur if there is no word before the cursor. BeginTest("object->("); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f(i\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall(X"); // There can't be any non-whitespace between cursor position and opening bracket, though! BeginTest("object->|( ("); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall()X|( ("); // Whitespace before the bracket is fine, though. BeginTest("object-> (<-Cursor here!"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Be careful with positioning the cursor if we delete leading text! BeginTest("object-> (<-Cursor here!"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>afunct"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 13))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // If we're removing tail on complete, it's whether there is a suitable opening // bracket *after* the word (not the cursor) that's important. BeginTest("object->function (<-Cursor here!"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall()"); + fakeCodeCompletionModel->setCompletions({ "functionCall()" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("12li"); // Start inserting before the "t" in "function" clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 12))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); qDebug() << "m_docChanges[2].changeRange(): " << m_docChanges[2].changeRange(); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 20), Cursor(0, 24))); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Repeat of bracket-merging stuff, this time for functions that take at least one argument. BeginTest("object->("); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f(i\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); qDebug() << "Range: " << m_docChanges[0].changeRange(); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall(X"); // There can't be any non-whitespace between cursor position and opening bracket, though! BeginTest("object->|( ("); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 20), Cursor(0, 22))); QCOMPARE(m_docChanges[1].newText(), QString("()")); TestPressKey("X"); FinishTest("object->functionCall(X)|( ("); // Whitespace before the bracket is fine, though. BeginTest("object-> (<-Cursor here!"); qDebug() << "NooooO"; - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>a\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 1); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[0].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Be careful with positioning the cursor if we delete leading text! BeginTest("object-> (<-Cursor here!"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("f>afunct"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 2); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 13))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // If we're removing tail on complete, it's whether there is a suitable opening // bracket *after* the word (not the cursor) that's important. BeginTest("object->function (<-Cursor here!"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...)"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...)" }); fakeCodeCompletionModel->setRemoveTailOnComplete(true); clearTrackedDocumentChanges(); TestPressKey("12li"); // Start inserting before the "t" in "function" clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 8), Cursor(0, 12))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 8), Cursor(0, 20))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextRemoved); qDebug() << "m_docChanges[2].changeRange(): " << m_docChanges[2].changeRange(); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 20), Cursor(0, 24))); TestPressKey("X"); FinishTest("object->functionCall (X<-Cursor here!"); // Deal with function completions which add a ";". BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall();"); + fakeCodeCompletionModel->setCompletions({ "functionCall();" }); clearTrackedDocumentChanges(); TestPressKey("ifun"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 3))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 12))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 12), Cursor(0, 15))); QCOMPARE(m_docChanges[2].newText(), QString("();")); FinishTest("functionCall();"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall();"); + fakeCodeCompletionModel->setCompletions({ "functionCall();" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall();X"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...);"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); clearTrackedDocumentChanges(); TestPressKey("ifun"); clearTrackedDocumentChanges(); TestPressKey("\\ctrl- \\enter"); QCOMPARE(m_docChanges.size(), 3); QCOMPARE(m_docChanges[0].changeType(), DocChange::TextRemoved); QCOMPARE(m_docChanges[0].changeRange(), Range(Cursor(0, 0), Cursor(0, 3))); QCOMPARE(m_docChanges[1].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[1].changeRange(), Range(Cursor(0, 0), Cursor(0, 12))); QCOMPARE(m_docChanges[1].newText(), QString("functionCall")); QCOMPARE(m_docChanges[2].changeType(), DocChange::TextInserted); QCOMPARE(m_docChanges[2].changeRange(), Range(Cursor(0, 12), Cursor(0, 15))); QCOMPARE(m_docChanges[2].newText(), QString("();")); FinishTest("functionCall();"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...);"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall(X);"); // Completions ending with ";" do not participate in bracket merging. BeginTest("(<-old bracket"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall();"); + fakeCodeCompletionModel->setCompletions({ "functionCall();" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall();X(<-old bracket"); BeginTest("(<-old bracket"); - fakeCodeCompletionModel->setCompletions(QStringList() << "functionCall(...);"); + fakeCodeCompletionModel->setCompletions({ "functionCall(...);" }); TestPressKey("ifun\\ctrl- \\enterX"); FinishTest("functionCall(X);(<-old bracket"); KateViewConfig::global()->setViInputModeStealKeys(oldStealKeys); kate_view->hide(); mainWindow->hide(); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; } void CompletionTest::CompletionTests() { const bool oldRemoveTailOnCompletion = KateViewConfig::global()->wordCompletionRemoveTail(); // For these tests, assume we don't swallow the tail on completion. KateViewConfig::global()->setWordCompletionRemoveTail(false); KateViewConfig::global()->setViInputModeStealKeys(true); // For Ctrl-P, Ctrl-N etc ensureKateViewVisible(); // KTextEditor::ViewPrivate needs to be visible for the completion widget. VimCodeCompletionTestModel *testModel = new VimCodeCompletionTestModel(kate_view); BeginTest(""); TestPressKey("i\\ctrl-p"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion3"); BeginTest(""); TestPressKey("i\\ctrl- "); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); BeginTest(""); TestPressKey("i\\ctrl-n"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); // Test wraps around from top to bottom. BeginTest(""); TestPressKey("i\\ctrl- \\ctrl-p"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion3"); // Test wraps around from bottom to top. BeginTest(""); TestPressKey("i\\ctrl- \\ctrl-n\\ctrl-n\\ctrl-n"); waitForCompletionWidgetToActivate(); TestPressKey("\\return"); FinishTest("completion1"); // Test does not re-invoke completion when doing a "." repeat. BeginTest(""); TestPressKey("i\\ctrl- "); waitForCompletionWidgetToActivate(); TestPressKey("\\return\\ctrl-c"); kate_view->unregisterCompletionModel(testModel); FailTestOnInvocationModel *failsTestOnInvocation = new FailTestOnInvocationModel(kate_view); TestPressKey("gg."); FinishTest("completion1completion1"); kate_view->unregisterCompletionModel(failsTestOnInvocation); kate_view->registerCompletionModel(testModel); // Test that the full completion is repeated when repeat an insert that uses completion, // where the completion list was not manually invoked. BeginTest(""); TestPressKey("i"); // Simulate "automatic" invoking of completion. kate_view->completionWidget()->userInvokedCompletion(); waitForCompletionWidgetToActivate(); TestPressKey("\\return\\ctrl-cgg."); FinishTest("completion1completion1"); clearAllMappings(); // Make sure the "Enter"/ "Return" used when invoking completions is not swallowed before being // passed to the key mapper. kate_view->registerCompletionModel(testModel); vi_global->mappings()->add(Mappings::InsertModeMapping, "cb", "mapped-shouldntbehere", Mappings::Recursive); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\enterb"); FinishTest("completion1b"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\returnb"); FinishTest("completion1b"); // Make sure the completion widget is dismissed on ESC, ctrl-c and ctrl-[. BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\esc"); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\ctrl-c"); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); BeginTest(""); TestPressKey("ic"); kate_view->userInvokedCompletion(); waitForCompletionWidgetToActivate(); QVERIFY(kate_view->completionWidget()->isCompletionActive()); TestPressKey("\\ctrl-["); QVERIFY(!kate_view->completionWidget()->isCompletionActive()); FinishTest("c"); kate_view->unregisterCompletionModel(testModel); // Check that the repeat-last-change handles Completions in the same way as Macros do // i.e. fairly intelligently :) FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); kate_view->registerCompletionModel(fakeCodeCompletionModel); clearAllMacros(); BeginTest("funct\nnoa\ncomtail\ncomtail"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "functionwithargs(...)" << "noargfunction()"); + fakeCodeCompletionModel->setCompletions({ "completionA", "functionwithargs(...)", "noargfunction()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a'. TestPressKey("i\\right\\right\\right\\right\\right\\ctrl- \\enterfirstArg"); // Function with args. TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter"); // Function no args. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter"); // Cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("\\home\\down\\right\\right\\right\\ctrl- \\enter\\ctrl-c"); // Don't cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); // Replay. fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("funct\nnoa\ncomtail\ncomtail"); TestPressKey("gg."); FinishTest("functionwithargs(firstArg)\nnoargfunction()\ncompletionA\ncompletionAtail"); // Clear our log of completions for each change. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); + fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("ciw\\ctrl- \\enter\\ctrl-c"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionB"); + fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ciw\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("."); FinishTest("completionB"); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; KateViewConfig::global()->setWordCompletionRemoveTail(oldRemoveTailOnCompletion); // Hide the kate_view for subsequent tests. kate_view->hide(); mainWindow->hide(); } void CompletionTest::waitForCompletionWidgetToActivate() { BaseTest::waitForCompletionWidgetToActivate(kate_view); } void CompletionTest::clearTrackedDocumentChanges() { m_docChanges.clear(); } //END: CompletionTest diff --git a/autotests/src/vimode/keys.cpp b/autotests/src/vimode/keys.cpp index 3c232618..251f944a 100644 --- a/autotests/src/vimode/keys.cpp +++ b/autotests/src/vimode/keys.cpp @@ -1,1581 +1,1581 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include "keys.h" #include "emulatedcommandbarsetupandteardown.h" #include "fakecodecompletiontestmodel.h" #include "vimode/mappings.h" #include "vimode/globalstate.h" using namespace KTextEditor; using KateVi::Mappings; using KateVi::KeyParser; QTEST_MAIN(KeysTest) //BEGIN: KeysTest void KeysTest::MappingTests() { // QVERIFY(false); const int mappingTimeoutMSOverride = QString::fromLocal8Bit(qgetenv("KATE_VIMODE_TEST_MAPPINGTIMEOUTMS")).toInt(); const int mappingTimeoutMS = (mappingTimeoutMSOverride > 0) ? mappingTimeoutMSOverride : 2000; KateViewConfig::global()->setViInputModeStealKeys(true); // For tests involving e.g. { // Check storage and retrieval of mapping recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); QVERIFY(vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "'")); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ihello", Mappings::NonRecursive); QVERIFY(!vi_global->mappings()->isRecursive(Mappings::NormalModeMapping, "a")); } clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello^aworld", Mappings::Recursive); DoTest("", "'", "hworldello"); // Ensure that the non-mapping logged keypresses are cleared before we execute a mapping vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "rO", Mappings::Recursive); DoTest("X", "'a", "O"); { // Check that '123 is mapped after the timeout, given that we also have mappings that // extend it (e.g. '1234, '12345, etc) and which it itself extends ('1, '12, etc). clearAllMappings(); BeginTest(""); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; QString consectiveDigits; for (int i = 1; i < 9; i++) { consectiveDigits += QString::number(i); vi_global->mappings()->add(Mappings::NormalModeMapping, "'" + consectiveDigits, "iMapped from " + consectiveDigits + "", Mappings::Recursive); } TestPressKey("'123"); QCOMPARE(kate_document->text(), QString("")); // Shouldn't add anything until after the timeout! QTest::qWait(2 * mappingTimeoutMS); FinishTest("Mapped from 123"); } // Mappings are not "counted": any count entered applies to the first command/ motion in the mapped sequence, // and is not used to replay the entire mapped sequence times in a row. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'downmapping", "j", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ifooibar", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmotionmapping", "lj", Mappings::Recursive); DoTest("AAAA\nXXXX\nXXXX\nXXXX\nXXXX\nBBBB\nCCCC\nDDDD", "jd3'downmapping", "AAAA\nBBBB\nCCCC\nDDDD"); DoTest("", "5'testmapping", "foofoofoofoofobaro"); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "3'testmotionmappingrO", "XXXX\nXXXO\nXXXX\nXXXX"); // Regression test for a weird mistake I made: *completely* remove all counting for the // first command in the sequence; don't just set it to 1! If it is set to 1, then "%" // will mean "go to position 1 percent of the way through the document" rather than // go to matching item. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::Recursive); DoTest("0\n1\n2\n3\n4\n5\nfoo bar(xyz) baz", "jjjjjjwdgl", "0\n1\n2\n3\n4\n5\nfoo baz"); // Test that countable mappings work even when triggered by timeouts. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmapping", "ljrO", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "'testmappingdummy", "dummy", Mappings::Recursive); BeginTest("XXXX\nXXXX\nXXXX\nXXXX"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("3'testmapping"); QTest::qWait(2 * mappingTimeoutMS); FinishTest("XXXX\nXXXO\nXXXX\nXXXX"); // Test that telescoping mappings don't interfere with built-in commands. Assumes that gp // is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gdummy", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpx", "hhellollo"); // Test that we can map a sequence of keys that extends a built-in command and use // that sequence without the built-in command firing. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); DoTest("hello", "yiwgpa", "dummyhello"); // Test that we can map a sequence of keys that extends a built-in command and still // have the original built-in command fire if we timeout after entering that command. // Once again, assumes that gp is implemented and working. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gpa", "idummy", Mappings::Recursive); BeginTest("hello"); vi_input_mode_manager->keyMapper()->setMappingTimeout(mappingTimeoutMS);; TestPressKey("yiwgp"); QTest::qWait(2 * mappingTimeoutMS); TestPressKey("x"); FinishTest("hhellollo"); // Test that something that starts off as a partial mapping following a command // (the "g" in the first "dg" is a partial mapping of "gj"), when extended to something // that is definitely not a mapping ("gg"), results in the full command being executed ("dgg"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gj", "aj", Mappings::Recursive); DoTest("foo\nbar\nxyz", "jjdgg", ""); // Make sure that a mapped sequence of commands is merged into a single undo-able edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofooofooofoo", Mappings::Recursive); DoTest("bar", "'au", "bar"); // Make sure that a counted mapping is merged into a single undoable edit. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'a", "ofoo", Mappings::Recursive); DoTest("bar", "5'au", "bar"); // Some test setup for non-recursive mapping g -> gj (cf: bug:314415) // Firstly: work out the expected result of gj (this might be fragile as default settings // change, etc.). We use BeginTest & FinishTest for the setup and teardown etc, but this is // not an actual test - it's just computing the expected result of the real test! const QString multiVirtualLineText = "foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo"; ensureKateViewVisible(); // Needs to be visible in order for virtual lines to make sense. KateViewConfig::global()->setDynWordWrap(true); BeginTest(multiVirtualLineText); TestPressKey("gjrX"); const QString expectedAfterVirtualLineDownAndChange = kate_document->text(); Q_ASSERT_X(expectedAfterVirtualLineDownAndChange.contains("X") && !expectedAfterVirtualLineDownAndChange.startsWith('X'), "setting up j->gj testcase data", "gj doesn't seem to have worked correctly!"); FinishTest(expectedAfterVirtualLineDownAndChange); // Test that non-recursive mappings are not expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); DoTest(multiVirtualLineText, "jrX", expectedAfterVirtualLineDownAndChange); KateViewConfig::global()->setDynWordWrap(false); // Test that recursive mappings are expanded. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "la", "fxo"); // Test that the flag that stops mappings being expanded is reset after the mapping has been executed. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "j", "gj", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "rx", Mappings::Recursive); DoTest("foo", "jla", "fxo"); // Even if we start with a recursive mapping, as soon as we hit one that is not recursive, we should stop // expanding. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "X", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "X", "r.", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "a", Mappings::Recursive); DoTest("foo", "li", "oo"); // Regression test: Using a mapping may trigger a call to updateSelection(), which can change the mode // from VisualLineMode to plain VisualMode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "gA", "%", Mappings::NonRecursive); DoTest("xyz\nfoo\n{\nbar\n}", "jVjgAdgglP", "foo\n{\nbar\n}\nxyz"); // Piggy back on the previous test with a regression test for issue where, if gA is mapped to %, vgly // will yank one more character than it should. DoTest("foo(bar)X", "vgAyp", "ffoo(bar)oo(bar)X"); // Make sure that a successful mapping does not break the "if we select stuff externally in Normal mode, // we should switch to Visual Mode" thing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "gA", "%", Mappings::NonRecursive); BeginTest("foo bar xyz()"); TestPressKey("gAr."); kate_view->setSelection(Range(0, 1, 0, 4)); // Actually selects "oo " (i.e. without the "b"). TestPressKey("d"); FinishTest("fbar xyz(."); // Regression tests for BUG:260655 clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "f", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "F", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo .ar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "t", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "adr.", "foo.dar"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "T", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "$adr.", "foo d.r"); clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "r", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "d", "i", Mappings::NonRecursive); DoTest("foo dar", "ad", "doo dar"); // Feel free to map the keypress after that, though. DoTest("foo dar", "addber\\esc", "berdoo dar"); // Also, be careful about how we interpret "waiting for find char/ replace char" DoTest("foo dar", "ffas", "soo dar"); // Ignore raw "Ctrl", "Shift", "Meta" and "Alt" keys, which will almost certainly end up being pressed as // we try to trigger mappings that contain these keys. clearAllMappings(); { // Ctrl. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ictrl", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-b"); FinishTest("ctrl"); } { // Shift. vi_global->mappings()->add(Mappings::NormalModeMapping, "C", "ishift", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *shiftKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), shiftKeyDown); QApplication::sendPostedEvents(); TestPressKey("C"); FinishTest("shift"); } { // Alt. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "ialt", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *altKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Alt, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\alt-b"); FinishTest("alt"); } { // Meta. vi_global->mappings()->add(Mappings::NormalModeMapping, "", "imeta", Mappings::NonRecursive); BeginTest(""); QKeyEvent *ctrlKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Control, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), ctrlKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-a"); QKeyEvent *metaKeyDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Meta, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), metaKeyDown); QApplication::sendPostedEvents(); TestPressKey("\\meta-b"); FinishTest("meta"); } { // Can have mappings in Visual mode, distinct from Normal mode.. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "3l", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("0123456", "lvad", "056"); // The recursion in Visual Mode is distinct from that of Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::Recursive); DoTest("XXX\nXXX", "lvajd", "XXX"); clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "b", Mappings::NonRecursive); DoTest("XXX\nXXX", "lvajd", "XXX\nXXX"); // A Visual mode mapping applies to all Visual modes (line, block, etc). clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "2j", Mappings::NonRecursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Same for recursion. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "b", "2j", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "a", "b", Mappings::Recursive); DoTest("123\n456\n789", "lvad", "19"); DoTest("123\n456\n789", "l\\ctrl-vad", "13\n46\n79"); DoTest("123\n456\n789", "lVad", ""); // Can clear Visual mode mappings. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "l", Mappings::Recursive); vi_global->mappings()->clear(Mappings::VisualModeMapping); DoTest("123\n456\n789", "lvhd", "3\n456\n789"); DoTest("123\n456\n789", "l\\ctrl-vhd", "3\n456\n789"); DoTest("123\n456\n789", "lVhd", "456\n789"); } { // Can have mappings in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "xyz", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "inose", Mappings::NonRecursive); DoTest("foo", "ia\\esc", "xyzfoo"); // Recursion for Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); DoTest("", "ia\\esc", "b"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "b", "c", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::Recursive); DoTest("", "ia\\esc", "c"); clearAllMappings(); // Clear mappings for Insert mode. vi_global->mappings()->add(Mappings::InsertModeMapping, "a", "b", Mappings::NonRecursive); vi_global->mappings()->clear(Mappings::InsertModeMapping); DoTest("", "ia\\esc", "a"); } { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Can have mappings in Emulated Command Bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "a", "xyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\enterrX", " a Xyz"); // Use mappings from Normal mode as soon as we exit command bar via Enter. vi_global->mappings()->add(Mappings::NormalModeMapping, "a", "ixyz", Mappings::NonRecursive); DoTest(" a xyz", "/a\\entera", " a xyzxyz"); // Multiple mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "123", Mappings::NonRecursive); DoTest(" xyz123", "/ab\\enterrX", " Xyz123"); // Recursive mappings. vi_global->mappings()->add(Mappings::CommandModeMapping, "b", "a", Mappings::Recursive); DoTest(" xyz", "/b\\enterrX", " Xyz"); // Can clear all. vi_global->mappings()->clear(Mappings::CommandModeMapping); DoTest(" ab xyz xyz123", "/ab\\enterrX", " Xb xyz xyz123"); } // Test that not *both* of the mapping and the mapped keys are logged for repetition via "." clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "ixyz", "iabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "gl", "%", Mappings::NonRecursive); DoTest("", "ixyz\\esc.", "ababcc"); DoTest("foo()X\nbarxyz()Y", "cglbaz\\escggj.", "bazX\nbazY"); // Regression test for a crash when executing a mapping that switches to Normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "h", "d", Mappings::Recursive); DoTest("foo", "vlh", "o"); { // Test that we can set/ unset mappings from the command-line. clearAllMappings(); DoTest("", "\\:nn foo ibar\\foo", "bar"); // "nn" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nn foo l\\foorX", "xXx"); // "no" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:no foo l\\foorX", "xXx"); // "noremap" is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:noremap foo l\\foorX", "xXx"); // "nm" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nm foo l\\foorX", "abXxxx"); // "nmap" is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:nmap foo l\\foorX", "abXxxx"); // Unfortunately, "map" is a reserved word :/ clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "iabc", Mappings::NonRecursive); DoTest("xxx", "\\:map foo l\\foorX", "abXxxx", ShouldFail, "'map' is reserved for other stuff in Kate command line"); // nunmap works in normal mode. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "w", "ciwabc", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "b", "ciwxyz", Mappings::NonRecursive); DoTest(" 123 456 789", "\\:nunmap b\\WWwbrX", " 123 Xbc 789"); // nmap and nunmap whose "from" is a complex encoded expression. clearAllMappings(); BeginTest("123"); TestPressKey("\\:nmap ciwxyz\\"); TestPressKey("\\ctrl-9"); FinishTest("xyz"); BeginTest("123"); TestPressKey("\\:nunmap \\"); TestPressKey("\\ctrl-9"); FinishTest("123"); // vmap works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vmap foo l\\v\\rightfoogU", "co"); // vmap does not work in Normal mode. clearAllMappings(); DoTest("xxx", "\\:vmap foo l\\foorX", "xxx\nrX"); // vm works in Visual mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vm foo l\\v\\rightfoogU", "co"); // vn works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vn foo l\\v\\rightfoogU", "ABCo"); // vnoremap works in Visual mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "d", Mappings::NonRecursive); DoTest("abco", "\\:vnoremap foo l\\v\\rightfoogU", "ABCo"); // vunmap works in Visual Mode. clearAllMappings(); vi_global->mappings()->add(Mappings::VisualModeMapping, "l", "w", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::VisualModeMapping, "gU", "2b", Mappings::NonRecursive); DoTest("foo bar xyz", "\\:vunmap gU\\wvlgUd", "foo BAR Xyz"); // imap works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:imap foo l\\ifoo\\esc", "d"); // im works in Insert mode and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:im foo l\\ifoo\\esc", "d"); // ino works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:ino foo l\\ifoo\\esc", "l"); // inoremap works in Insert mode and is not recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); DoTest("", "\\:inoremap foo l\\ifoo\\esc", "l"); // iunmap works in Insert mode. clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::InsertModeMapping, "m", "e", Mappings::NonRecursive); DoTest("", "\\:iunmap l\\ilm\\esc", "le"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // cmap works in emulated command bar and is recursive. // NOTE: need to do the cmap call using the direct execution (i.e. \\:cmap blah blah\\), *not* using // the emulated command bar (:cmap blah blah\\enter), as this will be subject to mappings, which // can interfere with the tests! clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cmap foo l\\/foo\\enterrX", " l X foo"); // cm works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cm foo l\\/foo\\enterrX", " l X foo"); // cnoremap works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cnoremap foo l\\/foo\\enterrX", " X d foo"); // cno works in emulated command bar and is recursive. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); DoTest(" l d foo", "\\:cno foo l\\/foo\\enterrX", " X d foo"); // cunmap works in emulated command bar. clearAllMappings(); vi_global->mappings()->add(Mappings::CommandModeMapping, "l", "d", Mappings::NonRecursive); vi_global->mappings()->add(Mappings::CommandModeMapping, "m", "e", Mappings::NonRecursive); DoTest(" de le", "\\:cunmap l\\/lm\\enterrX", " de Xe"); } // Can use to signify a space. clearAllMappings(); DoTest("", "\\:nn h iab\\h ", " a b"); } // More recursion tests - don't lose characters from a Recursive mapping if it looks like they might // be part of a different mapping (but end up not being so). // (Here, the leading "i" in "irecursive" could be part of the mapping "ihello"). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "'", "recursive"); // Capslock in insert mode is not handled by Vim nor by KateViewInternal, and ends up // being sent to KateViInputModeManager::handleKeypress twice (it could be argued that this is // incorrect behaviour on the part of KateViewInternal), which can cause infinite // recursion if we are not careful about identifying replayed rejected keypresses. BeginTest("foo bar"); TestPressKey("i"); QKeyEvent *capslockKeyPress = new QKeyEvent(QEvent::KeyPress, Qt::Key_CapsLock, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), capslockKeyPress); QApplication::sendPostedEvents(); FinishTest("foo bar"); // Mapping the u and the U commands to other keys. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "t", "u", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "r", "U", Mappings::Recursive); DoTest("", "ihello\\esct", ""); DoTest("", "ihello\\esctr", "hello"); // clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "lrr", "rello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "", Mappings::Recursive); DoTest("Hello", "sl\\esc", "ello"); clearAllMappings(); vi_global->mappings()->add(Mappings::InsertModeMapping, "l", "abc", Mappings::Recursive); DoTest("Hello", "sl\\esc", "abcello"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::LeaderTests() { // Clean slate. KateViewConfig::global()->setViInputModeStealKeys(true); clearAllMappings(); // By default the backslash character is the leader. The default leader // is picked from the config. If we don't want to mess this from other // tests, it's better if we mock the config. const QString viTestKConfigFileName = QStringLiteral("vimodetest-leader-katevimoderc"); KConfig viTestKConfig(viTestKConfigFileName); vi_global->mappings()->setLeader(QChar()); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "\\\\i", "i"); // We can change the leader and it will work. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, "i", "ii", Mappings::Recursive); DoTest("", ",i", "i"); // Mixing up the with its value. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); vi_global->mappings()->add(Mappings::NormalModeMapping, ",", "ii", Mappings::Recursive); DoTest("", ",,", "i"); // It doesn't work outside normal mode. clearAllMappings(); vi_global->readConfig(&viTestKConfig); vi_global->mappings()->setLeader(QChar::fromLatin1(',')); vi_global->mappings()->add(Mappings::InsertModeMapping, "i", "ii", Mappings::Recursive); DoTest("", "i,ii", ",ii"); // Clear mappings for subsequent tests. clearAllMappings(); } void KeysTest::ParsingTests() { // BUG #298726 const QChar char_o_diaeresis(246); // Test that we can correctly translate finnish key ö QKeyEvent *k = new QKeyEvent(QEvent::KeyPress, 214, Qt::NoModifier, 47, 246, 16400, char_o_diaeresis); QCOMPARE(KeyParser::self()->KeyEventToQChar(*k), QChar(246)); // Test that it can be used in mappings clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, char_o_diaeresis, "ifoo", Mappings::Recursive); DoTest("hello", QString("ll%1bar").arg(char_o_diaeresis), "hefoobarllo"); // Test that is parsed like QCOMPARE(KeyParser::self()->vi2qt("cr"), int(Qt::Key_Enter)); const QString &enter = KeyParser::self()->encodeKeySequence(QLatin1String("")); QCOMPARE(KeyParser::self()->decodeKeySequence(enter), QLatin1String("")); } void KeysTest::AltGr() { QKeyEvent *altGrDown; QKeyEvent *altGrUp; // Test Alt-gr still works - this isn't quite how things work in "real-life": in real-life, something like // Alt-gr+7 would be a "{", but I don't think this can be reproduced without sending raw X11 // keypresses to Qt, so just duplicate the keypress events we would receive if we pressed // Alt-gr+7 (that is: Alt-gr down; "{"; Alt-gr up). BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); // Not really Alt-gr and 7, but this is the key event that is reported by Qt if we press that. QKeyEvent *altGrAnd7 = new QKeyEvent(QEvent::KeyPress, Qt::Key_BraceLeft, Qt::GroupSwitchModifier, "{"); QApplication::postEvent(kate_view->focusProxy(), altGrAnd7); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); TestPressKey("\\ctrl-c"); FinishTest("{"); // French Bepo keyabord AltGr + Shift + s = Ù = Unicode(0x00D9); const QString ugrave = QString(QChar(0x00D9)); BeginTest(""); TestPressKey("i"); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); altGrDown = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::ShiftModifier | Qt::GroupSwitchModifier); QApplication::postEvent(kate_view->focusProxy(), altGrDown); QApplication::sendPostedEvents(); QKeyEvent *altGrAndUGrave = new QKeyEvent(QEvent::KeyPress, Qt::Key_Ugrave, Qt::ShiftModifier | Qt::GroupSwitchModifier, ugrave); qDebug() << QString("%1").arg(altGrAndUGrave->modifiers(), 10, 16); QApplication::postEvent(kate_view->focusProxy(), altGrAndUGrave); QApplication::sendPostedEvents(); altGrUp = new QKeyEvent(QEvent::KeyRelease, Qt::Key_AltGr, Qt::NoModifier); QApplication::postEvent(kate_view->focusProxy(), altGrUp); QApplication::sendPostedEvents(); FinishTest(ugrave); } void KeysTest::MacroTests() { // Update the status on qa. const QString macroIsRecordingStatus = QLatin1String("(") + i18n("recording") + QLatin1String(")"); clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qa"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // The closing "q" is not treated as the beginning of a new "begin recording macro" command. clearAllMacros(); BeginTest("foo"); TestPressKey("qaqa"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("xyz\\esc"); FinishTest("fxyzoo"); // Record and playback a single keypress into macro register "a". clearAllMacros(); DoTest("foo bar", "qawqgg@arX", "foo Xar"); // Two macros - make sure the old one is cleared. clearAllMacros(); DoTest("123 foo bar xyz", "qawqqabqggww@arX", "123 Xoo bar xyz"); // Update the status on qb. clearAllMacros(); BeginTest(""); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("qb"); QVERIFY(kate_view->viewModeHuman().contains(macroIsRecordingStatus)); TestPressKey("q"); QVERIFY(!kate_view->viewModeHuman().contains(macroIsRecordingStatus)); FinishTest(""); // Record and playback a single keypress into macro register "b". clearAllMacros(); DoTest("foo bar", "qbwqgg@brX", "foo Xar"); // More complex macros. clearAllMacros(); DoTest("foo", "qcrXql@c", "XXo"); // Re-recording a macro should only clear that macro. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqqbrBqw@a", "Boo a23"); // Empty macro clears it. clearAllMacros(); DoTest("", "qaixyz\\ctrl-cqqaq@a", "xyz"); // Hold two macros in memory simultanenously so both can be played. clearAllMacros(); DoTest("foo 123", "qaraqqbrbqw@al@b", "boo ab3"); // Do more complex things, including switching modes and using ctrl codes. clearAllMacros(); DoTest("foo bar", "qainose\\ctrl-c~qw@a", "nosEfoo nosEbar"); clearAllMacros(); DoTest("foo bar", "qayiwinose\\ctrl-r0\\ctrl-c~qw@a", "nosefoOfoo nosebaRbar"); clearAllMacros(); DoTest("foo bar", "qavldqw@a", "o r"); // Make sure we can use "q" in insert mode while recording a macro. clearAllMacros(); DoTest("foo bar", "qaiqueequeg\\ctrl-cqw@a", "queequegfoo queequegbar"); // Can invoke a macro in Visual Mode. clearAllMacros(); DoTest("foo bar", "qa~qvlll@a", "FOO Bar"); // Invoking a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "qallqggv@a~", "FOO bar");; // Can record & macros in Visual Mode for playback in Normal Mode. clearAllMacros(); DoTest("foo bar", "vqblq\\ctrl-c@b~", "foO bar"); // Recording a macro in Visual Mode does not exit Visual Mode. clearAllMacros(); DoTest("foo bar", "vqblql~", "FOO bar"); // Recognize correctly numbered registers clearAllMacros(); DoTest("foo", "q1iX\\escq@1", "XXfoo"); { // Ensure that we can call emulated command bar searches, and that we don't record // synthetic keypresses. EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); clearAllMacros(); DoTest("foo bar\nblank line", "qa/bar\\enterqgg@arX", "foo Xar\nblank line"); // More complex searching stuff. clearAllMacros(); DoTest("foo 123foo123\nbar 123bar123", "qayiw/\\ctrl-r0\\enterrXqggj@a", "foo 123Xoo123\nbar 123Xar123"); } // Expand mappings, but don't do *both* original keypresses and executed keypresses. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'q@a", "hellhelloo"); // Actually, just do the mapped keypresses, not the executed mappings (like Vim). clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); BeginTest(""); TestPressKey("qa'q"); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "igoodbye", Mappings::Recursive); TestPressKey("@a"); FinishTest("hellgoodbyeo"); // Clear the "stop recording macro keypresses because we're executing a mapping" when the mapping has finished // executing. clearAllMappings(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); clearAllMacros(); DoTest("", "qa'ixyz\\ctrl-cq@a", "hellxyhellxyzozo"); // ... make sure that *all* mappings have finished, though: take into account recursion. clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihello", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivrecursivee"); clearAllMappings(); clearAllMacros(); vi_global->mappings()->add(Mappings::NormalModeMapping, "'", "ihelloixyz", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "ihello", "irecursive", Mappings::Recursive); DoTest("", "qa'q@a", "recursivxyrecursivxyzeze"); clearAllMappings(); clearAllMacros(); // Don't save the trailing "q" with macros, and also test that we can call one macro from another, // without one of the macros being repeated. DoTest("", "qaixyz\\ctrl-cqqb@aq@b", "xyxyxyzzz"); clearAllMappings(); clearAllMacros(); // More stringent test that macros called from another macro aren't repeated - requires more nesting // of macros ('a' calls 'b' calls 'c'). DoTest("", "qciC\\ctrl-cq" "qb@ciB\\ctrl-cq" "qa@biA\\ctrl-cq" "dd@a", "ABC"); // Don't crash if we invoke a non-existent macro. clearAllMacros(); DoTest("", "@x", ""); // Make macros "counted". clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@a", "OXXX\nXOXX\nXXOX\nXXXO"); // A macro can be undone with one undo. clearAllMacros(); DoTest("foo bar", "qaciwxyz\\ctrl-ci123\\ctrl-cqw@au", "xy123z bar"); // As can a counted macro. clearAllMacros(); DoTest("XXXX\nXXXX\nXXXX\nXXXX", "qarOljq3@au", "OXXX\nXXXX\nXXXX\nXXXX"); { EmulatedCommandBarSetUpAndTearDown vimStyleCommandBarTestsSetUpAndTearDown(vi_input_mode, kate_view, mainWindow); // Make sure we can macro-ise an interactive sed replace. clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryynyAdone\\escqggj@a", "bar bar foo bardone\nbar bar foo bardone"); // Make sure the closing "q" in the interactive sed replace isn't mistaken for a macro's closing "q". clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqAdone\\escqggj@a", "bar bar foo foodone\nbar bar foo foodone"); clearAllMacros(); DoTest("foo foo foo foo\nfoo foo foo foo", "qa:s/foo/bar/gc\\enteryyqqAdone\\escggj@aAdone\\esc", "bar bar foo foodone\nbar bar foo foodone"); } clearAllMappings(); clearAllMacros(); // Expand mapping in an executed macro, if the invocation of the macro "@a" is a prefix of a mapping M, and // M ends up not being triggered. vi_global->mappings()->add(Mappings::NormalModeMapping, "@aaaa", "idummy", Mappings::Recursive); vi_global->mappings()->add(Mappings::NormalModeMapping, "S", "ixyz", Mappings::Recursive); DoTest("", "qaSq@abrX", "Xyxyzz"); clearAllMappings(); // Can't play old version of macro while recording new version. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-cqqa@aq", "aaa"); // Can't play the macro while recording it. clearAllMacros(); DoTest("", "qaiaaa\\ctrl-c@aq", "aaa"); // "@@" plays back macro "a" if "a" was the last macro we played back. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw@@", "a"); // "@@" plays back macro "b" if "b" was the last macro we played back. clearAllMacros(); DoTest("", "qbib\\ctrl-cq@bdiw@@", "b"); // "@@" does nothing if no macro was previously played. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@@", "a"); // Nitpick: "@@" replays the last played back macro, even if that macro had not been defined // when it was first played back. clearAllMacros(); DoTest("", "@aqaia\\ctrl-cq@@", "aa"); // "@@" is counted. clearAllMacros(); DoTest("", "qaia\\ctrl-cq@adiw5@@", "aaaaa"); // Test that we can save and restore a single macro. const QString viTestKConfigFileName = "vimodetest-katevimoderc"; { clearAllMacros(); KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macro "a", and clear the document. TestPressKey("qaidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a"); FinishTest("a"); } { // Test that we can save and restore several macros. clearAllMacros(); const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); BeginTest(""); TestPressKey("qaia\\ctrl-cqqbib\\ctrl-cq"); vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite macros "a" & "b", and clear the document. TestPressKey("qaidummy\\ctrl-cqqbidummy\\ctrl-cqdd"); vi_global->readConfig(&viTestKConfig); TestPressKey("@a@b"); FinishTest("ba"); } // Ensure that we don't crash when a "repeat change" occurs in a macro we execute. clearAllMacros(); DoTest("", "qqixyz\\ctrl-c.q@qdd", ""); // Don't record both the "." *and* the last-change keypresses when recording a macro; // just record the "." clearAllMacros(); DoTest("", "ixyz\\ctrl-cqq.qddi123\\ctrl-c@q", "121233"); // Test dealing with auto-completion. FakeCodeCompletionTestModel *fakeCodeCompletionModel = new FakeCodeCompletionTestModel(kate_view); kate_view->registerCompletionModel(fakeCodeCompletionModel); // Completion tests require a visible kate_view. ensureKateViewVisible(); // Want Vim mode to intercept ctrl-p, ctrl-n shortcuts, etc. const bool oldStealKeys = KateViewConfig::global()->viInputModeStealKeys(); KateViewConfig::global()->setViInputModeStealKeys(true); // Don't invoke completion via ctrl-space when replaying a macro. clearAllMacros(); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl- \\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-p when replaying a macro. clearAllMacros(); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-p\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // Don't invoke completion via ctrl-n when replaying a macro. clearAllMacros(); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); BeginTest(""); TestPressKey("qqico\\ctrl-n\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("ccoo"); // An "enter" in insert mode when no completion is activated (so, a newline) // is treated as a newline when replayed as a macro, even if completion is // active when the "enter" is replayed. clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\enterX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // Same for "return". clearAllMacros(); fakeCodeCompletionModel->setCompletions(QStringList()); // Prevent any completions. fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->clearWasInvoked(); BeginTest(""); TestPressKey("qqicompl\\returnX\\ctrl-cqdddd"); QVERIFY(!fakeCodeCompletionModel->wasInvoked()); // Error in test setup! - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->forceInvocationIfDocTextIs("compl"); fakeCodeCompletionModel->clearWasInvoked(); TestPressKey("@q"); QVERIFY(fakeCodeCompletionModel->wasInvoked()); // Error in test setup! fakeCodeCompletionModel->doNotForceInvocation(); FinishTest("compl\nX"); // If we do a plain-text completion in a macro, this should be repeated when we replay it. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompl\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("@q"); FinishTest("completionA"); // Should replace only the current word when we repeat the completion. clearAllMacros(); BeginTest("compl"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@q"); FinishTest("(completionA)"); // Tail-clearing completions should be undoable with one undo. clearAllMacros(); BeginTest("compl"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfla\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(compl)"); TestPressKey("gg@qu"); FinishTest("(compl)"); // Should be able to store multiple completions. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl-p\\enter com\\ctrl-p\\ctrl-p\\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionC completionB"); // Clear the completions for a macro when we start recording. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionOrig"); + fakeCodeCompletionModel->setCompletions({ "completionOrig" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicom\\ctrl- \\enter\\ctrl-cq"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionSecond"); + fakeCodeCompletionModel->setCompletions({ "completionSecond" }); TestPressKey("ddqqicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("completionSecond"); // Completions are per macro. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); + fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cq"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionB"); + fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ddqbicom\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@aA\\enter\\ctrl-c@b"); FinishTest("completionA\ncompletionB"); // Make sure completions work with recursive macros. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA1" << "completionA2"); + fakeCodeCompletionModel->setCompletions({ "completionA1", "completionA2" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a', which calls the (non-yet-existent) macro 'b'. TestPressKey("qaicom\\ctrl- \\enter\\ctrl-cA\\enter\\ctrl-c@bA\\enter\\ctrl-cicom\\ctrl- \\ctrl-p\\enter\\ctrl-cq"); // Clear document and record 'b'. - fakeCodeCompletionModel->setCompletions(QStringList() << "completionB"); + fakeCodeCompletionModel->setCompletions({ "completionB" }); TestPressKey("ggdGqbicom\\ctrl- \\enter\\ctrl-cq"); TestPressKey("dd@a"); FinishTest("completionA1\ncompletionB\ncompletionA2"); // Test that non-tail-removing completions are respected. // Note that there is no way (in general) to determine if a completion was // non-tail-removing, so we explicitly set the config to false. const bool oldRemoveTailOnCompletion = KateViewConfig::global()->wordCompletionRemoveTail(); KateViewConfig::global()->setWordCompletionRemoveTail(false); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); clearAllMacros(); BeginTest("compTail"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "completionB" << "completionC"); + fakeCodeCompletionModel->setCompletions({ "completionA", "completionB", "completionC" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("compTail"); TestPressKey("gg@q"); FinishTest("completionATail"); // A "word" consists of letters & numbers, plus "_". clearAllMacros(); BeginTest("(123_compTail"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail"); TestPressKey("gg@q"); FinishTest("(123_completionATail"); // Correctly remove word if we are set to remove tail. KateViewConfig::global()->setWordCompletionRemoveTail(true); clearAllMacros(); BeginTest("(123_compTail)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Again, a "word" consists of letters & numbers & underscores. clearAllMacros(); BeginTest("(123_compTail_456)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); // Actually, let whether the tail is swallowed or not depend on the value when the // completion occurred, not when we replay it. clearAllMacros(); BeginTest("(123_compTail_456)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); KateViewConfig::global()->setWordCompletionRemoveTail(false); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)"); clearAllMacros(); BeginTest("(123_compTail_456)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); kate_document->setText("(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionATail_456)"); // Can have remove-tail *and* non-remove-tail completions in one macro. clearAllMacros(); BeginTest("(123_compTail_456)\n(123_compTail_456)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqfTi\\ctrl- \\enter\\ctrl-c"); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("j^fTi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(123_compTail_456)\n(123_compTail_456)"); TestPressKey("gg@q"); FinishTest("(123_completionA)\n(123_completionATail_456)"); // Can repeat plain-text completions when there is no word to the left of the cursor. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); TestPressKey("gg@q"); FinishTest("123_completionA"); // Shouldn't swallow the letter under the cursor if we're not swallowing tails. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbeuntouched"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionAoldwordshouldbeuntouched"); // ... but do if we are swallowing tails. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "123_completionA"); + fakeCodeCompletionModel->setCompletions({ "123_completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("qqi\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("oldwordshouldbedeleted"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("123_completionA"); // Completion of functions. // Currently, not removing the tail on function completion is not supported. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); // A completed, no argument function "function()" is repeated correctly. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); + fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // Cursor is placed after the closing bracket when completion a no-arg function. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); + fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function().something();"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, is repeated as "function()"). BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function()"); // A function taking some arguments, repeated where there is no opening bracket to // merge with, places the cursor after the opening bracket. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with but repeated where there is no such bracket, is repeated as "function()" and the // cursor placed appropriately. BeginTest("(<-Mergeable opening bracket)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("dd@q"); FinishTest("function(firstArg)"); // A function taking some arguments, recorded where there was no opening bracket to merge // with but repeated where there is such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // A function taking some arguments, recorded where there was an opening bracket to merge // with and repeated where there is also such a bracket, is repeated as "function" and the // cursor moved to after the merged opening bracket. BeginTest("(<-mergeablebracket)"); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function(firstArg<-firstArg goes here)"); // The mergeable bracket can be separated by whitespace; the cursor is still placed after the // opening bracket. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText(" \t (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function \t (firstArg<-firstArg goes here)"); // Whitespace only, though! BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("| \t ()"); TestPressKey("gg@q"); FinishTest("function(firstArg)| \t ()"); // The opening bracket can actually be after the current word (with optional whitespace). // Note that this wouldn't be the case if we weren't swallowing tails when completion functions, // but this is not currently supported. BeginTest("function"); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("functxyz (<-firstArg goes here)"); TestPressKey("gg@q"); FinishTest("function (firstArg<-firstArg goes here)"); // Regression test for weird issue with replaying completions when the character to the left of the cursor // is not a word char. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); + fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA\n123\n456"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA"); + fakeCodeCompletionModel->setCompletions({ "completionA" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqciw\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddi.xyz.abc\\enter123\\enter456\\ctrl-cggl"); // Position cursor just after the "." TestPressKey("@q"); FinishTest(".completionA.abc\n123\n456"); // Functions taking no arguments are never bracket-merged. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); + fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifunc\\ctrl- \\enter.something();\\ctrl-cq"); fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("(<-don't merge this bracket)"); TestPressKey("gg@q"); FinishTest("function().something();(<-don't merge this bracket)"); // Not-removing-tail when completing functions is not currently supported, // so ignore the "do-not-remove-tail" settings when we try this. BeginTest("funct"); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...)"); + fakeCodeCompletionModel->setCompletions({ "function(...)" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfta\\ctrl- \\enterfirstArg\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(firstArg)"); BeginTest("funct"); - fakeCodeCompletionModel->setCompletions(QStringList() << "function()"); + fakeCodeCompletionModel->setCompletions({ "function()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("qqfta\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("functxyz"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function()"); KateViewConfig::global()->setWordCompletionRemoveTail(true); // Deal with cases where completion ends with ";". BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); + fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); + fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); + fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); + fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->clear(); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X);"); // Tests for completions ending in ";" where bracket merging should happen on replay. // NB: bracket merging when recording is impossible with completions that end in ";". BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); + fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enter\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(<-mergeable bracket"); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function(...);"); + fakeCodeCompletionModel->setCompletions({ "function(...);" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function(X<-mergeable bracket"); // Don't merge no arg functions. BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "function();"); + fakeCodeCompletionModel->setCompletions({ "function();" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqifun\\ctrl- \\enterX\\ctrl-cq"); kate_document->setText("(<-mergeable bracket"); fakeCodeCompletionModel->setFailTestOnInvocation(true); TestPressKey("gg@q"); FinishTest("function();X(<-mergeable bracket"); { const QString viTestKConfigFileName = "vimodetest-katevimoderc"; KConfig viTestKConfig(viTestKConfigFileName); // Test loading and saving of macro completions. clearAllMacros(); BeginTest("funct\nnoa\ncomtail\ncomtail\ncom"); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionA" << "functionwithargs(...)" << "noargfunction()"); + fakeCodeCompletionModel->setCompletions({ "completionA", "functionwithargs(...)", "noargfunction()" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); // Record 'a'. TestPressKey("qafta\\ctrl- \\enterfirstArg\\ctrl-c"); // Function with args. TestPressKey("\\enterea\\ctrl- \\enter\\ctrl-c"); // Function no args. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-c"); // Cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(false); KateViewConfig::global()->setWordCompletionRemoveTail(false); TestPressKey("\\enterfti\\ctrl- \\enter\\ctrl-cq"); // Don't cut off tail. fakeCodeCompletionModel->setRemoveTailOnComplete(true); KateViewConfig::global()->setWordCompletionRemoveTail(true); // Record 'b'. - fakeCodeCompletionModel->setCompletions(QStringList() << "completionB" << "semicolonfunctionnoargs();" << "semicolonfunctionwithargs(...);"); + fakeCodeCompletionModel->setCompletions({ "completionB", "semicolonfunctionnoargs();", "semicolonfunctionwithargs(...);" }); TestPressKey("\\enterqbea\\ctrl- \\enter\\ctrl-cosemicolonfunctionw\\ctrl- \\enterX\\ctrl-cosemicolonfunctionn\\ctrl- \\enterX\\ctrl-cq"); // Save. vi_global->writeConfig(&viTestKConfig); viTestKConfig.sync(); // Overwrite 'a' and 'b' and their completions. - fakeCodeCompletionModel->setCompletions(QStringList() << "blah1"); + fakeCodeCompletionModel->setCompletions({ "blah1" }); kate_document->setText(""); TestPressKey("ggqaiblah\\ctrl- \\enter\\ctrl-cq"); TestPressKey("ddqbiblah\\ctrl- \\enter\\ctrl-cq"); // Reload. vi_global->readConfig(&viTestKConfig); // Replay reloaded. fakeCodeCompletionModel->setFailTestOnInvocation(true); kate_document->setText("funct\nnoa\ncomtail\ncomtail\ncom"); TestPressKey("gg@a\\enter@b"); FinishTest("functionwithargs(firstArg)\nnoargfunction()\ncompletionA\ncompletionAtail\ncompletionB\nsemicolonfunctionwithargs(X);\nsemicolonfunctionnoargs();X"); } // Check that undo/redo operations work properly with macros. { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cqauq"); TestPressKey("@a\\enter"); FinishTest(""); } { clearAllMacros(); BeginTest(""); TestPressKey("ihello\\ctrl-cui.bye\\ctrl-cu"); TestPressKey("qa\\ctrl-r\\enterq"); TestPressKey("@a\\enter"); FinishTest(".bye"); } // When replaying a last change in the process of replaying a macro, take the next completion // event from the last change completions log, rather than the macro completions log. // Ensure that the last change completions log is kept up to date even while we're replaying the macro. clearAllMacros(); BeginTest(""); - fakeCodeCompletionModel->setCompletions(QStringList() << "completionMacro" << "completionRepeatLastChange"); + fakeCodeCompletionModel->setCompletions({ "completionMacro", "completionRepeatLastChange" }); fakeCodeCompletionModel->setFailTestOnInvocation(false); TestPressKey("qqicompletionM\\ctrl- \\enter\\ctrl-c"); TestPressKey("a completionRep\\ctrl- \\enter\\ctrl-c"); TestPressKey(".q"); qDebug() << "text: " << kate_document->text(); kate_document->clear(); TestPressKey("gg@q"); FinishTest("completionMacro completionRepeatLastChange completionRepeatLastChange"); KateViewConfig::global()->setWordCompletionRemoveTail(oldRemoveTailOnCompletion); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_view->unregisterCompletionModel(fakeCodeCompletionModel); delete fakeCodeCompletionModel; fakeCodeCompletionModel = nullptr; // Hide the kate_view for subsequent tests. kate_view->hide(); mainWindow->hide(); KateViewConfig::global()->setViInputModeStealKeys(oldStealKeys); } void KeysTest::MarkTests() { // Difference between ` and ' DoTest(" a\n b", "jmak'aii", " a\n ib"); DoTest(" a\n b", "jmak`aii", " a\ni b"); // Last edit markers. DoTest("foo", "ean\\escgg`.r.", "foo."); DoTest("foo", "ean\\escgg`[r[", "foo["); DoTest("foo", "ean\\escgg`]r]", "foo]"); DoTest("foo bar", "ean\\escgg`]r]", "foon]bar"); DoTest("", "ibar\\escgg`.r.", "ba."); DoTest("", "ibar\\escgggUiw`.r.", ".AR"); DoTest("", "2ibar\\escgg`]r]", "barba]"); DoTest("", "2ibar\\escgg`[r[", "[arbar"); DoTest("", "3ibar\\escgg`.r.", "barbar.ar"); // Vim is weird. DoTest("", "abar\\esc.gg`]r]", "barba]"); DoTest("foo bar", "wgUiwgg`]r]", "foo BA]"); DoTest("foo bar", "wgUiwgg`.r.", "foo .AR"); DoTest("foo bar", "gUiwgg`]r.", "FO. bar"); DoTest("foo bar", "wdiwgg`[r[", "foo["); DoTest("foo bar", "wdiwgg`]r]", "foo]"); DoTest("foo bar", "wdiwgg`.r.", "foo."); DoTest("foo bar", "wciwnose\\escgg`.r.", "foo nos."); DoTest("foo bar", "wciwnose\\escgg`[r[", "foo [ose"); DoTest("foo bar", "wciwnose\\escgg`]r]", "foo nos]"); DoTest("foo", "~ibar\\escgg`[r[", "F[aroo"); DoTest("foo bar", "lragg`.r.", "f.o bar"); DoTest("foo bar", "lragg`[r[", "f[o bar"); DoTest("foo bar", "lragg`]r]", "f]o bar"); DoTest("", "ifoo\\ctrl-hbar\\esc`[r[", "[obar"); DoTest("", "ifoo\\ctrl-wbar\\esc`[r[", "[ar"); DoTest("", "if\\ctrl-hbar\\esc`[r[", "[ar"); DoTest("", "5ofoo\\escgg`[r[", "\n[oo\nfoo\nfoo\nfoo\nfoo"); DoTest("", "5ofoo\\escgg`]r]", "\nfoo\nfoo\nfoo\nfoo\nfo]"); DoTest("", "5ofoo\\escgg`.r.", "\nfoo\nfoo\nfoo\nfoo\n.oo"); DoTest("foo", "yyp`[r[", "foo\n[oo"); DoTest("xyz\nfoo", "ja\\returnbar\\esc`[r[", "xyz\n[\nbaroo"); DoTest("foo", "lrayypgg`[r[", "fao\n[ao"); DoTest("foo", "l~u`[r[", "[oo"); DoTest("foo", "l~u`.r.", ".oo"); DoTest("foo", "l~u`]r]", "]oo"); DoTest("foo", "lia\\escu`[r[", "[oo"); DoTest("foo", "lia\\escu`.r.", ".oo"); DoTest("foo", "lia\\escu`]r]", "]oo"); DoTest("foo", "l~u~`[r[", "f[o"); DoTest("foo\nbar\nxyz", "jyypu`[r[", "foo\nbar\n[yz"); DoTest("foo\nbar\nxyz", "jyypu`.r.", "foo\nbar\n.yz"); DoTest("foo\nbar\nxyz", "jyypu`]r]", "foo\nbar\n]yz"); DoTest("foo\nbar\nxyz\n123", "jdju`[r[", "foo\n[ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`.r.", "foo\n.ar\nxyz\n123"); DoTest("foo\nbar\nxyz\n123", "jdju`]r]", "foo\nbar\n]yz\n123"); DoTest("foo\nbar\nxyz\n123", "jVj~u\\esc`[r[", "foo\n[ar\nxyz\n123", ShouldFail, "Vim is weird."); } //END: KeysTest diff --git a/autotests/src/vimode/view.cpp b/autotests/src/vimode/view.cpp index e5b9e5aa..2f357170 100644 --- a/autotests/src/vimode/view.cpp +++ b/autotests/src/vimode/view.cpp @@ -1,422 +1,422 @@ /* * This file is part of the KDE libraries * * Copyright (C) 2014 Miquel Sabaté Solà * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include "view.h" using namespace KTextEditor; QTEST_MAIN(ViewTest) void ViewTest::yankHighlightingTests() { const QColor yankHighlightColour = kate_view->renderer()->config()->savedLineColor(); BeginTest("foo bar xyz"); const QList rangesInitial = rangesOnFirstLine(); Q_ASSERT(rangesInitial.isEmpty() && "Assumptions about ranges are wrong - this test is invalid and may need updating!"); TestPressKey("wyiw"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 4); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 7); } FinishTest("foo bar xyz"); BeginTest("foom bar xyz"); TestPressKey("wY"); { const QList rangesAfterYank = rangesOnFirstLine(); QCOMPARE(rangesAfterYank.size(), rangesInitial.size() + 1); QCOMPARE(rangesAfterYank.first()->attribute()->background().color(), yankHighlightColour); QCOMPARE(rangesAfterYank.first()->start().line(), 0); QCOMPARE(rangesAfterYank.first()->start().column(), 5); QCOMPARE(rangesAfterYank.first()->end().line(), 0); QCOMPARE(rangesAfterYank.first()->end().column(), 12); } FinishTest("foom bar xyz"); // Unhighlight on keypress. DoTest("foo bar xyz", "yiww", "foo bar xyz"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Update colour on config change. DoTest("foo bar xyz", "yiw", "foo bar xyz"); const QColor newYankHighlightColour = QColor(255, 0, 0); kate_view->renderer()->config()->setSavedLineColor(newYankHighlightColour); QCOMPARE(rangesOnFirstLine().first()->attribute()->background().color(), newYankHighlightColour); // Visual Mode. DoTest("foo", "viwy", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); // Unhighlight on keypress in Visual Mode DoTest("foo", "viwyw", "foo"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size()); // Add a yank highlight and directly (i.e. without using Vim commands, // which would clear the highlight) delete all text; if this deletes the yank highlight behind our back // and we don't respond correctly to this, it will be double-deleted by KateViNormalMode. // Currently, this seems like it doesn't occur, but better safe than sorry :) BeginTest("foo bar xyz"); TestPressKey("yiw"); QCOMPARE(rangesOnFirstLine().size(), rangesInitial.size() + 1); kate_document->documentReload(); kate_document->clear(); vi_input_mode->reset(); vi_input_mode_manager = vi_input_mode->viInputModeManager(); FinishTest(""); } void ViewTest::visualLineUpDownTests() { // Need to ensure we have dynamic wrap, a fixed width font, and a decent size kate_view. ensureKateViewVisible(); const QSize oldSize = kate_view->size(); kate_view->resize(400, 400); // Default size is too cramped to have interesting text in. const QFont oldFont = kate_view->renderer()->config()->font(); - QFont fixedWidthFont("Monospace"); + QFont fixedWidthFont("Courier"); fixedWidthFont.setStyleHint(QFont::TypeWriter); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up visual line up down tests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); const bool oldDynWordWrap = KateViewConfig::global()->dynWordWrap(); KateViewConfig::global()->setDynWordWrap(true); const bool oldReplaceTabsDyn = kate_document->config()->replaceTabsDyn(); kate_document->config()->setReplaceTabsDyn(false); const int oldTabWidth = kate_document->config()->tabWidth(); const int tabWidth = 5; kate_document->config()->setTabWidth(tabWidth); KateViewConfig::global()->setShowScrollbars(0); // Compute the maximum width of text before line-wrapping sets it. int textWrappingLength = 1; while (true) { QString text = QString("X").repeated(textWrappingLength) + ' ' + 'O'; const int posOfO = text.length() - 1; kate_document->setText(text); if (kate_view->cursorToCoordinate(Cursor(0, posOfO)).y() != kate_view->cursorToCoordinate(Cursor(0, 0)).y()) { textWrappingLength++; // Number of x's, plus space. break; } textWrappingLength++; } const QString fillsLineAndEndsOnSpace = QString("X").repeated(textWrappingLength - 1) + ' '; // Create a QString consisting of enough concatenated fillsLineAndEndsOnSpace to completely // fill the viewport of the kate View. QString fillsView = fillsLineAndEndsOnSpace; while (true) { kate_document->setText(fillsView); const QString visibleText = kate_document->text(kate_view->visibleRange()); if (fillsView.length() > visibleText.length() * 2) { // Overkill. break; } fillsView += fillsLineAndEndsOnSpace; } const int numVisibleLinesToFillView = fillsView.length() / fillsLineAndEndsOnSpace.length(); { // gk/ gj when there is only one line. DoTest("foo", "lgkr.", "f.o"); DoTest("foo", "lgjr.", "f.o"); } { // gk when sticky bit is set to the end. const QString originalText = fillsLineAndEndsOnSpace.repeated(2); QString expectedText = originalText; kate_document->setText(originalText); Q_ASSERT(expectedText[textWrappingLength - 1] == ' '); expectedText[textWrappingLength - 1] = '.'; DoTest(originalText, "$gkr.", expectedText); } { // Regression test: more than fill the view up, go to end, and do gk on wrapped text (used to crash). // First work out the text that will fill up the view. QString expectedText = fillsView; Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[expectedText.length() - textWrappingLength - 1] = '.'; DoTest(fillsView, "$gkr.", expectedText); } { // Jump down a few lines all in one go, where we have some variable length lines to navigate. const int numVisualLinesOnLine[] = { 3, 5, 2, 3 }; const int numLines = sizeof(numVisualLinesOnLine) / sizeof(int); const int startVisualLine = 2; const int numberLinesToGoDownInOneGo = 10; int totalVisualLines = 0; for (int i = 0; i < numLines; i++) { totalVisualLines += numVisualLinesOnLine[i]; } QString startText; for (int i = 0; i < numLines; i++) { QString thisLine = fillsLineAndEndsOnSpace.repeated(numVisualLinesOnLine[i]); // Replace trailing space with carriage return. thisLine.chop(1); thisLine.append('\n'); startText += thisLine; } QString expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo) * fillsLineAndEndsOnSpace.length()] = '.'; Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < totalVisualLines); Q_ASSERT(numberLinesToGoDownInOneGo + startVisualLine < numVisibleLinesToFillView); DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gjr.", expectedText); // Now go up a few lines. const int numLinesToGoBackUp = 7; expectedText = startText; expectedText[((startVisualLine - 1) + numberLinesToGoDownInOneGo - numLinesToGoBackUp) * fillsLineAndEndsOnSpace.length()] = '.'; DoTest(startText, QString("gj").repeated(startVisualLine - 1) + QString::number(numberLinesToGoDownInOneGo) + "gj" + QString::number(numLinesToGoBackUp) + "gkr.", expectedText); } { // Move down enough lines in one go to disappear off the view. // About half-a-viewport past the end of the current viewport. const int numberLinesToGoDown = numVisibleLinesToFillView * 3 / 2; const int visualColumnNumber = 7; Q_ASSERT(fillsLineAndEndsOnSpace.length() > visualColumnNumber); QString expectedText = fillsView.repeated(2); Q_ASSERT(expectedText[expectedText.length() - textWrappingLength - 1] == ' '); expectedText[visualColumnNumber + fillsLineAndEndsOnSpace.length() * numberLinesToGoDown] = '.'; DoTest(fillsView.repeated(2), QString("l").repeated(visualColumnNumber) + QString::number(numberLinesToGoDown) + "gjr.", expectedText); } { // Deal with dynamic wrapping and indented blocks - continuations of a line are "invisibly" idented by // the same amount as the beginning of the line, and we have to subtract this indentation. const QString unindentedFirstLine = "stickyhelper\n"; const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjr.", unindentedFirstLine + indentedFillsLineEndsOnSpace + ".INE3"); // The first, non-wrapped portion of the line is not invisibly indented, though, so ensure we don't mess that up. QString expectedSecondLine = indentedFillsLineEndsOnSpace; expectedSecondLine[numIndentationSpaces] = '.'; DoTest(unindentedFirstLine + indentedFillsLineEndsOnSpace + "LINE3", QString("l").repeated(numIndentationSpaces) + "jgjgkr.", unindentedFirstLine + expectedSecondLine + "LINE3"); } { // Take into account any invisible indentation when setting the sticky column. const int numIndentationSpaces = 5; Q_ASSERT(textWrappingLength > numIndentationSpaces * 2 /* keep some wriggle room */); const QString indentedFillsLineEndsOnSpace = QString(" ").repeated(numIndentationSpaces) + QString("X").repeated(textWrappingLength - 1 - numIndentationSpaces) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); // Make sure we can do this more than once (i.e. clear any flags that need clearing). DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString::number(textWrappingLength + posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Take into account any invisible indentation when setting the sticky column as above, but use tabs. const QString indentedFillsLineEndsOnSpace = QString("\t") + QString("X").repeated(textWrappingLength - 1 - tabWidth) + ' '; const int posInSecondWrappedLineToChange = 3; QString expectedText = indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace; expectedText[textWrappingLength - tabWidth + posInSecondWrappedLineToChange] = '.'; DoTest(indentedFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("fXf ") + QString::number(posInSecondWrappedLineToChange) + "lgkgjr.", expectedText); } { // Deal with the fact that j/ k may set a sticky column that is impossible to adhere to in visual mode because // it is too high. // Here, we have one dummy line and one wrapped line. We start from the beginning of the wrapped line and // move right until we wrap and end up at posInWrappedLineToChange one the second line of the wrapped line. // We then move up and down with j and k to set the sticky column to a value to large to adhere to in a // visual line, and try to move a visual line up. const QString dummyLineForUseWithK("dummylineforusewithk\n"); QString startText = dummyLineForUseWithK + fillsLineAndEndsOnSpace.repeated(2); const int posInWrappedLineToChange = 3; QString expectedText = startText; expectedText[dummyLineForUseWithK.length() + posInWrappedLineToChange] = '.'; DoTest(startText, "j" + QString::number(textWrappingLength + posInWrappedLineToChange) + "lkjgkr.", expectedText); } { // Ensure gj works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); QString expectedText = fillsLineAndEndsOnSpace.toLower() + fillsLineAndEndsOnSpace; expectedText[textWrappingLength] = expectedText[textWrappingLength].toLower(); DoTest(fillsLineAndEndsOnSpace.repeated(2), "vgjgu", expectedText); } { // Ensure gk works in Visual mode. Q_ASSERT(fillsLineAndEndsOnSpace.toLower() != fillsLineAndEndsOnSpace); DoTest(fillsLineAndEndsOnSpace.repeated(2), "$vgkgu", fillsLineAndEndsOnSpace + fillsLineAndEndsOnSpace.toLower()); } { // Some tests for how well we handle things with real tabs. QString beginsWithTabFillsLineEndsOnSpace = "\t"; while (beginsWithTabFillsLineEndsOnSpace.length() + (tabWidth - 1) < textWrappingLength - 1) { beginsWithTabFillsLineEndsOnSpace += 'X'; } beginsWithTabFillsLineEndsOnSpace += ' '; const QString unindentedFirstLine = "stockyhelper\n"; const int posOnThirdLineToChange = 3; QString expectedThirdLine = fillsLineAndEndsOnSpace; expectedThirdLine[posOnThirdLineToChange] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnThirdLineToChange) + "gjgjr.", unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + expectedThirdLine); // As above, but go down twice and return to the middle line. const int posOnSecondLineToChange = 2; QString expectedSecondLine = beginsWithTabFillsLineEndsOnSpace; expectedSecondLine[posOnSecondLineToChange + 1 /* "+1" as we're not counting the leading tab as a pos */] = '.'; DoTest(unindentedFirstLine + beginsWithTabFillsLineEndsOnSpace + fillsLineAndEndsOnSpace, QString("l").repeated(tabWidth + posOnSecondLineToChange) + "gjgjgkr.", unindentedFirstLine + expectedSecondLine + fillsLineAndEndsOnSpace); } // Restore back to how we were before. kate_view->resize(oldSize); kate_view->renderer()->config()->setFont(oldFont); KateViewConfig::global()->setDynWordWrap(oldDynWordWrap); kate_document->config()->setReplaceTabsDyn(oldReplaceTabsDyn); kate_document->config()->setTabWidth(oldTabWidth); } void ViewTest::ScrollViewTests() { QSKIP("This is too unstable in Jenkins", SkipAll); // First of all, we have to initialize some sizes and fonts. ensureKateViewVisible(); const QSize oldSize = kate_view->size(); kate_view->resize(200, 200); const QFont oldFont = kate_view->renderer()->config()->font(); QFont fixedWidthFont("Monospace"); fixedWidthFont.setStyleHint(QFont::TypeWriter); fixedWidthFont.setPixelSize(14); Q_ASSERT_X(QFontInfo(fixedWidthFont).fixedPitch(), "setting up ScrollViewTests", "Need a fixed pitch font!"); kate_view->renderer()->config()->setFont(fixedWidthFont); // Generating our text here. QString text; for (int i = 0; i < 20; i++) { text += " aaaaaaaaaaaaaaaa\n"; } // TODO: fix the visibleRange's tests. // zz BeginTest(text); TestPressKey("10l9jzz"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // z. BeginTest(text); TestPressKey("10l9jz."); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(4, 0, 13, 20)); FinishTest(text); // zt BeginTest(text); TestPressKey("10l9jzt"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // z BeginTest(text); TestPressKey("10l9jz\\return"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(9, 0, 18, 20)); FinishTest(text); // zb BeginTest(text); TestPressKey("10l9jzb"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 10); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // z- BeginTest(text); TestPressKey("10l9jz-"); QCOMPARE(kate_view->cursorPosition().line(), 9); QCOMPARE(kate_view->cursorPosition().column(), 4); QCOMPARE(kate_view->visibleRange(), Range(0, 0, 9, 20)); FinishTest(text); // Restore back to how we were before. kate_view->resize(oldSize); kate_view->renderer()->config()->setFont(oldFont); } void ViewTest::clipboardTests_data() { QTest::addColumn("text"); QTest::addColumn("commands"); QTest::addColumn("clipboard"); QTest::newRow("yank") << "yyfoo\nbar" << "yy" << "yyfoo\n"; QTest::newRow("delete") << "ddfoo\nbar" << "dd" << "ddfoo\n"; QTest::newRow("yank empty line") << "\nbar" << "yy" << QString(); QTest::newRow("delete word") << "word foo" << "dw" << "word "; QTest::newRow("delete onechar word") << "w foo" << "dw" << "w "; QTest::newRow("delete onechar") << "word foo" << "dc" << QString(); QTest::newRow("delete empty lines") << " \t\n\n \nfoo" << "d3d" << QString(); } void ViewTest::clipboardTests() { QFETCH(QString, text); QFETCH(QString, commands); QFETCH(QString, clipboard); QApplication::clipboard()->clear(); BeginTest(text); TestPressKey(commands); QCOMPARE(QApplication::clipboard()->text(), clipboard); } QList ViewTest::rangesOnFirstLine() { return kate_document->buffer().rangesForLine(0, kate_view, true); } diff --git a/src/buffer/katesecuretextbuffer.cpp b/src/buffer/katesecuretextbuffer.cpp index b22fda98..26e0a10d 100644 --- a/src/buffer/katesecuretextbuffer.cpp +++ b/src/buffer/katesecuretextbuffer.cpp @@ -1,168 +1,168 @@ /* This file is part of the KTextEditor project. * * Copyright (C) 2017 KDE Developers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "katesecuretextbuffer_p.h" #include "config.h" #ifndef Q_OS_WIN #include #include #endif #include #include #include #include #include KAUTH_HELPER_MAIN("org.kde.ktexteditor.katetextbuffer", SecureTextBuffer) ActionReply SecureTextBuffer::savefile(const QVariantMap &args) { - const QString sourceFile = args[QLatin1String("sourceFile")].toString(); - const QString targetFile = args[QLatin1String("targetFile")].toString(); - const QByteArray checksum = args[QLatin1String("checksum")].toByteArray(); - const uint ownerId = (uint) args[QLatin1String("ownerId")].toInt(); - const uint groupId = (uint) args[QLatin1String("groupId")].toInt(); + const QString sourceFile = args[QStringLiteral("sourceFile")].toString(); + const QString targetFile = args[QStringLiteral("targetFile")].toString(); + const QByteArray checksum = args[QStringLiteral("checksum")].toByteArray(); + const uint ownerId = (uint) args[QStringLiteral("ownerId")].toInt(); + const uint groupId = (uint) args[QStringLiteral("groupId")].toInt(); if (saveFileInternal(sourceFile, targetFile, checksum, ownerId, groupId)) { return ActionReply::SuccessReply(); } return ActionReply::HelperErrorReply(); } bool SecureTextBuffer::saveFileInternal(const QString &sourceFile, const QString &targetFile, const QByteArray &checksum, const uint ownerId, const uint groupId) { QFileInfo targetFileInfo(targetFile); if (!QDir::setCurrent(targetFileInfo.dir().path())) { return false; } // get information about target file const QString targetFileName = targetFileInfo.fileName(); targetFileInfo.setFile(targetFileName); const bool newFile = !targetFileInfo.exists(); // open source and target file QFile readFile(sourceFile); //TODO use QSaveFile for saving contents and automatic atomic move on commit() when QSaveFile's security problem // (default temporary file permissions) is fixed // // We will first generate temporary filename and then use it relatively to prevent an attacker // to trick us to write contents to a different file by changing underlying directory. QTemporaryFile tempFile(targetFileName); if (!tempFile.open()) { return false; } tempFile.close(); QString tempFileName = QFileInfo(tempFile).fileName(); tempFile.setFileName(tempFileName); if (!readFile.open(QIODevice::ReadOnly) || !tempFile.open()) { return false; } const int tempFileDescriptor = tempFile.handle(); // prepare checksum maker QCryptographicHash cryptographicHash(checksumAlgorithm); // copy contents char buffer[bufferLength]; qint64 read = -1; while ((read = readFile.read(buffer, bufferLength)) > 0) { cryptographicHash.addData(buffer, read); if (tempFile.write(buffer, read) == -1) { return false; } } // check that copying was successful and checksum matched QByteArray localChecksum = cryptographicHash.result(); if (read == -1 || localChecksum != checksum || !tempFile.flush()) { return false; } tempFile.close(); if (newFile) { // ensure new file is readable by anyone tempFile.setPermissions(tempFile.permissions() | QFile::Permission::ReadGroup | QFile::Permission::ReadOther); } else { // ensure the same file permissions tempFile.setPermissions(targetFileInfo.permissions()); // ensure file has the same owner and group as before setOwner(tempFileDescriptor, ownerId, groupId); } // rename temporary file to the target file if (moveFile(tempFileName, targetFileName)) { // temporary file was renamed, there is nothing to remove anymore tempFile.setAutoRemove(false); return true; } return false; } void SecureTextBuffer::setOwner(const int filedes, const uint ownerId, const uint groupId) { #ifndef Q_OS_WIN if (ownerId != (uint)-2 && groupId != (uint)-2) { const int result = fchown(filedes, ownerId, groupId); // set at least correct group if owner cannot be changed if (result != 0 && errno == EPERM) { fchown(filedes, getuid(), groupId); } } #else // no-op for windows #endif } bool SecureTextBuffer::moveFile(const QString &sourceFile, const QString &targetFile) { #ifndef Q_OS_WIN const int result = std::rename(QFile::encodeName(sourceFile).constData(), QFile::encodeName(targetFile).constData()); if (result == 0) { syncToDisk(QFile(targetFile).handle()); return true; } return false; #else // use racy fallback for windows QFile::remove(targetFile); return QFile::rename(sourceFile, targetFile); #endif } void SecureTextBuffer::syncToDisk(const int fd) { #ifndef Q_OS_WIN #if HAVE_FDATASYNC fdatasync(fd); #else fsync(fd); #endif #else // no-op for windows #endif } diff --git a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions index 145d9882..2cdc63d2 100644 --- a/src/buffer/org.kde.ktexteditor.katetextbuffer.actions +++ b/src/buffer/org.kde.ktexteditor.katetextbuffer.actions @@ -1,99 +1,105 @@ [Domain] Name=Document Actions Name[ca]=Accions de document Name[ca@valencia]=Accions de document Name[cs]=Činnosti dokumentu Name[da]=Dokumenthandlinger Name[de]=Dokument-Aktionen Name[en_GB]=Document Actions Name[es]=Acciones de documento Name[eu]=Dokumentuen ekintzak Name[fi]=Tiedosto-toiminnot Name[fr]=Actions du document Name[gl]=Accións do documento Name[ia]=Actiones de documento Name[it]=Azioni documenti +Name[ko]=문서 동작 Name[nl]=Documentacties Name[nn]=Dokumenthandlingar Name[pl]=Działania na dokumentach Name[pt]=Acções do Documento +Name[pt_BR]=Ações do documento Name[ru]=Действия над документом Name[sl]=Dejanja dokumenta Name[sr]=Радње над документом Name[sr@ijekavian]=Радње над документом Name[sr@ijekavianlatin]=Radnje nad dokumentom Name[sr@latin]=Radnje nad dokumentom Name[sv]=Dokumentåtgärder Name[tr]=Belge İşlemleri Name[uk]=Дії над документом Name[x-test]=xxDocument Actionsxx Name[zh_CN]=文档动作 Name[zh_TW]=文件動作 Policy=auth_admin Persistence=session [org.kde.ktexteditor.katetextbuffer.savefile] Name=Save Document Name[ast]=Guardar documentu Name[ca]=Desa el document Name[ca@valencia]=Guarda el document Name[cs]=Uložit dokument Name[da]=Gem dokument Name[de]=Dokument speichern Name[en_GB]=Save Document Name[es]=Guardar documento Name[eu]=Gorde dokumentua Name[fi]=Tallenna tiedosto Name[fr]=Enregistrer le document Name[gl]=Gardar o documento Name[ia]=Salveguarda documento Name[it]=Salva documento +Name[ko]=문서 저장 Name[nl]=Document opslaan Name[nn]=Lagra dokument Name[pl]=Zapisz dokument Name[pt]=Gravar o Documento +Name[pt_BR]=Salvar documento Name[ru]=Сохранение документа Name[sl]=Shrani dokument Name[sr]=Сачувај документ Name[sr@ijekavian]=Сачувај документ Name[sr@ijekavianlatin]=Sačuvaj dokument Name[sr@latin]=Sačuvaj dokument Name[sv]=Spara dokument Name[tr]=Belgeyi Kaydet Name[uk]=Зберегти документ Name[x-test]=xxSave Documentxx Name[zh_CN]=保存文档 Name[zh_TW]=儲存文件 Description=Root privileges are needed to save this document Description[ast]=Precísense privilexos root pa guardar esti documentu Description[ca]=Es requereixen privilegis d'administrador per desar aquest document Description[ca@valencia]=Es requereixen privilegis d'administrador per guardar este document Description[cs]=K uložení dokumentu je potřeba práva uživatele root Description[da]=Root-rettigheder kræves for at gemme dette dokument Description[de]=Rechte als Systemverwalter sind für das Speichern diese Dokuments erforderlich Description[en_GB]=Root privileges are needed to save this document Description[es]=Se necesitan permisos de «root» para guardar este documento Description[eu]=Root pribilegioak behar dira dokumentu hau gordetzeko Description[fi]=Tämän tiedoston tallentamiseen tarvitaan pääkäyttäjän oikeudet Description[fr]=Vous devez disposer des privilèges de superutilisateur pour enregistrer ce document Description[gl]=Necesítanse privilexios de administrador para gardar este documento. Description[ia]=Privilegios de radice root es necessari per salveguardar iste documento Description[it]=Sono necessari i privilegi di root per salvare questo documento +Description[ko]=이 문서를 저장하려면 루트 권한이 필요함 Description[nl]=Om dit document op te slaan zijn root-rechten nodig Description[nn]=Du må ha rotløyve for å kunna lagra dokumentet Description[pl]=Wymagane są uprawnienia administratora do zapisania tego dokumentu Description[pt]=São necessários privilégios de 'root' para gravar este documento +Description[pt_BR]=São necessários privilégios de root para salvar este documento Description[ru]=Для сохранения этого документа необходимы права администратора Description[sl]=Za shranjevanje tega dokumenta so zahtevana skrbniška dovoljenja Description[sr]=Уписивање овог документа захтева корена овлашћења Description[sr@ijekavian]=Уписивање овог документа захтева корена овлашћења Description[sr@ijekavianlatin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja Description[sr@latin]=Upisivanje ovog dokumenta zahteva korena ovlašćenja Description[sv]=Systemadministratörsprivilegier krävs för att spara dokumentet Description[tr]=Bu belgeyi kaydetmek için yönetici ayrıcalıkları gereklidir Description[uk]=Для збереження документа потрібні права доступу користувача root Description[x-test]=xxRoot privileges are needed to save this documentxx Description[zh_CN]=保存文档需要超级用户权限 Description[zh_TW]=需要 root 權限以儲存此文件 Policy=auth_admin Persistence=session diff --git a/src/completion/katewordcompletion.cpp b/src/completion/katewordcompletion.cpp index 51cb7c89..e836ee94 100644 --- a/src/completion/katewordcompletion.cpp +++ b/src/completion/katewordcompletion.cpp @@ -1,583 +1,583 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003 Anders Lund * Copyright (C) 2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ //BEGIN includes #include "katewordcompletion.h" #include "kateview.h" #include "kateconfig.h" #include "katedocument.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include //END /// Amount of characters the document may have to enable automatic invocation (1MB) static const int autoInvocationMaxFilesize = 1000000; //BEGIN KateWordCompletionModel KateWordCompletionModel::KateWordCompletionModel(QObject *parent) : CodeCompletionModel (parent) , m_automatic(false) { setHasGroups(false); } KateWordCompletionModel::~KateWordCompletionModel() { } void KateWordCompletionModel::saveMatches(KTextEditor::View *view, const KTextEditor::Range &range) { m_matches = allMatches(view, range); m_matches.sort(); } QVariant KateWordCompletionModel::data(const QModelIndex &index, int role) const { if (role == UnimportantItemRole) { return QVariant(true); } if (role == InheritanceDepth) { return 10000; } if (!index.parent().isValid()) { //It is the group header switch (role) { case Qt::DisplayRole: return i18n("Auto Word Completion"); case GroupRole: return Qt::DisplayRole; } } if (index.column() == KTextEditor::CodeCompletionModel::Name && role == Qt::DisplayRole) { return m_matches.at(index.row()); } if (index.column() == KTextEditor::CodeCompletionModel::Icon && role == Qt::DecorationRole) { static QIcon icon(QIcon::fromTheme(QStringLiteral("insert-text")).pixmap(QSize(16, 16))); return icon; } return QVariant(); } QModelIndex KateWordCompletionModel::parent(const QModelIndex &index) const { if (index.internalId()) { return createIndex(0, 0, quintptr(0)); } else { return QModelIndex(); } } QModelIndex KateWordCompletionModel::index(int row, int column, const QModelIndex &parent) const { if (!parent.isValid()) { if (row == 0) { return createIndex(row, column, quintptr(0)); } else { return QModelIndex(); } } else if (parent.parent().isValid()) { return QModelIndex(); } if (row < 0 || row >= m_matches.count() || column < 0 || column >= ColumnCount) { return QModelIndex(); } return createIndex(row, column, 1); } int KateWordCompletionModel::rowCount(const QModelIndex &parent) const { if (!parent.isValid() && !m_matches.isEmpty()) { return 1; //One root node to define the custom group } else if (parent.parent().isValid()) { return 0; //Completion-items have no children } else { return m_matches.count(); } } bool KateWordCompletionModel::shouldStartCompletion(KTextEditor::View *view, const QString &insertedText, bool userInsertion, const KTextEditor::Cursor &position) { if (!userInsertion) { return false; } if (insertedText.isEmpty()) { return false; } KTextEditor::ViewPrivate *v = qobject_cast (view); if (view->document()->totalCharacters() > autoInvocationMaxFilesize) { // Disable automatic invocation for files larger than 1MB (see benchmarks) return false; } const QString &text = view->document()->line(position.line()).left(position.column()); const uint check = v->config()->wordCompletionMinimalWordLength(); // Start completion immediately if min. word size is zero if (!check) { return true; } // Otherwise, check if user has typed long enough text... const int start = text.length(); const int end = start - check; if (end < 0) { return false; } for (int i = start - 1; i >= end; i--) { const QChar c = text.at(i); if (!(c.isLetter() || (c.isNumber()) || c == QLatin1Char('_'))) { return false; } } return true; } bool KateWordCompletionModel::shouldAbortCompletion(KTextEditor::View *view, const KTextEditor::Range &range, const QString ¤tCompletion) { if (m_automatic) { KTextEditor::ViewPrivate *v = qobject_cast (view); if (currentCompletion.length() < v->config()->wordCompletionMinimalWordLength()) { return true; } } return CodeCompletionModelControllerInterface::shouldAbortCompletion(view, range, currentCompletion); } void KateWordCompletionModel::completionInvoked(KTextEditor::View *view, const KTextEditor::Range &range, InvocationType it) { m_automatic = it == AutomaticInvocation; saveMatches(view, range); } /** * Scan throughout the entire document for possible completions, * ignoring any dublets and words shorter than configured and/or * reasonable minimum length. */ QStringList KateWordCompletionModel::allMatches(KTextEditor::View *view, const KTextEditor::Range &range) const { QSet result; const int minWordSize = qMax(2, qobject_cast(view)->config()->wordCompletionMinimalWordLength()); const int lines = view->document()->lines(); for (int line = 0; line < lines; line++) { const QString &text = view->document()->line(line); int wordBegin = 0; int offset = 0; const int end = text.size(); const bool cursorLine = view->cursorPosition().line() == line; while (offset < end) { const QChar c = text.at(offset); // increment offset when at line end, so we take the last character too if ((! c.isLetterOrNumber() && c != QLatin1Char('_')) || (offset == end - 1 && offset++)) { if (offset - wordBegin > minWordSize && (line != range.end().line() || offset != range.end().column())) { /** * don't add the word we are inside with cursor! */ if (!cursorLine || (view->cursorPosition().column() < wordBegin || view->cursorPosition().column() > offset)) { result.insert(text.mid(wordBegin, offset - wordBegin)); } } wordBegin = offset + 1; } if (c.isSpace()) { wordBegin = offset + 1; } offset += 1; } } return result.values(); } void KateWordCompletionModel::executeCompletionItem (KTextEditor::View *view , const KTextEditor::Range &word , const QModelIndex &index ) const { KTextEditor::ViewPrivate *v = qobject_cast (view); if (v->config()->wordCompletionRemoveTail()) { int tailStart = word.end().column(); const QString &line = view->document()->line(word.end().line()); int tailEnd = line.length(); for (int i = word.end().column(); i < tailEnd; ++i) { // Letters, numbers and underscore are part of a word! /// \todo Introduce configurable \e word-separators?? if (!line[i].isLetterOrNumber() && line[i] != QLatin1Char('_')) { tailEnd = i; } } int sizeDiff = m_matches.at(index.row()).size() - (word.end().column() - word.start().column()); tailStart += sizeDiff; tailEnd += sizeDiff; KTextEditor::Range tail(KTextEditor::Cursor(word.start().line(), tailStart), KTextEditor::Cursor(word.end().line(), tailEnd)); view->document()->replaceText(word, m_matches.at(index.row())); v->doc()->editEnd(); v->doc()->editStart(); view->document()->replaceText(tail, QString()); } else { view->document()->replaceText(word, m_matches.at(index.row())); } } KTextEditor::CodeCompletionModelControllerInterface::MatchReaction KateWordCompletionModel::matchingItem(const QModelIndex & /*matched*/) { return HideListIfAutomaticInvocation; } bool KateWordCompletionModel::shouldHideItemsWithEqualNames() const { // We don't want word-completion items if the same items // are available through more sophisticated completion models return true; } // Return the range containing the word left of the cursor KTextEditor::Range KateWordCompletionModel::completionRange(KTextEditor::View *view, const KTextEditor::Cursor &position) { int line = position.line(); int col = position.column(); KTextEditor::Document *doc = view->document(); while (col > 0) { const QChar c = (doc->characterAt(KTextEditor::Cursor(line, col - 1))); if (c.isLetterOrNumber() || c.isMark() || c == QLatin1Char('_')) { col--; continue; } break; } return KTextEditor::Range(KTextEditor::Cursor(line, col), position); } //END KateWordCompletionModel //BEGIN KateWordCompletionView struct KateWordCompletionViewPrivate { KTextEditor::MovingRange *liRange; // range containing last inserted text KTextEditor::Range dcRange; // current range to be completed by directional completion KTextEditor::Cursor dcCursor; // directional completion search cursor - QRegExp re; // hrm + QRegularExpression wordRegEx; int directionalPos; // be able to insert "" at the correct time bool isCompleting; // true when the directional completion is doing a completion }; KateWordCompletionView::KateWordCompletionView(KTextEditor::View *view, KActionCollection *ac) : QObject(view), m_view(view), m_dWCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()), d(new KateWordCompletionViewPrivate) { d->isCompleting = false; d->dcRange = KTextEditor::Range::invalid(); d->liRange = static_cast(m_view->document())->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand); const KColorScheme &colors(KTextEditor::EditorPrivate::self()->defaultColors().view()); KTextEditor::Attribute::Ptr a = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); a->setBackground(colors.background(KColorScheme::ActiveBackground)); a->setForeground(colors.foreground(KColorScheme::ActiveText)); // ### this does 0 d->liRange->setAttribute(a); QAction *action; if (qobject_cast(view)) { action = new QAction(i18n("Shell Completion"), this); ac->addAction(QStringLiteral("doccomplete_sh"), action); connect(action, SIGNAL(triggered()), this, SLOT(shellComplete())); } action = new QAction(i18n("Reuse Word Above"), this); ac->addAction(QStringLiteral("doccomplete_bw"), action); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_8); connect(action, SIGNAL(triggered()), this, SLOT(completeBackwards())); action = new QAction(i18n("Reuse Word Below"), this); ac->addAction(QStringLiteral("doccomplete_fw"), action); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_9); connect(action, SIGNAL(triggered()), this, SLOT(completeForwards())); } KateWordCompletionView::~KateWordCompletionView() { delete d; } void KateWordCompletionView::completeBackwards() { complete(false); } void KateWordCompletionView::completeForwards() { complete(); } // Pop up the editors completion list if applicable void KateWordCompletionView::popupCompletionList() { qCDebug(LOG_KTE) << "entered ..."; KTextEditor::Range r = range(); KTextEditor::CodeCompletionInterface *cci = qobject_cast(m_view); if (!cci || cci->isCompletionActive()) { return; } m_dWCompletionModel->saveMatches(m_view, r); qCDebug(LOG_KTE) << "after save matches ..."; if (! m_dWCompletionModel->rowCount(QModelIndex())) { return; } cci->startCompletion(r, m_dWCompletionModel); } // Contributed by void KateWordCompletionView::shellComplete() { KTextEditor::Range r = range(); QStringList matches = m_dWCompletionModel->allMatches(m_view, r); if (matches.size() == 0) { return; } QString partial = findLongestUnique(matches, r.columnWidth()); if (partial.isEmpty()) { popupCompletionList(); } else { m_view->document()->insertText(r.end(), partial.mid(r.columnWidth())); d->liRange->setView(m_view); d->liRange->setRange(KTextEditor::Range(r.end(), partial.length() - r.columnWidth())); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); } } // Do one completion, searching in the desired direction, // if possible void KateWordCompletionView::complete(bool fw) { KTextEditor::Range r = range(); int inc = fw ? 1 : -1; KTextEditor::Document *doc = m_view->document(); if (d->dcRange.isValid()) { //qCDebug(LOG_KTE)<<"CONTINUE "<dcRange; // this is a repeted activation // if we are back to where we started, reset. if ((fw && d->directionalPos == -1) || (!fw && d->directionalPos == 1)) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); if (spansColumns > 0) { doc->removeText(*d->liRange); } d->liRange->setRange(KTextEditor::Range::invalid()); d->dcCursor = r.end(); d->directionalPos = 0; return; } if (fw) { const int spansColumns = d->liRange->end().column() - d->liRange->start().column(); d->dcCursor.setColumn(d->dcCursor.column() + spansColumns); } d->directionalPos += inc; } else { // new completion, reset all //qCDebug(LOG_KTE)<<"RESET FOR NEW"; d->dcRange = r; d->liRange->setRange(KTextEditor::Range::invalid()); d->dcCursor = r.start(); d->directionalPos = inc; d->liRange->setView(m_view); connect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); } - d->re.setPattern(QLatin1String("\\b") + doc->text(d->dcRange) + QLatin1String("(\\w+)")); + d->wordRegEx.setPattern(QLatin1String("\\b") + doc->text(d->dcRange) + QLatin1String("(\\w+)")); int pos(0); QString ln = doc->line(d->dcCursor.line()); while (true) { - //qCDebug(LOG_KTE)<<"SEARCHING FOR "<re.pattern()<<" "<dcCursor; - pos = fw ? - d->re.indexIn(ln, d->dcCursor.column()) : - d->re.lastIndexIn(ln, d->dcCursor.column()); + //qCDebug(LOG_KTE)<<"SEARCHING FOR "<wordRegEx.pattern()<<" "<dcCursor; + QRegularExpressionMatch match; + pos = fw ? ln.indexOf(d->wordRegEx, d->dcCursor.column(), &match) + : ln.lastIndexOf(d->wordRegEx, d->dcCursor.column(), &match); - if (pos > -1) { // we matched a word + if (match.hasMatch()) { // we matched a word //qCDebug(LOG_KTE)<<"USABLE MATCH"; - QString m = d->re.cap(1); + const QStringRef m = match.capturedRef(1); if (m != doc->text(*d->liRange) && (d->dcCursor.line() != d->dcRange.start().line() || pos != d->dcRange.start().column())) { // we got good a match! replace text and return. d->isCompleting = true; KTextEditor::Range replaceRange(d->liRange->toRange()); if (!replaceRange.isValid()) { replaceRange.setRange(r.end(), r.end()); } - doc->replaceText(replaceRange, m); + doc->replaceText(replaceRange, m.toString()); d->liRange->setRange(KTextEditor::Range(d->dcRange.end(), m.length())); d->dcCursor.setColumn(pos); // for next try d->isCompleting = false; return; } // equal to last one, continue else { //qCDebug(LOG_KTE)<<"SKIPPING, EQUAL MATCH"; d->dcCursor.setColumn(pos); // for next try if (fw) { d->dcCursor.setColumn(pos + m.length()); } else { if (pos == 0) { if (d->dcCursor.line() > 0) { int l = d->dcCursor.line() + inc; ln = doc->line(l); d->dcCursor.setPosition(l, ln.length()); } else { return; } } else { d->dcCursor.setColumn(d->dcCursor.column() - 1); } } } } else { // no match //qCDebug(LOG_KTE)<<"NO MATCH"; if ((! fw && d->dcCursor.line() == 0) || (fw && d->dcCursor.line() >= doc->lines())) { return; } int l = d->dcCursor.line() + inc; ln = doc->line(l); d->dcCursor.setPosition(l, fw ? 0 : ln.length()); } } // while true } void KateWordCompletionView::slotCursorMoved() { if (d->isCompleting) { return; } d->dcRange = KTextEditor::Range::invalid(); disconnect(m_view, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), this, SLOT(slotCursorMoved())); d->liRange->setView(nullptr); d->liRange->setRange(KTextEditor::Range::invalid()); } // Contributed by FIXME QString KateWordCompletionView::findLongestUnique(const QStringList &matches, int lead) const { QString partial = matches.first(); foreach (const QString ¤t, matches) { if (!current.startsWith(partial)) { while (partial.length() > lead) { partial.remove(partial.length() - 1, 1); if (current.startsWith(partial)) { break; } } if (partial.length() == lead) { return QString(); } } } return partial; } // Return the string to complete (the letters behind the cursor) QString KateWordCompletionView::word() const { return m_view->document()->text(range()); } // Return the range containing the word behind the cursor KTextEditor::Range KateWordCompletionView::range() const { return m_dWCompletionModel->completionRange(m_view, m_view->cursorPosition()); } //END diff --git a/src/data/ktexteditor.desktop b/src/data/ktexteditor.desktop index f8e7cfff..f5eef4f0 100644 --- a/src/data/ktexteditor.desktop +++ b/src/data/ktexteditor.desktop @@ -1,60 +1,60 @@ [Desktop Entry] Type=ServiceType X-KDE-ServiceType=KTextEditor/Document X-KDE-Derived=KParts/ReadWritePart Comment=Embeddable Text Editor Component (with Doc/View Separation) Comment[ar]=مكوّن محرّر نصوص يمكن تضمينه (يفصل بين Doc والمناظير) Comment[ast]=Componente d'editor empotrable de testu (con separtación de documentos/vista) Comment[bg]=Текстов редактор (с разделение между Doc/View) Comment[bs]=Ugradiva komponenta uređivača teksta (s razdvajanjem dokumenta i pogleda) Comment[ca]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[ca@valencia]=Component incrustable de l'editor de text (amb separació Doc/Vista) Comment[cs]=Pohltitelná komponenta textového editoru (s oddělením Doc/View) Comment[da]=Teksteditorkomponent som kan indlejres (med dok./visning-adskillelse) Comment[de]=Einbettungsfähige Editorkomponente (mit Text/Ansicht-Aufteilung) Comment[el]=Ενσωματώσιμο συστατικό επεξεργαστή κειμένου (με διαχωρισμό των δεδομένων από την προβολή τους) Comment[en_GB]=Embeddable Text Editor Component (with Doc/View Separation) Comment[es]=Componente empotrable de edición de texto (con separación documentos/vista) Comment[et]=Põimitav tekstiredaktori komponent (dokumendi/vaate eraldamisega) Comment[eu]=Testu-editore osagai txertagarria (dokumentu/ikuspegi banaketarekin) Comment[fi]=Upotettava tekstimuokkauskomponentti (Tiedosto/Näkymä-jaolla) Comment[fr]=Composant intégrable d'édition de texte (avec séparation Doc / Vue) Comment[ga]=Comhpháirt eagarthóireacht téacs inleabaithe (le deighilt idir cáipéis agus amharc) Comment[gd]=Co-phàirt deasaiche teacsa a ghabhas leabachadh (le sgaradh eadar modh deasachaidh is seallaidh) Comment[gl]=Compoñente integrábel de edición de texto (cunha Separación Doc/Vista) Comment[hu]=Beágyazható szövegszerkesztő (dokumentum/nézet modellel) Comment[ia]=Componente del editor interne de texto (con separation Doc/Vista) Comment[is]=Ívafin textaritilseining (með skjal/sýn aðskilnaði) Comment[it]=Componente editor di testo integrabile (con separazione documento/vista) Comment[kk]=Ендірілетін мәтін өңдеу компоненті (Құжат/көрініс үлгіні қолдайтын) Comment[km]=សមាសភាគ​កម្មវិធី​និពន្ធ​អត្ថបទ​ដែល​អាច​បង្កប់​បាន​​ (ជា​មួយ​ការ​បំបែក Doc/View ) -Comment[ko]=끼워넣을 수 있는 텍스트 편집기 구성 요소 (문서/뷰 구분) +Comment[ko]=끼워넣을 수 있는 텍스트 편집기 구성 요소(문서/뷰 구분) Comment[lt]=Įtraukiamas tekstų redagavimo komponentas (su dokumento/žiūrėjimo atskyrimu) Comment[lv]=Iegultā teksta redaktora komponente (ar redaktora/skatītāja atdalīšanu) Comment[mr]=अंतर्भूतयोग्य पाठ्य संपादक घटक (Doc/View विभाजनासकट) Comment[nb]=Innebygget skriveprogram-komponent (med Doc/View-skille) Comment[nds]=Inbettbor Texteditor-Komponent (mit Dokment-/Ansicht-Trennen) Comment[nl]=Ingebed tekstinvoercomponent (met scheiding van tekst/weergave) Comment[nn]=Innebyggbar skriveprogramkomponent (med Doc/View-skilje) Comment[pa]=ਇੰਬੈੱਡਯੋਗ ਟੈਕਸਟ ਐਡੀਟਰ ਭਾਗ (Doc/ਝਲਕ ਵੱਖ ਕਰਨ ਨਾਲ) Comment[pl]=Komponent osadzanego edytora tekstu (z podziałem Dokument/Widok) Comment[pt]=Componente Incorporado do Editor de Texto (com Separação entre Documentos) Comment[pt_BR]=Componente de edição de textos integrado (com separação de documentação/visualização) Comment[ro]=Componentă de Editare Text Încorporabilă (cu Separarea Documentului/Vizualizării) Comment[ru]=Встраиваемый компонент редактора текста (с поддержкой модели документ/вид) Comment[si]=තිළැලිය හැකි පෙළ සකසන සංරචකය (ලේඛන/දසුන් වෙන්කිරීම සහිතව) Comment[sk]=Vložiteľný komponent textového editora (s oddelením Doc/Pohľad) Comment[sl]=Vgradljiva enota urejevalnika besedil (z ločevanjem pogleda in dokumenta) Comment[sr]=Угнездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavian]=Угњездива компонента уређивача текста (уз раздвајање документ-приказ) Comment[sr@ijekavianlatin]=Ugnjezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sr@latin]=Ugnezdiva komponenta uređivača teksta (uz razdvajanje dokument-prikaz) Comment[sv]=Inbäddningsbar texteditor (med dok/vyseparation) Comment[tg]=Қисми таҳриргари матнии дарунсохтшаванда (бо тақсимкунии Санад/Намоиш) Comment[tr]=Gömülebilir Metin Düzenleyici Bileşeni (Doc/View ayrımı ile) Comment[ug]=سىڭدۈرۈشچان تېكىست تەھرىرلىگۈچ بۆلىكى(پۈتۈك/كۆرۈنۈش ئايرىلىدۇ) Comment[uk]=Компонент редактора текстів, який можна вбудовувати (з розділенням документ/вигляд) Comment[wa]=Ravalé compôzant aspougneu d' tecse (avou dispårtaedje documint/vuwe) Comment[x-test]=xxEmbeddable Text Editor Component (with Doc/View Separation)xx Comment[zh_CN]=可嵌入的文本编辑器部件(文档和视图分离) Comment[zh_TW]=可嵌入的文字編輯器元件 (Doc/View 分開) diff --git a/src/dialogs/katedialogs.cpp b/src/dialogs/katedialogs.cpp index 211ba4a4..28468627 100644 --- a/src/dialogs/katedialogs.cpp +++ b/src/dialogs/katedialogs.cpp @@ -1,1462 +1,1462 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2003 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2006 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig Copyright (C) 2009 Erlend Hamberg Based on work of: Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katedialogs.h" #include #include #include "kateautoindent.h" #include "katebuffer.h" #include "kateconfig.h" #include "katedocument.h" #include "kateglobal.h" #include "kateschema.h" #include "katemodeconfigpage.h" #include "kateview.h" #include "spellcheck/spellcheck.h" #include "kateglobal.h" // auto generated ui files #include "ui_textareaappearanceconfigwidget.h" #include "ui_bordersappearanceconfigwidget.h" #include "ui_navigationconfigwidget.h" #include "ui_editconfigwidget.h" #include "ui_indentationconfigwidget.h" #include "ui_completionconfigtab.h" #include "ui_opensaveconfigwidget.h" #include "ui_opensaveconfigadvwidget.h" #include "ui_spellcheckconfigwidget.h" #include #include #include #include #include #include #include "katepartdebug.h" #include "kateabstractinputmodefactory.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // trailing slash is important #define HLDOWNLOADPATH QStringLiteral("http://kate.kde.org/syntax/") //END //BEGIN KateIndentConfigTab KateIndentConfigTab::KateIndentConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::IndentationConfigWidget(); ui->setupUi(newWidget); ui->cmbMode->addItems(KateAutoIndent::listModes()); ui->label->setTextInteractionFlags(Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard); connect(ui->label, SIGNAL(linkActivated(QString)), this, SLOT(showWhatsThis(QString))); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbMode, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentMixed, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbIndentWithTabs, SIGNAL(toggled(bool)), ui->sbIndentWidth, SLOT(setDisabled(bool))); connect(ui->chkKeepExtraSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkIndentPaste, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkBackspaceUnindents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbTabWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->sbIndentWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->rbTabAdvances, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabIndents, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->rbTabSmart, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateIndentConfigTab::~KateIndentConfigTab() { delete ui; } void KateIndentConfigTab::slotChanged() { if (ui->rbIndentWithTabs->isChecked()) { ui->sbIndentWidth->setValue(ui->sbTabWidth->value()); } KateConfigPage::slotChanged(); } void KateIndentConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateIndentConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setKeepExtraSpaces(ui->chkKeepExtraSpaces->isChecked()); KateDocumentConfig::global()->setBackspaceIndents(ui->chkBackspaceUnindents->isChecked()); KateDocumentConfig::global()->setIndentPastedText(ui->chkIndentPaste->isChecked()); KateDocumentConfig::global()->setIndentationWidth(ui->sbIndentWidth->value()); KateDocumentConfig::global()->setIndentationMode(KateAutoIndent::modeName(ui->cmbMode->currentIndex())); KateDocumentConfig::global()->setTabWidth(ui->sbTabWidth->value()); KateDocumentConfig::global()->setReplaceTabsDyn(ui->rbIndentWithSpaces->isChecked()); if (ui->rbTabAdvances->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabInsertsTab); } else if (ui->rbTabIndents->isChecked()) { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabIndents); } else { KateDocumentConfig::global()->setTabHandling(KateDocumentConfig::tabSmart); } KateDocumentConfig::global()->configEnd(); } void KateIndentConfigTab::reload() { ui->sbTabWidth->setSuffix(ki18np(" character", " characters")); ui->sbTabWidth->setValue(KateDocumentConfig::global()->tabWidth()); ui->sbIndentWidth->setSuffix(ki18np(" character", " characters")); ui->sbIndentWidth->setValue(KateDocumentConfig::global()->indentationWidth()); ui->chkKeepExtraSpaces->setChecked(KateDocumentConfig::global()->keepExtraSpaces()); ui->chkIndentPaste->setChecked(KateDocumentConfig::global()->indentPastedText()); ui->chkBackspaceUnindents->setChecked(KateDocumentConfig::global()->backspaceIndents()); ui->rbTabAdvances->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabInsertsTab); ui->rbTabIndents->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabIndents); ui->rbTabSmart->setChecked(KateDocumentConfig::global()->tabHandling() == KateDocumentConfig::tabSmart); ui->cmbMode->setCurrentIndex(KateAutoIndent::modeNumber(KateDocumentConfig::global()->indentationMode())); if (KateDocumentConfig::global()->replaceTabsDyn()) { ui->rbIndentWithSpaces->setChecked(true); } else { if (KateDocumentConfig::global()->indentationWidth() == KateDocumentConfig::global()->tabWidth()) { ui->rbIndentWithTabs->setChecked(true); } else { ui->rbIndentMixed->setChecked(true); } } ui->sbIndentWidth->setEnabled(!ui->rbIndentWithTabs->isChecked()); } QString KateIndentConfigTab::name() const { return i18n("Indentation"); } //END KateIndentConfigTab //BEGIN KateCompletionConfigTab KateCompletionConfigTab::KateCompletionConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::CompletionConfigTab(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->chkAutoCompletionEnabled, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbWordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->gbKeywordCompletion, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->minimalWordLength, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->removeTail, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateCompletionConfigTab::~KateCompletionConfigTab() { delete ui; } void KateCompletionConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateCompletionConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateViewConfig::global()->setAutomaticCompletionInvocation(ui->chkAutoCompletionEnabled->isChecked()); KateViewConfig::global()->setWordCompletion(ui->gbWordCompletion->isChecked()); KateViewConfig::global()->setWordCompletionMinimalWordLength(ui->minimalWordLength->value()); KateViewConfig::global()->setWordCompletionRemoveTail(ui->removeTail->isChecked()); KateViewConfig::global()->setKeywordCompletion(ui->gbKeywordCompletion->isChecked()); KateViewConfig::global()->configEnd(); } void KateCompletionConfigTab::reload() { ui->chkAutoCompletionEnabled->setChecked(KateViewConfig::global()->automaticCompletionInvocation()); ui->gbWordCompletion->setChecked(KateViewConfig::global()->wordCompletion()); ui->minimalWordLength->setValue(KateViewConfig::global()->wordCompletionMinimalWordLength()); ui->gbKeywordCompletion->setChecked(KateViewConfig::global()->keywordCompletion()); ui->removeTail->setChecked(KateViewConfig::global()->wordCompletionRemoveTail()); } QString KateCompletionConfigTab::name() const { return i18n("Auto Completion"); } //END KateCompletionConfigTab //BEGIN KateSpellCheckConfigTab KateSpellCheckConfigTab::KateSpellCheckConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::SpellCheckConfigWidget(); ui->setupUi(newWidget); // What's This? help can be found in the ui file reload(); // // after initial reload, connect the stuff for the changed () signal m_sonnetConfigWidget = new Sonnet::ConfigWidget(this); connect(m_sonnetConfigWidget, SIGNAL(configChanged()), this, SLOT(slotChanged())); layout->addWidget(m_sonnetConfigWidget); layout->addWidget(newWidget); setLayout(layout); } KateSpellCheckConfigTab::~KateSpellCheckConfigTab() { delete ui; } void KateSpellCheckConfigTab::showWhatsThis(const QString &text) { QWhatsThis::showText(QCursor::pos(), text); } void KateSpellCheckConfigTab::apply() { if (!hasChanged()) { // nothing changed, no need to apply stuff return; } m_changed = false; KateDocumentConfig::global()->configStart(); m_sonnetConfigWidget->save(); KateDocumentConfig::global()->configEnd(); foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { doc->refreshOnTheFlyCheck(); } } void KateSpellCheckConfigTab::reload() { // does nothing } QString KateSpellCheckConfigTab::name() const { return i18n("Spellcheck"); } //END KateSpellCheckConfigTab //BEGIN KateNavigationConfigTab KateNavigationConfigTab::KateNavigationConfigTab(QWidget *parent) : KateConfigPage(parent) { // This will let us having more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::NavigationConfigWidget(); ui->setupUi(newWidget); // What's This? Help is in the ui-files reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cbTextSelectionMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkSmartHome, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkPagingMovesCursor, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbAutoCenterCursor, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkScrollPastEnd, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } KateNavigationConfigTab::~KateNavigationConfigTab() { delete ui; } void KateNavigationConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setSmartHome(ui->chkSmartHome->isChecked()); KateViewConfig::global()->setAutoCenterLines(qMax(0, ui->sbAutoCenterCursor->value())); KateDocumentConfig::global()->setPageUpDownMovesCursor(ui->chkPagingMovesCursor->isChecked()); KateViewConfig::global()->setPersistentSelection(ui->cbTextSelectionMode->currentIndex() == 1); KateViewConfig::global()->setScrollPastEnd(ui->chkScrollPastEnd->isChecked()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateNavigationConfigTab::reload() { ui->cbTextSelectionMode->setCurrentIndex(KateViewConfig::global()->persistentSelection() ? 1 : 0); ui->chkSmartHome->setChecked(KateDocumentConfig::global()->smartHome()); ui->chkPagingMovesCursor->setChecked(KateDocumentConfig::global()->pageUpDownMovesCursor()); ui->sbAutoCenterCursor->setValue(KateViewConfig::global()->autoCenterLines()); ui->chkScrollPastEnd->setChecked(KateViewConfig::global()->scrollPastEnd()); } QString KateNavigationConfigTab::name() const { return i18n("Text Navigation"); } //END KateNavigationConfigTab //BEGIN KateEditGeneralConfigTab KateEditGeneralConfigTab::KateEditGeneralConfigTab(QWidget *parent) : KateConfigPage(parent) { QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::EditConfigWidget(); ui->setupUi(newWidget); QList inputModes = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *fact, inputModes) { ui->cmbInputMode->addItem(fact->name(), static_cast(fact->inputMode())); } reload(); connect(ui->chkStaticWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkShowStaticWordWrapMarker, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->sbWordWrap, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->chkAutoBrackets, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkSmartCopyCut, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->cmbInputMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); // "What's this?" help is in the ui-file layout->addWidget(newWidget); setLayout(layout); } KateEditGeneralConfigTab::~KateEditGeneralConfigTab() { delete ui; } void KateEditGeneralConfigTab::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateDocumentConfig::global()->setWordWrapAt(ui->sbWordWrap->value()); KateDocumentConfig::global()->setWordWrap(ui->chkStaticWordWrap->isChecked()); KateRendererConfig::global()->setWordWrapMarker(ui->chkShowStaticWordWrapMarker->isChecked()); KateViewConfig::global()->setAutoBrackets(ui->chkAutoBrackets->isChecked()); KateViewConfig::global()->setSmartCopyCut(ui->chkSmartCopyCut->isChecked()); KateViewConfig::global()->setInputModeRaw(ui->cmbInputMode->currentData().toInt()); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateEditGeneralConfigTab::reload() { ui->chkStaticWordWrap->setChecked(KateDocumentConfig::global()->wordWrap()); ui->chkShowStaticWordWrapMarker->setChecked(KateRendererConfig::global()->wordWrapMarker()); ui->sbWordWrap->setSuffix(ki18ncp("Wrap words at (value is at 20 or larger)", " character", " characters")); ui->sbWordWrap->setValue(KateDocumentConfig::global()->wordWrapAt()); ui->chkAutoBrackets->setChecked(KateViewConfig::global()->autoBrackets()); ui->chkSmartCopyCut->setChecked(KateViewConfig::global()->smartCopyCut()); const int id = static_cast(KateViewConfig::global()->inputMode()); ui->cmbInputMode->setCurrentIndex(ui->cmbInputMode->findData(id)); } QString KateEditGeneralConfigTab::name() const { return i18n("General"); } //END KateEditGeneralConfigTab //BEGIN KateEditConfigTab KateEditConfigTab::KateEditConfigTab(QWidget *parent) : KateConfigPage(parent) , editConfigTab(new KateEditGeneralConfigTab(this)) , navigationConfigTab(new KateNavigationConfigTab(this)) , indentConfigTab(new KateIndentConfigTab(this)) , completionConfigTab(new KateCompletionConfigTab(this)) , spellCheckConfigTab(new KateSpellCheckConfigTab(this)) { QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); QTabWidget *tabWidget = new QTabWidget(this); // add all tabs tabWidget->insertTab(0, editConfigTab, editConfigTab->name()); tabWidget->insertTab(1, navigationConfigTab, navigationConfigTab->name()); tabWidget->insertTab(2, indentConfigTab, indentConfigTab->name()); tabWidget->insertTab(3, completionConfigTab, completionConfigTab->name()); tabWidget->insertTab(4, spellCheckConfigTab, spellCheckConfigTab->name()); connect(editConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(navigationConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(indentConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(completionConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); connect(spellCheckConfigTab, SIGNAL(changed()), this, SLOT(slotChanged())); int i = tabWidget->count(); Q_FOREACH(KateAbstractInputModeFactory *factory, KTextEditor::EditorPrivate::self()->inputModeFactories()) { KateConfigPage *tab = factory->createConfigPage(this); if (tab) { m_inputModeConfigTabs << tab; tabWidget->insertTab(i, tab, tab->name()); connect(tab, SIGNAL(changed()), this, SLOT(slotChanged())); i++; } } layout->addWidget(tabWidget); setLayout(layout); } KateEditConfigTab::~KateEditConfigTab() { qDeleteAll(m_inputModeConfigTabs); } void KateEditConfigTab::apply() { // try to update the rest of tabs editConfigTab->apply(); navigationConfigTab->apply(); indentConfigTab->apply(); completionConfigTab->apply(); spellCheckConfigTab->apply(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->apply(); } } void KateEditConfigTab::reload() { editConfigTab->reload(); navigationConfigTab->reload(); indentConfigTab->reload(); completionConfigTab->reload(); spellCheckConfigTab->reload(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reload(); } } void KateEditConfigTab::reset() { editConfigTab->reset(); navigationConfigTab->reset(); indentConfigTab->reset(); completionConfigTab->reset(); spellCheckConfigTab->reset(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->reset(); } } void KateEditConfigTab::defaults() { editConfigTab->defaults(); navigationConfigTab->defaults(); indentConfigTab->defaults(); completionConfigTab->defaults(); spellCheckConfigTab->defaults(); Q_FOREACH(KateConfigPage *tab, m_inputModeConfigTabs) { tab->defaults(); } } QString KateEditConfigTab::name() const { return i18n("Editing"); } QString KateEditConfigTab::fullName() const { return i18n("Editing Options"); } QIcon KateEditConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("accessories-text-editor")); } //END KateEditConfigTab //BEGIN KateViewDefaultsConfig KateViewDefaultsConfig::KateViewDefaultsConfig(QWidget *parent) : KateConfigPage(parent) , textareaUi(new Ui::TextareaAppearanceConfigWidget()) , bordersUi(new Ui::BordersAppearanceConfigWidget()) { QLayout *layout = new QVBoxLayout(this); QTabWidget *tabWidget = new QTabWidget(this); layout->addWidget(tabWidget); layout->setMargin(0); QWidget *textareaTab = new QWidget(tabWidget); textareaUi->setupUi(textareaTab); tabWidget->addTab(textareaTab, i18n("General")); QWidget *bordersTab = new QWidget(tabWidget); bordersUi->setupUi(bordersTab); tabWidget->addTab(bordersTab, i18n("Borders")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Off")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Follow Line Numbers")); textareaUi->cmbDynamicWordWrapIndicator->addItem(i18n("Always On")); // What's This? help is in the ui-file reload(); // // after initial reload, connect the stuff for the changed () signal // connect(textareaUi->gbWordWrap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->cmbDynamicWordWrapIndicator, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(textareaUi->sbDynamicWordWrapDepth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(textareaUi->chkShowTabs, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowSpaces, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkShowIndentationLines, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->sliSetMarkerSize, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(textareaUi->chkShowWholeBracketExpression, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkAnimateBracketMatching, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(textareaUi->chkFoldFirstLine, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkIconBorder, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMarks, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMap, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkScrollbarMiniMapAll, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); bordersUi->chkScrollbarMiniMapAll->hide(); // this is temporary until the feature is done connect(bordersUi->spBoxMiniMapWidth, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(bordersUi->chkLineNumbers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowLineModification, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingMarkers, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->chkShowFoldingPreview, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByPosition, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->rbSortBookmarksByCreation, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(bordersUi->cmbShowScrollbars, SIGNAL(activated(int)), this, SLOT(slotChanged())); } KateViewDefaultsConfig::~KateViewDefaultsConfig() { delete bordersUi; delete textareaUi; } void KateViewDefaultsConfig::apply() { // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); KateViewConfig::global()->setDynWordWrap(textareaUi->gbWordWrap->isChecked()); KateViewConfig::global()->setDynWordWrapIndicators(textareaUi->cmbDynamicWordWrapIndicator->currentIndex()); KateViewConfig::global()->setDynWordWrapAlignIndent(textareaUi->sbDynamicWordWrapDepth->value()); KateDocumentConfig::global()->setShowTabs(textareaUi->chkShowTabs->isChecked()); KateDocumentConfig::global()->setShowSpaces(textareaUi->chkShowSpaces->isChecked()); KateDocumentConfig::global()->setMarkerSize(textareaUi->sliSetMarkerSize->value()); KateViewConfig::global()->setLineNumbers(bordersUi->chkLineNumbers->isChecked()); KateViewConfig::global()->setIconBar(bordersUi->chkIconBorder->isChecked()); KateViewConfig::global()->setScrollBarMarks(bordersUi->chkScrollbarMarks->isChecked()); KateViewConfig::global()->setScrollBarPreview(bordersUi->chkScrollbarPreview->isChecked()); KateViewConfig::global()->setScrollBarMiniMap(bordersUi->chkScrollbarMiniMap->isChecked()); KateViewConfig::global()->setScrollBarMiniMapAll(bordersUi->chkScrollbarMiniMapAll->isChecked()); KateViewConfig::global()->setScrollBarMiniMapWidth(bordersUi->spBoxMiniMapWidth->value()); KateViewConfig::global()->setFoldingBar(bordersUi->chkShowFoldingMarkers->isChecked()); KateViewConfig::global()->setFoldingPreview(bordersUi->chkShowFoldingPreview->isChecked()); KateViewConfig::global()->setLineModification(bordersUi->chkShowLineModification->isChecked()); KateViewConfig::global()->setShowScrollbars(bordersUi->cmbShowScrollbars->currentIndex()); KateViewConfig::global()->setBookmarkSort(bordersUi->rbSortBookmarksByPosition->isChecked() ? 0 : 1); KateRendererConfig::global()->setShowIndentationLines(textareaUi->chkShowIndentationLines->isChecked()); KateRendererConfig::global()->setShowWholeBracketExpression(textareaUi->chkShowWholeBracketExpression->isChecked()); KateRendererConfig::global()->setAnimateBracketMatching(textareaUi->chkAnimateBracketMatching->isChecked()); KateViewConfig::global()->setFoldFirstLine(textareaUi->chkFoldFirstLine->isChecked()); KateRendererConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); } void KateViewDefaultsConfig::reload() { textareaUi->gbWordWrap->setChecked(KateViewConfig::global()->dynWordWrap()); textareaUi->cmbDynamicWordWrapIndicator->setCurrentIndex(KateViewConfig::global()->dynWordWrapIndicators()); textareaUi->sbDynamicWordWrapDepth->setValue(KateViewConfig::global()->dynWordWrapAlignIndent()); textareaUi->chkShowTabs->setChecked(KateDocumentConfig::global()->showTabs()); textareaUi->chkShowSpaces->setChecked(KateDocumentConfig::global()->showSpaces()); textareaUi->sliSetMarkerSize->setValue(KateDocumentConfig::global()->markerSize()); bordersUi->chkLineNumbers->setChecked(KateViewConfig::global()->lineNumbers()); bordersUi->chkIconBorder->setChecked(KateViewConfig::global()->iconBar()); bordersUi->chkScrollbarMarks->setChecked(KateViewConfig::global()->scrollBarMarks()); bordersUi->chkScrollbarPreview->setChecked(KateViewConfig::global()->scrollBarPreview()); bordersUi->chkScrollbarMiniMap->setChecked(KateViewConfig::global()->scrollBarMiniMap()); bordersUi->chkScrollbarMiniMapAll->setChecked(KateViewConfig::global()->scrollBarMiniMapAll()); bordersUi->spBoxMiniMapWidth->setValue(KateViewConfig::global()->scrollBarMiniMapWidth()); bordersUi->chkShowFoldingMarkers->setChecked(KateViewConfig::global()->foldingBar()); bordersUi->chkShowFoldingPreview->setChecked(KateViewConfig::global()->foldingPreview()); bordersUi->chkShowLineModification->setChecked(KateViewConfig::global()->lineModification()); bordersUi->rbSortBookmarksByPosition->setChecked(KateViewConfig::global()->bookmarkSort() == 0); bordersUi->rbSortBookmarksByCreation->setChecked(KateViewConfig::global()->bookmarkSort() == 1); bordersUi->cmbShowScrollbars->setCurrentIndex(KateViewConfig::global()->showScrollbars()); textareaUi->chkShowIndentationLines->setChecked(KateRendererConfig::global()->showIndentationLines()); textareaUi->chkShowWholeBracketExpression->setChecked(KateRendererConfig::global()->showWholeBracketExpression()); textareaUi->chkAnimateBracketMatching->setChecked(KateRendererConfig::global()->animateBracketMatching()); textareaUi->chkFoldFirstLine->setChecked(KateViewConfig::global()->foldFirstLine()); } void KateViewDefaultsConfig::reset() { ; } void KateViewDefaultsConfig::defaults() { ; } QString KateViewDefaultsConfig::name() const { return i18n("Appearance"); } QString KateViewDefaultsConfig::fullName() const { return i18n("Appearance"); } QIcon KateViewDefaultsConfig::icon() const { return QIcon::fromTheme(QStringLiteral("preferences-desktop-theme")); } //END KateViewDefaultsConfig //BEGIN KateSaveConfigTab KateSaveConfigTab::KateSaveConfigTab(QWidget *parent) : KateConfigPage(parent) , modeConfigPage(new ModeConfigPage(this)) { // FIXME: Is really needed to move all this code below to another class, // since it is another tab itself on the config dialog. This means we should // initialize, add and work with as we do with modeConfigPage (ereslibre) QVBoxLayout *layout = new QVBoxLayout; layout->setMargin(0); QTabWidget *tabWidget = new QTabWidget(this); QWidget *tmpWidget = new QWidget(tabWidget); QVBoxLayout *internalLayout = new QVBoxLayout; QWidget *newWidget = new QWidget(tabWidget); ui = new Ui::OpenSaveConfigWidget(); ui->setupUi(newWidget); QWidget *tmpWidget2 = new QWidget(tabWidget); QVBoxLayout *internalLayout2 = new QVBoxLayout; QWidget *newWidget2 = new QWidget(tabWidget); uiadv = new Ui::OpenSaveConfigAdvWidget(); uiadv->setupUi(newWidget2); // What's this help is added in ui/opensaveconfigwidget.ui reload(); // // after initial reload, connect the stuff for the changed () signal // connect(ui->cmbEncoding, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingDetection, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEncodingFallback, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbEOL, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->chkDetectEOL, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->chkEnableBOM, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(ui->lineLengthLimit, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->cbRemoveTrailingSpaces, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(ui->chkNewLineAtEof, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupLocalFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->chkBackupRemoteFiles, SIGNAL(toggled(bool)), this, SLOT(slotChanged())); connect(uiadv->edtBackupPrefix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->edtBackupSuffix, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(slotChanged())); connect(uiadv->cmbSwapFileMode, SIGNAL(currentIndexChanged(int)), this, SLOT(swapFileModeChanged(int))); connect(uiadv->kurlSwapDirectory, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(uiadv->spbSwapFileSync, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); internalLayout->addWidget(newWidget); tmpWidget->setLayout(internalLayout); internalLayout2->addWidget(newWidget2); tmpWidget2->setLayout(internalLayout2); // add all tabs tabWidget->insertTab(0, tmpWidget, i18n("General")); tabWidget->insertTab(1, tmpWidget2, i18n("Advanced")); tabWidget->insertTab(2, modeConfigPage, modeConfigPage->name()); connect(modeConfigPage, SIGNAL(changed()), this, SLOT(slotChanged())); layout->addWidget(tabWidget); setLayout(layout); } KateSaveConfigTab::~KateSaveConfigTab() { delete ui; } void KateSaveConfigTab::swapFileModeChanged(int idx) { const KateDocumentConfig::SwapFileMode mode = static_cast(idx); switch (mode) { case KateDocumentConfig::DisableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(false); uiadv->spbSwapFileSync->setEnabled(false); break; case KateDocumentConfig::EnableSwapFile: uiadv->lblSwapDirectory->setEnabled(false); uiadv->kurlSwapDirectory->setEnabled(false); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; case KateDocumentConfig::SwapFilePresetDirectory: uiadv->lblSwapDirectory->setEnabled(true); uiadv->kurlSwapDirectory->setEnabled(true); uiadv->lblSwapFileSync->setEnabled(true); uiadv->spbSwapFileSync->setEnabled(true); break; } } void KateSaveConfigTab::apply() { modeConfigPage->apply(); // nothing changed, no need to apply stuff if (!hasChanged()) { return; } m_changed = false; KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); if (uiadv->edtBackupSuffix->text().isEmpty() && uiadv->edtBackupPrefix->text().isEmpty()) { KMessageBox::information( this, i18n("You did not provide a backup suffix or prefix. Using default suffix: '~'"), i18n("No Backup Suffix or Prefix") ); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); } uint f(0); if (uiadv->chkBackupLocalFiles->isChecked()) { f |= KateDocumentConfig::LocalFiles; } if (uiadv->chkBackupRemoteFiles->isChecked()) { f |= KateDocumentConfig::RemoteFiles; } KateDocumentConfig::global()->setBackupFlags(f); KateDocumentConfig::global()->setBackupPrefix(uiadv->edtBackupPrefix->text()); KateDocumentConfig::global()->setBackupSuffix(uiadv->edtBackupSuffix->text()); KateDocumentConfig::global()->setSwapFileMode(uiadv->cmbSwapFileMode->currentIndex()); KateDocumentConfig::global()->setSwapDirectory(uiadv->kurlSwapDirectory->url().toLocalFile()); KateDocumentConfig::global()->setSwapSyncInterval(uiadv->spbSwapFileSync->value()); KateDocumentConfig::global()->setRemoveSpaces(ui->cbRemoveTrailingSpaces->currentIndex()); KateDocumentConfig::global()->setNewLineAtEof(ui->chkNewLineAtEof->isChecked()); // set both standard and fallback encoding KateDocumentConfig::global()->setEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncoding->currentText())); KateGlobalConfig::global()->setProberType((KEncodingProber::ProberType)ui->cmbEncodingDetection->currentIndex()); KateGlobalConfig::global()->setFallbackEncoding(KCharsets::charsets()->encodingForName(ui->cmbEncodingFallback->currentText())); KateDocumentConfig::global()->setEol(ui->cmbEOL->currentIndex()); KateDocumentConfig::global()->setAllowEolDetection(ui->chkDetectEOL->isChecked()); KateDocumentConfig::global()->setBom(ui->chkEnableBOM->isChecked()); KateDocumentConfig::global()->setLineLengthLimit(ui->lineLengthLimit->value()); KateDocumentConfig::global()->configEnd(); KateGlobalConfig::global()->configEnd(); } void KateSaveConfigTab::reload() { modeConfigPage->reload(); // encodings ui->cmbEncoding->clear(); ui->cmbEncodingFallback->clear(); QStringList encodings(KCharsets::charsets()->descriptiveEncodingNames()); int insert = 0; for (int i = 0; i < encodings.count(); i++) { bool found = false; QTextCodec *codecForEnc = KCharsets::charsets()->codecForName(KCharsets::charsets()->encodingForName(encodings[i]), found); if (found) { ui->cmbEncoding->addItem(encodings[i]); ui->cmbEncodingFallback->addItem(encodings[i]); if (codecForEnc == KateDocumentConfig::global()->codec()) { ui->cmbEncoding->setCurrentIndex(insert); } if (codecForEnc == KateGlobalConfig::global()->fallbackCodec()) { // adjust index for fallback config, has no default! ui->cmbEncodingFallback->setCurrentIndex(insert); } insert++; } } // encoding detection ui->cmbEncodingDetection->clear(); bool found = false; for (int i = 0; !KEncodingProber::nameForProberType((KEncodingProber::ProberType) i).isEmpty(); ++i) { ui->cmbEncodingDetection->addItem(KEncodingProber::nameForProberType((KEncodingProber::ProberType) i)); if (i == KateGlobalConfig::global()->proberType()) { ui->cmbEncodingDetection->setCurrentIndex(ui->cmbEncodingDetection->count() - 1); found = true; } } if (!found) { ui->cmbEncodingDetection->setCurrentIndex(KEncodingProber::Universal); } // eol ui->cmbEOL->setCurrentIndex(KateDocumentConfig::global()->eol()); ui->chkDetectEOL->setChecked(KateDocumentConfig::global()->allowEolDetection()); ui->chkEnableBOM->setChecked(KateDocumentConfig::global()->bom()); ui->lineLengthLimit->setValue(KateDocumentConfig::global()->lineLengthLimit()); ui->cbRemoveTrailingSpaces->setCurrentIndex(KateDocumentConfig::global()->removeSpaces()); ui->chkNewLineAtEof->setChecked(KateDocumentConfig::global()->newLineAtEof()); // other stuff uint f(KateDocumentConfig::global()->backupFlags()); uiadv->chkBackupLocalFiles->setChecked(f & KateDocumentConfig::LocalFiles); uiadv->chkBackupRemoteFiles->setChecked(f & KateDocumentConfig::RemoteFiles); uiadv->edtBackupPrefix->setText(KateDocumentConfig::global()->backupPrefix()); uiadv->edtBackupSuffix->setText(KateDocumentConfig::global()->backupSuffix()); uiadv->cmbSwapFileMode->setCurrentIndex(KateDocumentConfig::global()->swapFileModeRaw()); uiadv->kurlSwapDirectory->setUrl(QUrl::fromLocalFile(KateDocumentConfig::global()->swapDirectory())); uiadv->spbSwapFileSync->setValue(KateDocumentConfig::global()->swapSyncInterval()); swapFileModeChanged(KateDocumentConfig::global()->swapFileMode()); } void KateSaveConfigTab::reset() { modeConfigPage->reset(); } void KateSaveConfigTab::defaults() { modeConfigPage->defaults(); ui->cbRemoveTrailingSpaces->setCurrentIndex(0); uiadv->chkBackupLocalFiles->setChecked(true); uiadv->chkBackupRemoteFiles->setChecked(false); uiadv->edtBackupPrefix->setText(QString()); uiadv->edtBackupSuffix->setText(QStringLiteral("~")); uiadv->cmbSwapFileMode->setCurrentIndex(1); uiadv->kurlSwapDirectory->setDisabled(true); uiadv->lblSwapDirectory->setDisabled(true); uiadv->spbSwapFileSync->setValue(15); } QString KateSaveConfigTab::name() const { return i18n("Open/Save"); } QString KateSaveConfigTab::fullName() const { return i18n("File Opening & Saving"); } QIcon KateSaveConfigTab::icon() const { return QIcon::fromTheme(QStringLiteral("document-save")); } //END KateSaveConfigTab //BEGIN KateHlDownloadDialog KateHlDownloadDialog::KateHlDownloadDialog(QWidget *parent, const char *name, bool modal) : QDialog(parent) { setWindowTitle(i18n("Highlight Download")); setObjectName(QString::fromUtf8(name)); setModal(modal); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QLabel *label = new QLabel(i18n("Select the syntax highlighting files you want to update:"), this); mainLayout->addWidget(label); list = new QTreeWidget(this); list->setColumnCount(4); - list->setHeaderLabels(QStringList() << QString() << i18n("Name") << i18n("Installed") << i18n("Latest")); + list->setHeaderLabels({ QString(), i18n("Name"), i18n("Installed"), i18n("Latest") }); list->setSelectionMode(QAbstractItemView::MultiSelection); list->setAllColumnsShowFocus(true); list->setRootIsDecorated(false); list->setColumnWidth(0, 22); mainLayout->addWidget(list); label = new QLabel(i18n("Note: New versions are selected automatically."), this); mainLayout->addWidget(label); // buttons QDialogButtonBox *buttons = new QDialogButtonBox(this); mainLayout->addWidget(buttons); m_installButton = new QPushButton(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("&Install")); m_installButton->setDefault(true); buttons->addButton(m_installButton, QDialogButtonBox::AcceptRole); connect(m_installButton, SIGNAL(clicked()), this, SLOT(slotInstall())); QPushButton *closeButton = new QPushButton; KGuiItem::assign(closeButton, KStandardGuiItem::cancel()); buttons->addButton(closeButton, QDialogButtonBox::RejectRole); connect(closeButton, SIGNAL(clicked()), this, SLOT(reject())); transferJob = KIO::get(QUrl(QStringLiteral("%1update-%2.%3.xml").arg(HLDOWNLOADPATH).arg(KTEXTEDITOR_VERSION_MAJOR).arg(KTEXTEDITOR_VERSION_MINOR)), KIO::Reload); connect(transferJob, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(listDataReceived(KIO::Job*,QByteArray))); // void data( KIO::Job *, const QByteArray &data); resize(450, 400); } KateHlDownloadDialog::~KateHlDownloadDialog() {} /// Split typical version string (\c major.minor.patch) into /// numeric components, convert 'em to \c unsigned and form a /// single value that can be compared w/ other versions /// using relation operators. /// \note It takes into account only first 3 numbers unsigned KateHlDownloadDialog::parseVersion(const QString &version_string) { unsigned vn[3] = {0, 0, 0}; unsigned idx = 0; foreach (const QString &n, version_string.split(QLatin1Char('.'))) { vn[idx++] = n.toUInt(); if (idx == sizeof(vn)) { break; } } return (((vn[0]) << 16) | ((vn[1]) << 8) | (vn[2])); } void KateHlDownloadDialog::listDataReceived(KIO::Job *, const QByteArray &data) { if (!transferJob || transferJob->isErrorPage()) { m_installButton->setEnabled(false); if (data.size() == 0) { KMessageBox::error(this, i18n("The list of highlightings could not be found on / retrieved from the server")); } return; } listData += QLatin1String(data); qCDebug(LOG_KTE) << QStringLiteral("CurrentListData: ") << listData; qCDebug(LOG_KTE) << QStringLiteral("Data length: %1").arg(data.size()); qCDebug(LOG_KTE) << QStringLiteral("listData length: %1").arg(listData.length()); if (data.size() == 0) { if (listData.length() > 0) { QString installedVersion; KateHlManager *hlm = KateHlManager::self(); QDomDocument doc; doc.setContent(listData); QDomElement DocElem = doc.documentElement(); QDomNode n = DocElem.firstChild(); KateHighlighting *hl = nullptr; if (n.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("There is no usable childnode"); } while (!n.isNull()) { installedVersion = QStringLiteral(" --"); QDomElement e = n.toElement(); if (!e.isNull()) { qCDebug(LOG_KTE) << QStringLiteral("NAME: ") << e.tagName() << QStringLiteral(" - ") << e.attribute(QStringLiteral("name")); } n = n.nextSibling(); QString Name = e.attribute(QStringLiteral("name")); for (int i = 0; i < hlm->highlights(); i++) { hl = hlm->getHl(i); if (hl && hl->name() == Name) { installedVersion = QLatin1String(" ") + hl->version(); break; } else { hl = nullptr; } } // autoselect entry if new or updated. QTreeWidgetItem *entry = new QTreeWidgetItem(list); entry->setText(0, QString()); entry->setText(1, e.attribute(QStringLiteral("name"))); entry->setText(2, installedVersion); entry->setText(3, e.attribute(QStringLiteral("version"))); entry->setText(4, e.attribute(QStringLiteral("url"))); bool is_fresh = false; if (hl) { unsigned prev_version = parseVersion(hl->version()); unsigned next_version = parseVersion(e.attribute(QStringLiteral("version"))); is_fresh = prev_version < next_version; } else { is_fresh = true; } if (is_fresh) { entry->treeWidget()->setItemSelected(entry, true); entry->setIcon(0, QIcon::fromTheme((QStringLiteral("get-hot-new-stuff")))); } } list->resizeColumnToContents(1); list->sortItems(1, Qt::AscendingOrder); } } } void KateHlDownloadDialog::slotInstall() { const QString destdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/org.kde.syntax-highlighting/syntax/"); QDir(destdir).mkpath(QStringLiteral(".")); // make sure the dir is there foreach (QTreeWidgetItem *it, list->selectedItems()) { QUrl src(it->text(4)); QString filename = src.fileName(); // if there is no fileName construct at least something if (filename.isEmpty()) { filename = src.path().replace(QLatin1Char('/'), QLatin1Char('_')); } QUrl dest = QUrl::fromLocalFile(destdir + filename); KIO::FileCopyJob *job = KIO::file_copy(src, dest); KJobWidgets::setWindow(job, this); job->exec(); } } //END KateHlDownloadDialog //BEGIN KateGotoBar KateGotoBar::KateGotoBar(KTextEditor::View *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); gotoRange = new QSpinBox(centralWidget()); QLabel *label = new QLabel(i18n("&Go to line:"), centralWidget()); label->setBuddy(gotoRange); QToolButton *btnOK = new QToolButton(centralWidget()); btnOK->setAutoRaise(true); btnOK->setIcon(QIcon::fromTheme(QStringLiteral("go-jump"))); btnOK->setText(i18n("Go")); btnOK->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); connect(btnOK, SIGNAL(clicked()), this, SLOT(gotoLine())); topLayout->addWidget(label); topLayout->addWidget(gotoRange, 1); topLayout->setStretchFactor(gotoRange, 0); topLayout->addWidget(btnOK); topLayout->addStretch(); setFocusProxy(gotoRange); } void KateGotoBar::updateData() { gotoRange->setMaximum(m_view->document()->lines()); if (!isVisible()) { gotoRange->setValue(m_view->cursorPosition().line() + 1); gotoRange->adjustSize(); // ### does not respect the range :-( } gotoRange->setFocus(Qt::OtherFocusReason); gotoRange->selectAll(); } void KateGotoBar::keyPressEvent(QKeyEvent *event) { int key = event->key(); if (key == Qt::Key_Return || key == Qt::Key_Enter) { gotoLine(); return; } KateViewBarWidget::keyPressEvent(event); } void KateGotoBar::gotoLine() { KTextEditor::ViewPrivate *kv = qobject_cast(m_view); if (kv && kv->selection() && !kv->config()->persistentSelection()) { kv->clearSelection(); } m_view->setCursorPosition(KTextEditor::Cursor(gotoRange->value() - 1, 0)); m_view->setFocus(); emit hideMe(); } //END KateGotoBar //BEGIN KateDictionaryBar KateDictionaryBar::KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) , m_view(view) { Q_ASSERT(m_view != nullptr); // this bar widget is pointless w/o a view QHBoxLayout *topLayout = new QHBoxLayout(centralWidget()); topLayout->setMargin(0); //topLayout->setSpacing(spacingHint()); m_dictionaryComboBox = new Sonnet::DictionaryComboBox(centralWidget()); connect(m_dictionaryComboBox, SIGNAL(dictionaryChanged(QString)), this, SLOT(dictionaryChanged(QString))); connect(view->doc(), SIGNAL(defaultDictionaryChanged(KTextEditor::DocumentPrivate*)), this, SLOT(updateData())); QLabel *label = new QLabel(i18n("Dictionary:"), centralWidget()); label->setBuddy(m_dictionaryComboBox); topLayout->addWidget(label); topLayout->addWidget(m_dictionaryComboBox, 1); topLayout->setStretchFactor(m_dictionaryComboBox, 0); topLayout->addStretch(); } KateDictionaryBar::~KateDictionaryBar() { } void KateDictionaryBar::updateData() { KTextEditor::DocumentPrivate *document = m_view->doc(); QString dictionary = document->defaultDictionary(); if (dictionary.isEmpty()) { dictionary = Sonnet::Speller().defaultLanguage(); } m_dictionaryComboBox->setCurrentByDictionary(dictionary); } void KateDictionaryBar::dictionaryChanged(const QString &dictionary) { KTextEditor::Range selection = m_view->selectionRange(); if (selection.isValid() && !selection.isEmpty()) { m_view->doc()->setDictionary(dictionary, selection); } else { m_view->doc()->setDefaultDictionary(dictionary); } } //END KateGotoBar //BEGIN KateModOnHdPrompt KateModOnHdPrompt::KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason) : QObject(doc) , m_doc(doc) , m_modtype(modtype) , m_proc(nullptr) , m_diffFile(nullptr) , m_diffAction(nullptr) { m_message = new KTextEditor::Message(reason, KTextEditor::Message::Information); m_message->setPosition(KTextEditor::Message::AboveView); m_message->setWordWrap(true); // If the file isn't deleted, present a diff button const bool onDiskDeleted = modtype == KTextEditor::ModificationInterface::OnDiskDeleted; if (!onDiskDeleted) { if (!QStandardPaths::findExecutable(QStringLiteral("diff")).isEmpty()) { m_diffAction = new QAction(i18n("View &Difference"), this); m_diffAction->setToolTip(i18n("Shows a diff of the changes")); m_message->addAction(m_diffAction, false); connect(m_diffAction, SIGNAL(triggered()), this, SLOT(slotDiff())); } QAction * aReload = new QAction(i18n("&Reload"), this); aReload->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); aReload->setToolTip(i18n("Reload the file from disk. Unsaved changes will be lost.")); m_message->addAction(aReload); connect(aReload, SIGNAL(triggered()), this, SIGNAL(reloadTriggered())); } else { QAction * aSaveAs = new QAction(i18n("&Save As..."), this); aSaveAs->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); aSaveAs->setToolTip(i18n("Lets you select a location and save the file again.")); m_message->addAction(aSaveAs, false); connect(aSaveAs, SIGNAL(triggered()), this, SIGNAL(saveAsTriggered())); } QAction * aIgnore = new QAction(i18n("&Ignore"), this); aIgnore->setToolTip(i18n("Ignores the changes on disk without any action.")); aIgnore->setIcon(KStandardGuiItem::overwrite().icon()); m_message->addAction(aIgnore); connect(aIgnore, SIGNAL(triggered()), this, SIGNAL(ignoreTriggered())); m_doc->postMessage(m_message); } KateModOnHdPrompt::~KateModOnHdPrompt() { delete m_proc; m_proc = nullptr; if (m_diffFile) { m_diffFile->setAutoRemove(true); delete m_diffFile; m_diffFile = nullptr; } delete m_message; } void KateModOnHdPrompt::slotDiff() { if (m_diffFile) { return; } m_diffFile = new QTemporaryFile(); m_diffFile->open(); // Start a KProcess that creates a diff m_proc = new KProcess(this); m_proc->setOutputChannelMode(KProcess::MergedChannels); *m_proc << QStringLiteral("diff") << QLatin1String("-u") << QStringLiteral("-") << m_doc->url().toLocalFile(); connect(m_proc, SIGNAL(readyRead()), this, SLOT(slotDataAvailable())); connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotPDone())); // disable the diff button, to hinder the user to run it twice. m_diffAction->setEnabled(false); m_proc->start(); QTextStream ts(m_proc); int lastln = m_doc->lines() - 1; for (int l = 0; l < lastln; ++l) { ts << m_doc->line(l) << '\n'; } ts << m_doc->line(lastln); ts.flush(); m_proc->closeWriteChannel(); } void KateModOnHdPrompt::slotDataAvailable() { m_diffFile->write(m_proc->readAll()); } void KateModOnHdPrompt::slotPDone() { m_diffAction->setEnabled(true); const QProcess::ExitStatus es = m_proc->exitStatus(); delete m_proc; m_proc = nullptr; if (es != QProcess::NormalExit) { KMessageBox::sorry(nullptr, i18n("The diff command failed. Please make sure that " "diff(1) is installed and in your PATH."), i18n("Error Creating Diff")); delete m_diffFile; m_diffFile = nullptr; return; } if (m_diffFile->size() == 0) { KMessageBox::information(nullptr, i18n("The files are identical."), i18n("Diff Output")); delete m_diffFile; m_diffFile = nullptr; return; } m_diffFile->setAutoRemove(false); QUrl url = QUrl::fromLocalFile(m_diffFile->fileName()); delete m_diffFile; m_diffFile = nullptr; // KRun::runUrl should delete the file, once the client exits KRun::runUrl(url, QStringLiteral("text/x-patch"), nullptr, KRun::RunFlags(KRun::DeleteTemporaryFiles)); } //END KateModOnHdPrompt diff --git a/src/dialogs/katedialogs.h b/src/dialogs/katedialogs.h index d122236a..a0d3f806 100644 --- a/src/dialogs/katedialogs.h +++ b/src/dialogs/katedialogs.h @@ -1,375 +1,374 @@ /* This file is part of the KDE libraries Copyright (C) 2002, 2003 Anders Lund Copyright (C) 2003 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2006-2016 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Michel Ludwig Copyright (C) 1999 Jochen Wilhelmy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_DIALOGS_H__ #define __KATE_DIALOGS_H__ #include "katehighlight.h" #include "kateviewhelpers.h" #include "kateconfigpage.h" #include #include #include #include #include -#include #include #include #include #include class ModeConfigPage; namespace KTextEditor { class DocumentPrivate; } namespace KTextEditor { class ViewPrivate; } namespace KTextEditor { class Message; } namespace KIO { class Job; class TransferJob; } class KComboBox; class KShortcutsEditor; class QSpinBox; class KPluginSelector; class KPluginInfo; class KProcess; class QCheckBox; class QLabel; class QCheckBox; class QKeyEvent; class QTemporaryFile; class QTableWidget; namespace Ui { class TextareaAppearanceConfigWidget; class BordersAppearanceConfigWidget; class NavigationConfigWidget; class EditConfigWidget; class IndentationConfigWidget; class OpenSaveConfigWidget; class OpenSaveConfigAdvWidget; class CompletionConfigTab; class SpellCheckConfigWidget; } class KateGotoBar : public KateViewBarWidget { Q_OBJECT public: explicit KateGotoBar(KTextEditor::View *view, QWidget *parent = nullptr); void updateData(); protected Q_SLOTS: void gotoLine(); protected: void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; private: KTextEditor::View *const m_view; QSpinBox *gotoRange; }; class KateDictionaryBar : public KateViewBarWidget { Q_OBJECT public: explicit KateDictionaryBar(KTextEditor::ViewPrivate *view, QWidget *parent = nullptr); virtual ~KateDictionaryBar(); public Q_SLOTS: void updateData(); protected Q_SLOTS: void dictionaryChanged(const QString &dictionary); private: KTextEditor::ViewPrivate *m_view; Sonnet::DictionaryComboBox *m_dictionaryComboBox; }; class KateIndentConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateIndentConfigTab(QWidget *parent); ~KateIndentConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::IndentationConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void slotChanged(); void showWhatsThis(const QString &text); }; class KateCompletionConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateCompletionConfigTab(QWidget *parent); ~KateCompletionConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::CompletionConfigTab *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditGeneralConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditGeneralConfigTab(QWidget *parent); ~KateEditGeneralConfigTab(); QString name() const Q_DECL_OVERRIDE; private: Ui::EditConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} }; class KateNavigationConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateNavigationConfigTab(QWidget *parent); ~KateNavigationConfigTab(); QString name() const Q_DECL_OVERRIDE; private: Ui::NavigationConfigWidget *ui; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} }; class KateSpellCheckConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSpellCheckConfigTab(QWidget *parent); ~KateSpellCheckConfigTab(); QString name() const Q_DECL_OVERRIDE; protected: Ui::SpellCheckConfigWidget *ui; Sonnet::ConfigWidget *m_sonnetConfigWidget; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE {} void defaults() Q_DECL_OVERRIDE {} private Q_SLOTS: void showWhatsThis(const QString &text); }; class KateEditConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateEditConfigTab(QWidget *parent); ~KateEditConfigTab(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; private: KateEditGeneralConfigTab *editConfigTab; KateNavigationConfigTab *navigationConfigTab; KateIndentConfigTab *indentConfigTab; KateCompletionConfigTab *completionConfigTab; KateSpellCheckConfigTab *spellCheckConfigTab; QList m_inputModeConfigTabs; }; class KateViewDefaultsConfig : public KateConfigPage { Q_OBJECT public: explicit KateViewDefaultsConfig(QWidget *parent); ~KateViewDefaultsConfig(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; private: Ui::TextareaAppearanceConfigWidget *const textareaUi; Ui::BordersAppearanceConfigWidget *const bordersUi; }; class KateSaveConfigTab : public KateConfigPage { Q_OBJECT public: explicit KateSaveConfigTab(QWidget *parent); ~KateSaveConfigTab(); QString name() const Q_DECL_OVERRIDE; QString fullName() const Q_DECL_OVERRIDE; QIcon icon() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; void swapFileModeChanged(int); protected: //why? //KComboBox *m_encoding, *m_encodingDetection, *m_eol; QCheckBox *cbLocalFiles, *cbRemoteFiles; QCheckBox *replaceTabs, *removeSpaces, *allowEolDetection; class QSpinBox *blockCount; class QLabel *blockCountLabel; private: Ui::OpenSaveConfigWidget *ui; Ui::OpenSaveConfigAdvWidget *uiadv; ModeConfigPage *modeConfigPage; }; class KateHlDownloadDialog: public QDialog { Q_OBJECT public: KateHlDownloadDialog(QWidget *parent, const char *name, bool modal); ~KateHlDownloadDialog(); private: static unsigned parseVersion(const QString &); class QTreeWidget *list; class QString listData; KIO::TransferJob *transferJob; QPushButton *m_installButton; private Q_SLOTS: void listDataReceived(KIO::Job *, const QByteArray &data); void slotInstall(); }; /** * This dialog will prompt the user for what do with a file that is * modified on disk. * If the file wasn't deleted, it has a 'diff' button, which will create * a diff file (uing diff(1)) and launch that using KRun. */ class KateModOnHdPrompt : public QObject { Q_OBJECT public: enum Status { Reload = 1, // 0 is QDialog::Rejected Save, Overwrite, Ignore, Close }; KateModOnHdPrompt(KTextEditor::DocumentPrivate *doc, KTextEditor::ModificationInterface::ModifiedOnDiskReason modtype, const QString &reason); ~KateModOnHdPrompt(); Q_SIGNALS: void saveAsTriggered(); void ignoreTriggered(); void reloadTriggered(); private Q_SLOTS: /** * Show a diff between the document text and the disk file. */ void slotDiff(); private Q_SLOTS: void slotDataAvailable(); ///< read data from the process void slotPDone(); ///< Runs the diff file when done private: KTextEditor::DocumentPrivate *m_doc; QPointer m_message; KTextEditor::ModificationInterface::ModifiedOnDiskReason m_modtype; KProcess *m_proc; QTemporaryFile *m_diffFile; QAction *m_diffAction; }; #endif diff --git a/src/document/editorconfig.cpp b/src/document/editorconfig.cpp index 14c0bef2..d14b2c24 100644 --- a/src/document/editorconfig.cpp +++ b/src/document/editorconfig.cpp @@ -1,166 +1,166 @@ /* This file is part of the KTextEditor project. * * Copyright (C) 2017 Grzegorz Szymaszek * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "editorconfig.h" /** * @return whether a string value could be converted to a bool value as * supported. The value is put in *result. */ static bool checkBoolValue(QString val, bool *result) { val = val.trimmed().toLower(); - static const QStringList trueValues = QStringList() << QStringLiteral("1") << QStringLiteral("on") << QStringLiteral("true"); + static const QStringList trueValues { QStringLiteral("1"), QStringLiteral("on"), QStringLiteral("true") }; if (trueValues.contains(val)) { *result = true; return true; } - static const QStringList falseValues = QStringList() << QStringLiteral("0") << QStringLiteral("off") << QStringLiteral("false"); + static const QStringList falseValues { QStringLiteral("0"), QStringLiteral("off"), QStringLiteral("false") }; if (falseValues.contains(val)) { *result = false; return true; } return false; } /** * @return whether a string value could be converted to a integer value. The * value is put in *result. */ static bool checkIntValue(QString val, int *result) { bool ret(false); *result = val.toInt(&ret); return ret; } EditorConfig::EditorConfig(KTextEditor::DocumentPrivate *document) : m_document(document) , m_handle(editorconfig_handle_init()) { } EditorConfig::~EditorConfig() { editorconfig_handle_destroy(m_handle); } int EditorConfig::parse() { const int code = editorconfig_parse(m_document->url().toLocalFile().toStdString().c_str(), m_handle); if (code != 0) { if (code == EDITORCONFIG_PARSE_MEMORY_ERROR) { qCDebug(LOG_KTE) << "Failed to parse .editorconfig, memory error occurred"; } else if (code > 0) { qCDebug(LOG_KTE) << "Failed to parse .editorconfig, error in line" << code; } else { qCDebug(LOG_KTE) << "Failed to parse .editorconfig, unknown error"; } return code; } // count of found key-value pairs const unsigned int count = editorconfig_handle_get_name_value_count(m_handle); // if indent_size=tab bool setIndentSizeAsTabWidth = false; // whether corresponding fields were found in .editorconfig bool indentSizeSet = false; bool tabWidthSet = false; // the following only applies if indent_size=tab and there isn’t tab_width int tabWidth = m_document->config()->tabWidth(); for (unsigned int i = 0; i < count; ++ i) { // raw values from EditorConfig library const char *rawKey = nullptr; const char *rawValue = nullptr; // buffers for integer/boolean values int intValue; bool boolValue; // fetch next key-value pair editorconfig_handle_get_name_value(m_handle, i, &rawKey, &rawValue); // and convert to Qt strings const QLatin1String key = QLatin1String(rawKey); const QLatin1String value = QLatin1String(rawValue); if (QLatin1String("charset") == key) { m_document->setEncoding(value); } else if (QLatin1String("end_of_line") == key) { QStringList eols; // NOTE: EOLs are declared in Kate::TextBuffer::EndOfLineMode eols << QLatin1String("lf") << QLatin1String("crlf") << QLatin1String("cr"); if ((intValue = eols.indexOf(value)) != -1) { m_document->config()->setEol(intValue); m_document->config()->setAllowEolDetection(false); } else { qCDebug(LOG_KTE) << "End of line in .editorconfig other than unix/dos/mac"; } } else if (QLatin1String("indent_size") == key) { if (QLatin1String("tab") == value) { setIndentSizeAsTabWidth = true; } else if (checkIntValue(value, &intValue)) { m_document->config()->setIndentationWidth(intValue); indentSizeSet = true; } else { qCDebug(LOG_KTE) << "Indent size in .editorconfig not a number, nor tab"; } } else if (QLatin1String("indent_style") == key) { if (QLatin1String("tab") == value) { m_document->config()->setReplaceTabsDyn(false); } else if (QLatin1String("space") == value) { m_document->config()->setReplaceTabsDyn(true); } else { qCDebug(LOG_KTE) << "Indent style in .editorconfig other than tab or space"; } } else if (QLatin1String("insert_final_newline") == key && checkBoolValue(value, &boolValue)) { m_document->config()->setNewLineAtEof(boolValue); } else if (QLatin1String("max_line_length") == key && checkIntValue(value, &intValue)) { m_document->config()->setWordWrapAt(intValue); } else if (QLatin1String("tab_width") == key && checkIntValue(value, &intValue)) { m_document->config()->setTabWidth(intValue); tabWidth = intValue; tabWidthSet = true; } else if (QLatin1String("trim_trailing_whitespace") == key && checkBoolValue(value, &boolValue)) { if (boolValue) { m_document->config()->setRemoveSpaces(2); } else { m_document->config()->setRemoveSpaces(0); } } } if (setIndentSizeAsTabWidth) { m_document->config()->setIndentationWidth(tabWidth); } else if (! tabWidthSet && indentSizeSet) { m_document->config()->setTabWidth(m_document->config()->indentationWidth()); } return 0; } diff --git a/src/document/katedocument.cpp b/src/document/katedocument.cpp index 1577936c..8248cea7 100644 --- a/src/document/katedocument.cpp +++ b/src/document/katedocument.cpp @@ -1,6010 +1,6021 @@ /* This file is part of the KDE libraries Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy Copyright (C) 2006 Hamish Rodda Copyright (C) 2007 Mirko Stocker Copyright (C) 2009-2010 Michel Ludwig Copyright (C) 2013 Gerald Senarclens de Grancy Copyright (C) 2013 Andrey Matveyakin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-13020, USA. */ //BEGIN includes #include "config.h" #include "katedocument.h" #include "kateglobal.h" #include "katedialogs.h" #include "katehighlight.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katehighlighthelpers.h" #include "katerenderer.h" #include "kateregexp.h" #include "kateplaintextsearch.h" #include "kateregexpsearch.h" #include "kateconfig.h" #include "katemodemanager.h" #include "kateschema.h" #include "katebuffer.h" #include "kateundomanager.h" #include "spellcheck/prefixstore.h" #include "spellcheck/ontheflycheck.h" #include "spellcheck/spellcheck.h" #include "katescriptmanager.h" #include "kateswapfile.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "kateabstractinputmode.h" #include "katetemplatehandler.h" #if EDITORCONFIG_FOUND #include "editorconfig.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #if LIBGIT2_FOUND #include #include #include #endif //END includes #if 0 #define EDIT_DEBUG qCDebug(LOG_KTE) #else #define EDIT_DEBUG if (0) qCDebug(LOG_KTE) #endif static inline QChar matchingStartBracket(QChar c, bool withQuotes) { switch (c.toLatin1()) { case '}': return QLatin1Char('{'); case ']': return QLatin1Char('['); case ')': return QLatin1Char('('); case '\'': return withQuotes ? QLatin1Char('\'') : QChar(); case '"': return withQuotes ? QLatin1Char('"') : QChar(); } return QChar(); } static inline QChar matchingEndBracket(QChar c, bool withQuotes) { switch (c.toLatin1()) { case '{': return QLatin1Char('}'); case '[': return QLatin1Char(']'); case '(': return QLatin1Char(')'); case '\'': return withQuotes ? QLatin1Char('\'') : QChar(); case '"': return withQuotes ? QLatin1Char('"') : QChar(); } return QChar(); } static inline QChar matchingBracket(QChar c, bool withQuotes) { QChar bracket = matchingStartBracket(c, withQuotes); if (bracket.isNull()) { bracket = matchingEndBracket(c, false); } return bracket; } static inline bool isStartBracket(const QChar &c) { return ! matchingEndBracket(c, false).isNull(); } static inline bool isEndBracket(const QChar &c) { return ! matchingStartBracket(c, false).isNull(); } static inline bool isBracket(const QChar &c) { return isStartBracket(c) || isEndBracket(c); } /** * normalize given url * @param url input url * @return normalized url */ static QUrl normalizeUrl (const QUrl &url) { /** * only normalize local urls */ if (url.isEmpty() || !url.isLocalFile()) return url; /** * don't normalize if not existing! * canonicalFilePath won't work! */ const QString normalizedUrl(QFileInfo(url.toLocalFile()).canonicalFilePath()); if (normalizedUrl.isEmpty()) return url; /** * else: use canonicalFilePath to normalize */ return QUrl::fromLocalFile(normalizedUrl); } //BEGIN d'tor, c'tor // // KTextEditor::DocumentPrivate Constructor // KTextEditor::DocumentPrivate::DocumentPrivate(bool bSingleViewMode, bool bReadOnly, QWidget *parentWidget, QObject *parent) : KTextEditor::Document (this, parent), m_bSingleViewMode(bSingleViewMode), m_bReadOnly(bReadOnly), m_activeView(nullptr), editSessionNumber(0), editIsRunning(false), m_undoMergeAllEdits(false), m_undoManager(new KateUndoManager(this)), m_editableMarks(markType01), m_annotationModel(nullptr), m_buffer(new KateBuffer(this)), m_indenter(new KateAutoIndent(this)), m_hlSetByUser(false), m_bomSetByUser(false), m_indenterSetByUser(false), m_userSetEncodingForNextReload(false), m_modOnHd(false), m_modOnHdReason(OnDiskUnmodified), m_prevModOnHdReason(OnDiskUnmodified), m_docName(QStringLiteral("need init")), m_docNameNumber(0), m_fileType(QStringLiteral("Normal")), m_fileTypeSetByUser(false), m_reloading(false), m_config(new KateDocumentConfig(this)), m_fileChangedDialogsActivated(false), m_onTheFlyChecker(nullptr), m_documentState(DocumentIdle), m_readWriteStateBeforeLoading(false), m_isUntitled(true), m_openingError(false) { /** * no plugins from kparts here */ setPluginLoadingMode (DoNotLoadPlugins); /** * pass on our component data, do this after plugin loading is off */ setComponentData(KTextEditor::EditorPrivate::self()->aboutData()); /** * avoid spamming plasma and other window managers with progress dialogs * we show such stuff inline in the views! */ setProgressInfoEnabled(false); // register doc at factory KTextEditor::EditorPrivate::self()->registerDocument(this); // normal hl m_buffer->setHighlight(0); // swap file m_swapfile = (config()->swapFileMode() == KateDocumentConfig::DisableSwapFile) ? nullptr : new Kate::SwapFile(this); // important, fill in the config into the indenter we use... m_indenter->updateConfig(); // some nice signals from the buffer connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); // if the user changes the highlight with the dialog, notify the doc connect(KateHlManager::self(), SIGNAL(changed()), SLOT(internalHlChanged())); // signals for mod on hd connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(dirty(QString)), this, SLOT(slotModOnHdDirty(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(created(QString)), this, SLOT(slotModOnHdCreated(QString))); connect(KTextEditor::EditorPrivate::self()->dirWatch(), SIGNAL(deleted(QString)), this, SLOT(slotModOnHdDeleted(QString))); /** * singleshot timer to handle updates of mod on hd state delayed */ m_modOnHdTimer.setSingleShot(true); m_modOnHdTimer.setInterval(200); connect(&m_modOnHdTimer, SIGNAL(timeout()), this, SLOT(slotDelayedHandleModOnHd())); /** * load handling * this is needed to ensure we signal the user if a file ist still loading * and to disallow him to edit in that time */ connect(this, SIGNAL(started(KIO::Job*)), this, SLOT(slotStarted(KIO::Job*))); connect(this, SIGNAL(completed()), this, SLOT(slotCompleted())); connect(this, SIGNAL(canceled(QString)), this, SLOT(slotCanceled())); connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(slotUrlChanged(QUrl))); // update doc name updateDocName(); // if single view mode, like in the konqui embedding, create a default view ;) // be lazy, only create it now, if any parentWidget is given, otherwise widget() // will create it on demand... if (m_bSingleViewMode && parentWidget) { KTextEditor::View *view = (KTextEditor::View *)createView(parentWidget); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); } connect(m_undoManager, SIGNAL(undoChanged()), this, SIGNAL(undoChanged())); connect(m_undoManager, SIGNAL(undoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(undoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoStart(KTextEditor::Document*)), this, SIGNAL(editingStarted(KTextEditor::Document*))); connect(m_undoManager, SIGNAL(redoEnd(KTextEditor::Document*)), this, SIGNAL(editingFinished(KTextEditor::Document*))); connect(this, SIGNAL(sigQueryClose(bool*,bool*)), this, SLOT(slotQueryClose_save(bool*,bool*))); connect(this, &KTextEditor::DocumentPrivate::textRemoved, this, &KTextEditor::DocumentPrivate::saveEditingPositions); connect(this, &KTextEditor::DocumentPrivate::textInserted, this, &KTextEditor::DocumentPrivate::saveEditingPositions); connect(this, SIGNAL(aboutToInvalidateMovingInterfaceContent(KTextEditor::Document*)), this, SLOT(clearEditingPosStack())); onTheFlySpellCheckingEnabled(config()->onTheFlySpellCheck()); } // // KTextEditor::DocumentPrivate Destructor // KTextEditor::DocumentPrivate::~DocumentPrivate() { // delete pending mod-on-hd message, if applicable delete m_modOnHdHandler; /** * we are about to delete cursors/ranges/... */ emit aboutToDeleteMovingInterfaceContent(this); // kill it early, it has ranges! delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; clearDictionaryRanges(); // Tell the world that we're about to close (== destruct) // Apps must receive this in a direct signal-slot connection, and prevent // any further use of interfaces once they return. emit aboutToClose(this); // remove file from dirwatch deactivateDirWatch(); // thanks for offering, KPart, but we're already self-destructing setAutoDeleteWidget(false); setAutoDeletePart(false); // clean up remaining views qDeleteAll (m_views.keys()); m_views.clear(); // cu marks for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { delete i.value(); } m_marks.clear(); delete m_config; KTextEditor::EditorPrivate::self()->deregisterDocument(this); } //END void KTextEditor::DocumentPrivate::saveEditingPositions(KTextEditor::Document *, const KTextEditor::Range &range) { if (m_editingStackPosition != m_editingStack.size() - 1) { m_editingStack.resize(m_editingStackPosition); } KTextEditor::MovingInterface *moving = qobject_cast(this); const auto c = range.start(); QSharedPointer mc (moving->newMovingCursor(c)); if (!m_editingStack.isEmpty() && c.line() == m_editingStack.top()->line()) { m_editingStack.pop(); } m_editingStack.push(mc); if (m_editingStack.size() > s_editingStackSizeLimit) { m_editingStack.removeFirst(); } m_editingStackPosition = m_editingStack.size() - 1; } KTextEditor::Cursor KTextEditor::DocumentPrivate::lastEditingPosition(EditingPositionKind nextOrPrev, KTextEditor::Cursor currentCursor) { if (m_editingStack.isEmpty()) { return KTextEditor::Cursor::invalid(); } auto targetPos = m_editingStack.at(m_editingStackPosition)->toCursor(); if (targetPos == currentCursor) { if (nextOrPrev == Previous) { m_editingStackPosition--; } else { m_editingStackPosition++; } m_editingStackPosition = qBound(0, m_editingStackPosition, m_editingStack.size() - 1); } return m_editingStack.at(m_editingStackPosition)->toCursor(); } void KTextEditor::DocumentPrivate::clearEditingPosStack() { m_editingStack.clear(); m_editingStackPosition = -1; } // on-demand view creation QWidget *KTextEditor::DocumentPrivate::widget() { // no singleViewMode -> no widget()... if (!singleViewMode()) { return nullptr; } // does a widget exist already? use it! if (KTextEditor::Document::widget()) { return KTextEditor::Document::widget(); } // create and return one... KTextEditor::View *view = (KTextEditor::View *)createView(nullptr); insertChildClient(view); view->setContextMenu(view->defaultContextMenu()); setWidget(view); return view; } //BEGIN KTextEditor::Document stuff KTextEditor::View *KTextEditor::DocumentPrivate::createView(QWidget *parent, KTextEditor::MainWindow *mainWindow) { KTextEditor::ViewPrivate *newView = new KTextEditor::ViewPrivate(this, parent, mainWindow); if (m_fileChangedDialogsActivated) { connect(newView, SIGNAL(focusIn(KTextEditor::View*)), this, SLOT(slotModifiedOnDisk())); } emit viewCreated(this, newView); // post existing messages to the new view, if no specific view is given foreach (KTextEditor::Message *message, m_messageHash.keys()) { if (!message->view()) { newView->postMessage(message, m_messageHash[message]); } } return newView; } KTextEditor::Range KTextEditor::DocumentPrivate::rangeOnLine(KTextEditor::Range range, int line) const { const int col1 = toVirtualColumn(range.start()); const int col2 = toVirtualColumn(range.end()); return KTextEditor::Range(line, fromVirtualColumn(line, col1), line, fromVirtualColumn(line, col2)); } //BEGIN KTextEditor::EditInterface stuff bool KTextEditor::DocumentPrivate::isEditingTransactionRunning() const { return editSessionNumber > 0; } QString KTextEditor::DocumentPrivate::text() const { return m_buffer->text(); } QString KTextEditor::DocumentPrivate::text(const KTextEditor::Range &range, bool blockwise) const { if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return QString(); } QString s; if (range.start().line() == range.end().line()) { if (range.start().column() > range.end().column()) { return QString(); } Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return QString(); } return textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { s.append(textLine->string(range.start().column(), textLine->length() - range.start().column())); } else if (i == range.end().line()) { s.append(textLine->string(0, range.end().column())); } else { s.append(textLine->string()); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); s.append(textLine->string(subRange.start().column(), subRange.columnWidth())); } if (i < range.end().line()) { s.append(QLatin1Char('\n')); } } } return s; } QChar KTextEditor::DocumentPrivate::characterAt(const KTextEditor::Cursor &position) const { Kate::TextLine textLine = m_buffer->plainLine(position.line()); if (!textLine) { return QChar(); } return textLine->at(position.column()); } QString KTextEditor::DocumentPrivate::wordAt(const KTextEditor::Cursor &cursor) const { return text(wordRangeAt(cursor)); } KTextEditor::Range KTextEditor::DocumentPrivate::wordRangeAt(const KTextEditor::Cursor &cursor) const { // get text line const int line = cursor.line(); Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { return KTextEditor::Range::invalid(); } // make sure the cursor is const int lineLenth = textLine->length(); if (cursor.column() > lineLenth) { return KTextEditor::Range::invalid(); } int start = cursor.column(); int end = start; while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) { start--; } while (end < lineLenth && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) { end++; } return KTextEditor::Range(line, start, line, end); } bool KTextEditor::DocumentPrivate::isValidTextPosition(const KTextEditor::Cursor& cursor) const { const int ln = cursor.line(); const int col = cursor.column(); // cursor in document range? if (ln < 0 || col < 0 || ln >= lines() || col > lineLength(ln)) { return false; } const QString str = line(ln); Q_ASSERT(str.length() >= col); // cursor at end of line? const int len = lineLength(ln); if (col == 0 || col == len) { return true; } // cursor in the middle of a valid utf32-surrogate? return (! str.at(col).isLowSurrogate()) || (! str.at(col-1).isHighSurrogate()); } QStringList KTextEditor::DocumentPrivate::textLines(const KTextEditor::Range &range, bool blockwise) const { QStringList ret; if (!range.isValid()) { qCWarning(LOG_KTE) << "Text requested for invalid range" << range; return ret; } if (blockwise && (range.start().column() > range.end().column())) { return ret; } if (range.start().line() == range.end().line()) { Q_ASSERT(range.start() <= range.end()); Kate::TextLine textLine = m_buffer->plainLine(range.start().line()); if (!textLine) { return ret; } ret << textLine->string(range.start().column(), range.end().column() - range.start().column()); } else { for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i) { Kate::TextLine textLine = m_buffer->plainLine(i); if (!blockwise) { if (i == range.start().line()) { ret << textLine->string(range.start().column(), textLine->length() - range.start().column()); } else if (i == range.end().line()) { ret << textLine->string(0, range.end().column()); } else { ret << textLine->string(); } } else { KTextEditor::Range subRange = rangeOnLine(range, i); ret << textLine->string(subRange.start().column(), subRange.columnWidth()); } } } return ret; } QString KTextEditor::DocumentPrivate::line(int line) const { Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return QString(); } return l->string(); } bool KTextEditor::DocumentPrivate::setText(const QString &s) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor(), s); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::setText(const QStringList &text) { if (!isReadWrite()) { return false; } QList msave; foreach (KTextEditor::Mark *mark, m_marks) { msave.append(*mark); } editStart(); // delete the text clear(); // insert the new text insertText(KTextEditor::Cursor::start(), text); editEnd(); foreach (KTextEditor::Mark mark, msave) { setMark(mark.line, mark.type); } return true; } bool KTextEditor::DocumentPrivate::clear() { if (!isReadWrite()) { return false; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->clear(); view->tagAll(); view->update(); } clearMarks(); emit aboutToInvalidateMovingInterfaceContent(this); m_buffer->invalidateRanges(); emit aboutToRemoveText(documentRange()); return editRemoveLines(0, lastLine()); } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QString &text, bool block) { if (!isReadWrite()) { return false; } if (text.isEmpty()) { return true; } editStart(); int currentLine = position.line(); int currentLineStart = 0; const int totalLength = text.length(); int insertColumn = position.column(); // pad with empty lines, if insert position is after last line if (position.line() > lines()) { int line = lines(); while (line <= position.line()) { editInsertLine(line, QString()); line++; } } const int tabWidth = config()->tabWidth(); static const QChar newLineChar(QLatin1Char('\n')); int insertColumnExpanded = insertColumn; Kate::TextLine l = plainKateTextLine(currentLine); if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } int positionColumnExpanded = insertColumnExpanded; int pos = 0; for (; pos < totalLength; pos++) { const QChar &ch = text.at(pos); if (ch == newLineChar) { // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } if (!block) { editWrapLine(currentLine, insertColumn + pos - currentLineStart); insertColumn = 0; } currentLine++; l = plainKateTextLine(currentLine); if (block) { if (currentLine == lastLine() + 1) { editInsertLine(currentLine, QString()); } insertColumn = positionColumnExpanded; if (l) { insertColumn = l->fromVirtualColumn(insertColumn, tabWidth); } } currentLineStart = pos + 1; if (l) { insertColumnExpanded = l->toVirtualColumn(insertColumn, tabWidth); } } } // Only perform the text insert if there is text to insert if (currentLineStart < pos) { editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart)); } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertText(const KTextEditor::Cursor &position, const QStringList &textLines, bool block) { if (!isReadWrite()) { return false; } // just reuse normal function return insertText(position, textLines.join(QStringLiteral("\n")), block); } bool KTextEditor::DocumentPrivate::removeText(const KTextEditor::Range &_range, bool block) { KTextEditor::Range range = _range; if (!isReadWrite()) { return false; } // Should now be impossible to trigger with the new Range class Q_ASSERT(range.start().line() <= range.end().line()); if (range.start().line() > lastLine()) { return false; } if (!block) { emit aboutToRemoveText(range); } editStart(); if (!block) { if (range.end().line() > lastLine()) { range.setEnd(KTextEditor::Cursor(lastLine() + 1, 0)); } if (range.onSingleLine()) { editRemoveText(range.start().line(), range.start().column(), range.columnWidth()); } else { int from = range.start().line(); int to = range.end().line(); // remove last line if (to <= lastLine()) { editRemoveText(to, 0, range.end().column()); } // editRemoveLines() will be called on first line (to remove bookmark) if (range.start().column() == 0 && from > 0) { --from; } // remove middle lines editRemoveLines(from + 1, to - 1); // remove first line if not already removed by editRemoveLines() if (range.start().column() > 0 || range.start().line() == 0) { editRemoveText(from, range.start().column(), m_buffer->plainLine(from)->length() - range.start().column()); editUnWrapLine(from); } } } // if ( ! block ) else { int startLine = qMax(0, range.start().line()); int vc1 = toVirtualColumn(range.start()); int vc2 = toVirtualColumn(range.end()); for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line) { int col1 = fromVirtualColumn(line, vc1); int col2 = fromVirtualColumn(line, vc2); editRemoveText(line, qMin(col1, col2), qAbs(col2 - col1)); } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::insertLine(int l, const QString &str) { if (!isReadWrite()) { return false; } if (l < 0 || l > lines()) { return false; } return editInsertLine(l, str); } bool KTextEditor::DocumentPrivate::insertLines(int line, const QStringList &text) { if (!isReadWrite()) { return false; } if (line < 0 || line > lines()) { return false; } bool success = true; foreach (const QString &string, text) { success &= editInsertLine(line++, string); } return success; } bool KTextEditor::DocumentPrivate::removeLine(int line) { if (!isReadWrite()) { return false; } if (line < 0 || line > lastLine()) { return false; } return editRemoveLine(line); } int KTextEditor::DocumentPrivate::totalCharacters() const { int l = 0; for (int i = 0; i < m_buffer->count(); ++i) { Kate::TextLine line = m_buffer->plainLine(i); if (line) { l += line->length(); } } return l; } int KTextEditor::DocumentPrivate::lines() const { return m_buffer->count(); } int KTextEditor::DocumentPrivate::lineLength(int line) const { if (line < 0 || line > lastLine()) { return -1; } Kate::TextLine l = m_buffer->plainLine(line); if (!l) { return -1; } return l->length(); } bool KTextEditor::DocumentPrivate::isLineModified(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified(); } bool KTextEditor::DocumentPrivate::isLineSaved(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsSavedOnDisk(); } bool KTextEditor::DocumentPrivate::isLineTouched(int line) const { if (line < 0 || line >= lines()) { return false; } Kate::TextLine l = m_buffer->plainLine(line); Q_ASSERT(l); return l->markedAsModified() || l->markedAsSavedOnDisk(); } //END //BEGIN KTextEditor::EditInterface internal stuff // // Starts an edit session with (or without) undo, update of view disabled during session // bool KTextEditor::DocumentPrivate::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return false; } editIsRunning = true; m_undoManager->editStart(); foreach (KTextEditor::ViewPrivate *view, m_views) { view->editStart(); } m_buffer->editStart(); return true; } // // End edit session and update Views // bool KTextEditor::DocumentPrivate::editEnd() { if (editSessionNumber == 0) { Q_ASSERT(0); return false; } // wrap the new/changed text, if something really changed! if (m_buffer->editChanged() && (editSessionNumber == 1)) if (m_undoManager->isActive() && config()->wordWrap()) { wrapText(m_buffer->editTagStart(), m_buffer->editTagEnd()); } editSessionNumber--; if (editSessionNumber > 0) { return false; } // end buffer edit, will trigger hl update // this will cause some possible adjustment of tagline start/end m_buffer->editEnd(); m_undoManager->editEnd(); // edit end for all views !!!!!!!!! foreach (KTextEditor::ViewPrivate *view, m_views) { view->editEnd(m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom()); } if (m_buffer->editChanged()) { setModified(true); emit textChanged(this); } editIsRunning = false; return true; } void KTextEditor::DocumentPrivate::pushEditState() { editStateStack.push(editSessionNumber); } void KTextEditor::DocumentPrivate::popEditState() { if (editStateStack.isEmpty()) { return; } int count = editStateStack.pop() - editSessionNumber; while (count < 0) { ++count; editEnd(); } while (count > 0) { --count; editStart(); } } void KTextEditor::DocumentPrivate::inputMethodStart() { m_undoManager->inputMethodStart(); } void KTextEditor::DocumentPrivate::inputMethodEnd() { m_undoManager->inputMethodEnd(); } bool KTextEditor::DocumentPrivate::wrapText(int startLine, int endLine) { if (startLine < 0 || endLine < 0) { return false; } if (!isReadWrite()) { return false; } int col = config()->wordWrapAt(); if (col == 0) { return false; } editStart(); for (int line = startLine; (line <= endLine) && (line < lines()); line++) { Kate::TextLine l = kateTextLine(line); if (!l) { break; } //qCDebug(LOG_KTE) << "try wrap line: " << line; if (l->virtualLength(m_buffer->tabWidth()) > col) { Kate::TextLine nextl = kateTextLine(line + 1); //qCDebug(LOG_KTE) << "do wrap line: " << line; int eolPosition = l->length() - 1; // take tabs into account here, too int x = 0; const QString &t = l->string(); int z2 = 0; for (; z2 < l->length(); z2++) { static const QChar tabChar(QLatin1Char('\t')); if (t.at(z2) == tabChar) { x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); } else { x++; } if (x > col) { break; } } const int colInChars = qMin(z2, l->length() - 1); int searchStart = colInChars; // If where we are wrapping is an end of line and is a space we don't // want to wrap there if (searchStart == eolPosition && t.at(searchStart).isSpace()) { searchStart--; } // Scan backwards looking for a place to break the line // We are not interested in breaking at the first char // of the line (if it is a space), but we are at the second // anders: if we can't find a space, try breaking on a word // boundary, using KateHighlight::canBreakAt(). // This could be a priority (setting) in the hl/filetype/document int z = -1; int nw = -1; // alternative position, a non word character for (z = searchStart; z >= 0; z--) { if (t.at(z).isSpace()) { break; } if ((nw < 0) && highlight()->canBreakAt(t.at(z), l->attribute(z))) { nw = z; } } if (z >= 0) { // So why don't we just remove the trailing space right away? // Well, the (view's) cursor may be directly in front of that space // (user typing text before the last word on the line), and if that // happens, the cursor would be moved to the next line, which is not // what we want (bug #106261) z++; } else { // There was no space to break at so break at a nonword character if // found, or at the wrapcolumn ( that needs be configurable ) // Don't try and add any white space for the break if ((nw >= 0) && nw < colInChars) { nw++; // break on the right side of the character } z = (nw >= 0) ? nw : colInChars; } if (nextl && !nextl->isAutoWrapped()) { editWrapLine(line, z, true); editMarkLineAutoWrapped(line + 1, true); endLine++; } else { if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } bool newLineAdded = false; editWrapLine(line, z, false, &newLineAdded); editMarkLineAutoWrapped(line + 1, true); endLine++; } } } editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertText(int line, int col, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertText" << line << col << s; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (s.isEmpty()) { return true; } editStart(); QString s2 = s; int col2 = col; if (col2 > l->length()) { s2 = QString(col2 - l->length(), QLatin1Char(' ')) + s; col2 = l->length(); } m_undoManager->slotTextInserted(line, col2, s2); // insert text into line m_buffer->insertText(KTextEditor::Cursor(line, col2), s2); emit textInserted(this, KTextEditor::Range(line, col2, line, col2 + s2.length())); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveText(int line, int col, int len) { // verbose debug EDIT_DEBUG << "editRemoveText" << line << col << len; if (line < 0 || col < 0 || len < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } // nothing to do, do nothing! if (len == 0) { return true; } // wrong column if (col >= l->text().size()) { return false; } // don't try to remove what's not there len = qMin(len, l->text().size() - col); editStart(); QString oldText = l->string().mid(col, len); m_undoManager->slotTextRemoved(line, col, oldText); // remove text from line m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, col), KTextEditor::Cursor(line, col + len))); emit textRemoved(this, KTextEditor::Range(line, col, line, col + len), oldText); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editMarkLineAutoWrapped(int line, bool autowrapped) { // verbose debug EDIT_DEBUG << "editMarkLineAutoWrapped" << line << autowrapped; if (line < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); m_undoManager->slotMarkLineAutoWrapped(line, autowrapped); l->setAutoWrapped(autowrapped); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editWrapLine(int line, int col, bool newLine, bool *newLineAdded) { // verbose debug EDIT_DEBUG << "editWrapLine" << line << col << newLine; if (line < 0 || col < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); if (!l) { return false; } editStart(); Kate::TextLine nextLine = kateTextLine(line + 1); const int length = l->length(); m_undoManager->slotLineWrapped(line, col, length - col, (!nextLine || newLine)); if (!nextLine || newLine) { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { if ((col == 0) || (i.value()->line > line)) { list.append(i.value()); } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } // yes, we added a new line ! if (newLineAdded) { (*newLineAdded) = true; } } else { m_buffer->wrapLine(KTextEditor::Cursor(line, col)); m_buffer->unwrapLine(line + 2); // no, no new line added ! if (newLineAdded) { (*newLineAdded) = false; } } emit textInserted(this, KTextEditor::Range(line, col, line + 1, 0)); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editUnWrapLine(int line, bool removeLine, int length) { // verbose debug EDIT_DEBUG << "editUnWrapLine" << line << removeLine << length; if (line < 0 || length < 0) { return false; } if (!isReadWrite()) { return false; } Kate::TextLine l = kateTextLine(line); Kate::TextLine nextLine = kateTextLine(line + 1); if (!l || !nextLine) { return false; } editStart(); int col = l->length(); m_undoManager->slotLineUnWrapped(line, col, length, removeLine); if (removeLine) { m_buffer->unwrapLine(line + 1); } else { m_buffer->wrapLine(KTextEditor::Cursor(line + 1, length)); m_buffer->unwrapLine(line + 1); } QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line + 1) { list.append(i.value()); } if (i.value()->line == line + 1) { KTextEditor::Mark *mark = m_marks.take(line); if (mark) { i.value()->type |= mark->type; } } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line--; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } emit textRemoved(this, KTextEditor::Range(line, col, line + 1, 0), QStringLiteral("\n")); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editInsertLine(int line, const QString &s) { // verbose debug EDIT_DEBUG << "editInsertLine" << line << s; if (line < 0) { return false; } if (!isReadWrite()) { return false; } if (line > lines()) { return false; } editStart(); m_undoManager->slotLineInserted(line, s); // wrap line if (line > 0) { Kate::TextLine previousLine = m_buffer->line(line - 1); m_buffer->wrapLine(KTextEditor::Cursor(line - 1, previousLine->text().size())); } else { m_buffer->wrapLine(KTextEditor::Cursor(0, 0)); } // insert text m_buffer->insertText(KTextEditor::Cursor(line, 0), s); Kate::TextLine tl = m_buffer->line(line); QList list; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { if (i.value()->line >= line) { list.append(i.value()); } } for (int i = 0; i < list.size(); ++i) { m_marks.take(list.at(i)->line); } for (int i = 0; i < list.size(); ++i) { list.at(i)->line++; m_marks.insert(list.at(i)->line, list.at(i)); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeInserted(line, 0, line, tl->length()); if (line) { Kate::TextLine prevLine = plainKateTextLine(line - 1); rangeInserted.setStart(KTextEditor::Cursor(line - 1, prevLine->length())); } else { rangeInserted.setEnd(KTextEditor::Cursor(line + 1, 0)); } emit textInserted(this, rangeInserted); editEnd(); return true; } bool KTextEditor::DocumentPrivate::editRemoveLine(int line) { return editRemoveLines(line, line); } bool KTextEditor::DocumentPrivate::editRemoveLines(int from, int to) { // verbose debug EDIT_DEBUG << "editRemoveLines" << from << to; if (to < from || from < 0 || to > lastLine()) { return false; } if (!isReadWrite()) { return false; } if (lines() == 1) { return editRemoveText(0, 0, kateTextLine(0)->length()); } editStart(); QStringList oldText; /** * first remove text */ for (int line = to; line >= from; --line) { Kate::TextLine tl = m_buffer->line(line); oldText.prepend(this->line(line)); m_undoManager->slotLineRemoved(line, this->line(line)); m_buffer->removeText(KTextEditor::Range(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, tl->text().size()))); } /** * then collapse lines */ for (int line = to; line >= from; --line) { /** * unwrap all lines, prefer to unwrap line behind, skip to wrap line 0 */ if (line + 1 < m_buffer->lines()) { m_buffer->unwrapLine(line + 1); } else if (line) { m_buffer->unwrapLine(line); } } QList rmark; QList list; foreach (KTextEditor::Mark *mark, m_marks) { int line = mark->line; if (line > to) { list << line; } else if (line >= from) { rmark << line; } } foreach (int line, rmark) { delete m_marks.take(line); } foreach (int line, list) { KTextEditor::Mark *mark = m_marks.take(line); mark->line -= to - from + 1; m_marks.insert(mark->line, mark); } if (!list.isEmpty()) { emit marksChanged(this); } KTextEditor::Range rangeRemoved(from, 0, to + 1, 0); if (to == lastLine() + to - from + 1) { rangeRemoved.setEnd(KTextEditor::Cursor(to, oldText.last().length())); if (from > 0) { Kate::TextLine prevLine = plainKateTextLine(from - 1); rangeRemoved.setStart(KTextEditor::Cursor(from - 1, prevLine->length())); } } emit textRemoved(this, rangeRemoved, oldText.join(QStringLiteral("\n")) + QLatin1Char('\n')); editEnd(); return true; } //END //BEGIN KTextEditor::UndoInterface stuff uint KTextEditor::DocumentPrivate::undoCount() const { return m_undoManager->undoCount(); } uint KTextEditor::DocumentPrivate::redoCount() const { return m_undoManager->redoCount(); } void KTextEditor::DocumentPrivate::undo() { m_undoManager->undo(); } void KTextEditor::DocumentPrivate::redo() { m_undoManager->redo(); } //END //BEGIN KTextEditor::SearchInterface stuff QVector KTextEditor::DocumentPrivate::searchText( const KTextEditor::Range &range, const QString &pattern, const KTextEditor::SearchOptions options) const { const bool escapeSequences = options.testFlag(KTextEditor::EscapeSequences); const bool regexMode = options.testFlag(KTextEditor::Regex); const bool backwards = options.testFlag(KTextEditor::Backwards); const bool wholeWords = options.testFlag(KTextEditor::WholeWords); const Qt::CaseSensitivity caseSensitivity = options.testFlag(KTextEditor::CaseInsensitive) ? Qt::CaseInsensitive : Qt::CaseSensitive; if (regexMode) { // regexp search // escape sequences are supported by definition KateRegExpSearch searcher(this, caseSensitivity); return searcher.search(pattern, range, backwards); } if (escapeSequences) { // escaped search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(KateRegExpSearch::escapePlaintext(pattern), range, backwards); QVector result; result.append(match); return result; } // plaintext search KatePlainTextSearch searcher(this, caseSensitivity, wholeWords); KTextEditor::Range match = searcher.search(pattern, range, backwards); QVector result; result.append(match); return result; } //END QWidget *KTextEditor::DocumentPrivate::dialogParent() { QWidget *w = widget(); if (!w) { w = activeView(); if (!w) { w = QApplication::activeWindow(); } } return w; } //BEGIN KTextEditor::HighlightingInterface stuff bool KTextEditor::DocumentPrivate::setMode(const QString &name) { updateFileType(name); return true; } KTextEditor::DefaultStyle KTextEditor::DocumentPrivate::defaultStyleAt(const KTextEditor::Cursor &position) const { // TODO, FIXME KDE5: in surrogate, use 2 bytes before if (! isValidTextPosition(position)) { return dsNormal; } int ds = const_cast(this)-> defStyleNum(position.line(), position.column()); if (ds < 0 || ds > static_cast(dsError)) { return dsNormal; } return static_cast(ds); } QString KTextEditor::DocumentPrivate::mode() const { return m_fileType; } QStringList KTextEditor::DocumentPrivate::modes() const { QStringList m; const QList &modeList = KTextEditor::EditorPrivate::self()->modeManager()->list(); foreach (KateFileType *type, modeList) { m << type->name; } return m; } bool KTextEditor::DocumentPrivate::setHighlightingMode(const QString &name) { int mode = KateHlManager::self()->nameFind(name); if (mode == -1) { return false; } m_buffer->setHighlight(mode); return true; } QString KTextEditor::DocumentPrivate::highlightingMode() const { return highlight()->name(); } QStringList KTextEditor::DocumentPrivate::highlightingModes() const { QStringList hls; for (int i = 0; i < KateHlManager::self()->highlights(); ++i) { hls << KateHlManager::self()->hlName(i); } return hls; } QString KTextEditor::DocumentPrivate::highlightingModeSection(int index) const { return KateHlManager::self()->hlSection(index); } QString KTextEditor::DocumentPrivate::modeSection(int index) const { return KTextEditor::EditorPrivate::self()->modeManager()->list().at(index)->section; } void KTextEditor::DocumentPrivate::bufferHlChanged() { // update all views makeAttribs(false); // deactivate indenter if necessary m_indenter->checkRequiredStyle(); emit highlightingModeChanged(this); } void KTextEditor::DocumentPrivate::setDontChangeHlOnSave() { m_hlSetByUser = true; } void KTextEditor::DocumentPrivate::bomSetByUser() { m_bomSetByUser = true; } //END //BEGIN KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff void KTextEditor::DocumentPrivate::readSessionConfig(const KConfigGroup &kconfig, const QSet &flags) { if (!flags.contains(QStringLiteral("SkipEncoding"))) { // get the encoding QString tmpenc = kconfig.readEntry("Encoding"); if (!tmpenc.isEmpty() && (tmpenc != encoding())) { setEncoding(tmpenc); } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // restore the url QUrl url(kconfig.readEntry("URL")); // open the file if url valid if (!url.isEmpty() && url.isValid()) { openUrl(url); } else { completed(); //perhaps this should be emitted at the end of this function } } else { completed(); //perhaps this should be emitted at the end of this function } if (!flags.contains(QStringLiteral("SkipMode"))) { // restore the filetype if (kconfig.hasKey("Mode")) { updateFileType(kconfig.readEntry("Mode", fileType())); // restore if set by user, too! m_fileTypeSetByUser = kconfig.readEntry("Mode Set By User", false); } } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // restore the hl stuff if (kconfig.hasKey("Highlighting")) { const int mode = KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")); if (mode >= 0) { m_buffer->setHighlight(mode); // restore if set by user, too! see bug 332605, otherwise we loose the hl later again on save m_hlSetByUser = kconfig.readEntry("Highlighting Set By User", false); } } } // indent mode config()->setIndentationMode(kconfig.readEntry("Indentation Mode", config()->indentationMode())); // Restore Bookmarks const QList marks = kconfig.readEntry("Bookmarks", QList()); for (int i = 0; i < marks.count(); i++) { addMark(marks.at(i), KTextEditor::DocumentPrivate::markType01); } } void KTextEditor::DocumentPrivate::writeSessionConfig(KConfigGroup &kconfig, const QSet &flags) { if (this->url().isLocalFile()) { const QString path = this->url().toLocalFile(); if (path.startsWith(QDir::tempPath())) { return; // inside tmp resource, do not save } } if (!flags.contains(QStringLiteral("SkipUrl"))) { // save url kconfig.writeEntry("URL", this->url().toString()); } if (!flags.contains(QStringLiteral("SkipEncoding"))) { // save encoding kconfig.writeEntry("Encoding", encoding()); } if (!flags.contains(QStringLiteral("SkipMode"))) { // save file type kconfig.writeEntry("Mode", m_fileType); // save if set by user, too! kconfig.writeEntry("Mode Set By User", m_fileTypeSetByUser); } if (!flags.contains(QStringLiteral("SkipHighlighting"))) { // save hl kconfig.writeEntry("Highlighting", highlight()->name()); // save if set by user, too! see bug 332605, otherwise we loose the hl later again on save kconfig.writeEntry("Highlighting Set By User", m_hlSetByUser); } // indent mode kconfig.writeEntry("Indentation Mode", config()->indentationMode()); // Save Bookmarks QList marks; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) if (i.value()->type & KTextEditor::MarkInterface::markType01) { marks << i.value()->line; } kconfig.writeEntry("Bookmarks", marks); } //END KTextEditor::SessionConfigInterface and KTextEditor::ParameterizedSessionConfigInterface stuff uint KTextEditor::DocumentPrivate::mark(int line) { KTextEditor::Mark *m = m_marks.value(line); if (!m) { return 0; } return m->type; } void KTextEditor::DocumentPrivate::setMark(int line, uint markType) { clearMark(line); addMark(line, markType); } void KTextEditor::DocumentPrivate::clearMark(int line) { if (line < 0 || line > lastLine()) { return; } if (!m_marks.value(line)) { return; } KTextEditor::Mark *mark = m_marks.take(line); emit markChanged(this, *mark, MarkRemoved); emit marksChanged(this); delete mark; tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::addMark(int line, uint markType) { KTextEditor::Mark *mark; if (line < 0 || line > lastLine()) { return; } if (markType == 0) { return; } if ((mark = m_marks.value(line))) { // Remove bits already set markType &= ~mark->type; if (markType == 0) { return; } // Add bits mark->type |= markType; } else { mark = new KTextEditor::Mark; mark->line = line; mark->type = markType; m_marks.insert(line, mark); } // Emit with a mark having only the types added. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkAdded); emit marksChanged(this); tagLines(line, line); repaintViews(true); } void KTextEditor::DocumentPrivate::removeMark(int line, uint markType) { if (line < 0 || line > lastLine()) { return; } KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } // Remove bits not set markType &= mark->type; if (markType == 0) { return; } // Subtract bits mark->type &= ~markType; // Emit with a mark having only the types removed. KTextEditor::Mark temp; temp.line = line; temp.type = markType; emit markChanged(this, temp, MarkRemoved); if (mark->type == 0) { m_marks.remove(line); delete mark; } emit marksChanged(this); tagLines(line, line); repaintViews(true); } const QHash &KTextEditor::DocumentPrivate::marks() { return m_marks; } void KTextEditor::DocumentPrivate::requestMarkTooltip(int line, QPoint position) { KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { return; } bool handled = false; emit markToolTipRequested(this, *mark, position, handled); } bool KTextEditor::DocumentPrivate::handleMarkClick(int line) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markClicked(this, KTextEditor::Mark{line, 0}, handled); } else { emit markClicked(this, *mark, handled); } return handled; } bool KTextEditor::DocumentPrivate::handleMarkContextMenu(int line, QPoint position) { bool handled = false; KTextEditor::Mark *mark = m_marks.value(line); if (!mark) { emit markContextMenuRequested(this, KTextEditor::Mark{line, 0}, position, handled); } else { emit markContextMenuRequested(this, *mark, position, handled); } return handled; } void KTextEditor::DocumentPrivate::clearMarks() { while (!m_marks.isEmpty()) { QHash::iterator it = m_marks.begin(); KTextEditor::Mark mark = *it.value(); delete it.value(); m_marks.erase(it); emit markChanged(this, mark, MarkRemoved); tagLines(mark.line, mark.line); } m_marks.clear(); emit marksChanged(this); repaintViews(true); } void KTextEditor::DocumentPrivate::setMarkPixmap(MarkInterface::MarkTypes type, const QPixmap &pixmap) { m_markPixmaps.insert(type, pixmap); } void KTextEditor::DocumentPrivate::setMarkDescription(MarkInterface::MarkTypes type, const QString &description) { m_markDescriptions.insert(type, description); } QPixmap KTextEditor::DocumentPrivate::markPixmap(MarkInterface::MarkTypes type) const { return m_markPixmaps.value(type, QPixmap()); } QColor KTextEditor::DocumentPrivate::markColor(MarkInterface::MarkTypes type) const { uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1; if ((uint)type >= (uint)markType01 && (uint)type <= reserved) { return KateRendererConfig::global()->lineMarkerColor(type); } else { return QColor(); } } QString KTextEditor::DocumentPrivate::markDescription(MarkInterface::MarkTypes type) const { return m_markDescriptions.value(type, QString()); } void KTextEditor::DocumentPrivate::setEditableMarks(uint markMask) { m_editableMarks = markMask; } uint KTextEditor::DocumentPrivate::editableMarks() const { return m_editableMarks; } //END //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::DocumentPrivate::print() { return KatePrinter::print(this); } void KTextEditor::DocumentPrivate::printPreview() { KatePrinter::printPreview(this); } //END KTextEditor::PrintInterface stuff //BEGIN KTextEditor::DocumentInfoInterface (### unfinished) QString KTextEditor::DocumentPrivate::mimeType() { /** * collect first 4k of text * only heuristic */ QByteArray buf; for (int i = 0; (i < lines()) && (buf.size() <= 4096); ++i) { buf.append(line(i).toUtf8()); buf.append('\n'); } // use path of url, too, if set if (!url().path().isEmpty()) { return QMimeDatabase().mimeTypeForFileNameAndData(url().path(), buf).name(); } // else only use the content return QMimeDatabase().mimeTypeForData(buf).name(); } //END KTextEditor::DocumentInfoInterface //BEGIN: error void KTextEditor::DocumentPrivate::showAndSetOpeningErrorAccess() { QPointer message = new KTextEditor::Message(i18n("The file %1 could not be loaded, as it was not possible to read from it.
Check if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)), KTextEditor::Message::Error); message->setWordWrap(true); QAction *tryAgainAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("translators: you can also translate 'Try Again' with 'Reload'", "Try Again"), nullptr); connect(tryAgainAction, SIGNAL(triggered()), SLOT(documentReload()), Qt::QueuedConnection); QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); // add try again and close actions message->addAction(tryAgainAction); message->addAction(closeAction); // finally post message postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 could not be loaded, as it was not possible to read from it.\n\nCheck if you have read access to this file.", this->url().toDisplayString(QUrl::PreferLocalFile)); } //END: error void KTextEditor::DocumentPrivate::openWithLineLengthLimitOverride() { // raise line length limit to the next power of 2 const int longestLine = m_buffer->longestLineLoaded(); int newLimit = pow(2, ceil(log2(longestLine))); if (newLimit <= longestLine) { newLimit *= 2; } // do the raise config()->setLineLengthLimit(newLimit); // just reload m_buffer->clear(); openFile(); if (!m_openingError) { setReadWrite(true); m_readWriteStateBeforeLoading = true; } } int KTextEditor::DocumentPrivate::lineLengthLimit() const { return config()->lineLengthLimit(); } //BEGIN KParts::ReadWrite stuff bool KTextEditor::DocumentPrivate::openFile() { /** * we are about to invalidate all cursors/ranges/.. => m_buffer->openFile will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // no open errors until now... m_openingError = false; m_openingErrorMessage.clear (); // add new m_file to dirwatch activateDirWatch(); // remember current encoding QString currentEncoding = encoding(); // // mime type magic to get encoding right // QString mimeType = arguments().mimeType(); int pos = mimeType.indexOf(QLatin1Char(';')); if (pos != -1 && !(m_reloading && m_userSetEncodingForNextReload)) { setEncoding(mimeType.mid(pos + 1)); } // update file type, we do this here PRE-LOAD, therefore pass file name for reading from updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, localFilePath())); // read dir config (if possible and wanted) // do this PRE-LOAD to get encoding info! readDirConfig(); // perhaps we need to re-set again the user encoding if (m_reloading && m_userSetEncodingForNextReload && (currentEncoding != encoding())) { setEncoding(currentEncoding); } bool success = m_buffer->openFile(localFilePath(), (m_reloading && m_userSetEncodingForNextReload)); // // yeah, success // read variables // if (success) { readVariables(); } // // update views // foreach (KTextEditor::ViewPrivate *view, m_views) { // This is needed here because inserting the text moves the view's start position (it is a MovingCursor) view->setCursorPosition(KTextEditor::Cursor()); view->updateView(true); } // Inform that the text has changed (required as we're not inside the usual editStart/End stuff) emit textChanged(this); emit loaded(this); // // to houston, we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // // display errors // if (!success) { showAndSetOpeningErrorAccess(); } // warn: broken encoding if (m_buffer->brokenEncoding()) { // this file can't be saved again without killing it setReadWrite(false); m_readWriteStateBeforeLoading=false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened with %2 encoding but contained invalid characters.
" "It is set to read-only mode, as saving might destroy its content.
" "Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())), KTextEditor::Message::Warning); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened with %2 encoding but contained invalid characters." " It is set to read-only mode, as saving might destroy its content." " Either reopen the file with the correct encoding chosen or enable the read-write mode again in the tools menu to be able to edit it.", this->url().toDisplayString(QUrl::PreferLocalFile), QString::fromLatin1(m_buffer->textCodec()->name())); } // warn: too long lines if (m_buffer->tooLongLinesWrapped()) { // this file can't be saved again without modifications setReadWrite(false); m_readWriteStateBeforeLoading = false; QPointer message = new KTextEditor::Message(i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(), m_buffer->longestLineLoaded()), KTextEditor::Message::Warning); QAction *increaseAndReload = new QAction(i18n("Temporarily raise limit and reload file"), message); connect(increaseAndReload, SIGNAL(triggered()), this, SLOT(openWithLineLengthLimitOverride())); message->addAction(increaseAndReload, true); message->addAction(new QAction(i18n("Close"), message), true); message->setWordWrap(true); postMessage(message); // remember error m_openingError = true; m_openingErrorMessage = i18n("The file %1 was opened and contained lines longer than the configured Line Length Limit (%2 characters).
" "The longest of those lines was %3 characters long
" "Those lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().toDisplayString(QUrl::PreferLocalFile), config()->lineLengthLimit(),m_buffer->longestLineLoaded()); } // // return the success // return success; } bool KTextEditor::DocumentPrivate::saveFile() { // delete pending mod-on-hd message if applicable. delete m_modOnHdHandler; // some warnings, if file was changed by the outside! if (!url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { QString str = reasonedMOHString() + QLatin1String("\n\n"); if (!isModified()) { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this unmodified file? You could overwrite changed data in the file on disk."), i18n("Trying to Save Unmodified File"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } else { if (KMessageBox::warningContinueCancel(dialogParent(), str + i18n("Do you really want to save this file? Both your open file and the file on disk were changed. There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue) { return false; } } } } // // can we encode it if we want to save it ? // if (!m_buffer->canEncode() && (KMessageBox::warningContinueCancel(dialogParent(), i18n("The selected encoding cannot encode every Unicode character in this document. Do you really want to save it? There could be some data lost."), i18n("Possible Data Loss"), KGuiItem(i18n("Save Nevertheless"))) != KMessageBox::Continue)) { return false; } /** * create a backup file or abort if that fails! * if no backup file wanted, this routine will just return true */ if (!createBackupFile()) return false; // update file type, pass no file path, read file type content from this document QString oldPath = m_dirWatchFile; // only update file type if path has changed so that variables are not overridden on normal save if (oldPath != localFilePath()) { updateFileType(KTextEditor::EditorPrivate::self()->modeManager()->fileType(this, QString())); if (url().isLocalFile()) { // if file is local then read dir config for new path readDirConfig(); } } // read our vars readVariables(); // remove file from dirwatch deactivateDirWatch(); // remove all trailing spaces in the document (as edit actions) // NOTE: we need this as edit actions, since otherwise the edit actions // in the swap file recovery may happen at invalid cursor positions removeTrailingSpaces(); // // try to save // if (!m_buffer->saveFile(localFilePath())) { // add m_file again to dirwatch activateDirWatch(oldPath); KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // update the checksum createDigest(); // add m_file again to dirwatch activateDirWatch(); // // we are not modified // if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // (dominik) mark last undo group as not mergeable, otherwise the next // edit action might be merged and undo will never stop at the saved state m_undoManager->undoSafePoint(); m_undoManager->updateLineModifications(); // // return success // return true; } bool KTextEditor::DocumentPrivate::createBackupFile() { /** * backup for local or remote files wanted? */ const bool backupLocalFiles = (config()->backupFlags() & KateDocumentConfig::LocalFiles); const bool backupRemoteFiles = (config()->backupFlags() & KateDocumentConfig::RemoteFiles); /** * early out, before mount check: backup wanted at all? * => if not, all fine, just return */ if (!backupLocalFiles && !backupRemoteFiles) { return true; } /** * decide if we need backup based on locality * skip that, if we always want backups, as currentMountPoints is not that fast */ QUrl u(url()); bool needBackup = backupLocalFiles && backupRemoteFiles; if (!needBackup) { bool slowOrRemoteFile = !u.isLocalFile(); if (!slowOrRemoteFile) { // could be a mounted remote filesystem (e.g. nfs, sshfs, cifs) // we have the early out above to skip this, if we want no backup, which is the default KMountPoint::Ptr mountPoint = KMountPoint::currentMountPoints().findByDevice(u.toLocalFile()); slowOrRemoteFile = (mountPoint && mountPoint->probablySlow()); } needBackup = (!slowOrRemoteFile && backupLocalFiles) || (slowOrRemoteFile && backupRemoteFiles); } /** * no backup needed? be done */ if (!needBackup) { return true; } /** * else: try to backup */ if (config()->backupPrefix().contains(QDir::separator())) { /** * replace complete path, as prefix is a path! */ u.setPath(config()->backupPrefix() + u.fileName() + config()->backupSuffix()); } else { /** * replace filename in url */ const QString fileName = u.fileName(); u = u.adjusted(QUrl::RemoveFilename); u.setPath(u.path() + config()->backupPrefix() + fileName + config()->backupSuffix()); } qCDebug(LOG_KTE) << "backup src file name: " << url(); qCDebug(LOG_KTE) << "backup dst file name: " << u; // handle the backup... bool backupSuccess = false; // local file mode, no kio if (u.isLocalFile()) { if (QFile::exists(url().toLocalFile())) { // first: check if backupFile is already there, if true, unlink it QFile backupFile(u.toLocalFile()); if (backupFile.exists()) { backupFile.remove(); } backupSuccess = QFile::copy(url().toLocalFile(), u.toLocalFile()); } else { backupSuccess = true; } } else { // remote file mode, kio // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (statJob->exec()) { // do a evil copy which will overwrite target if possible KFileItem item(statJob->statResult(), url()); KIO::FileCopyJob *job = KIO::file_copy(url(), u, item.permissions(), KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); backupSuccess = job->exec(); } else { backupSuccess = true; } } // backup has failed, ask user how to proceed if (!backupSuccess && (KMessageBox::warningContinueCancel(dialogParent() , i18n("For file %1 no backup copy could be created before saving." " If an error occurs while saving, you might lose the data of this file." " A reason could be that the media you write to is full or the directory of the file is read-only for you.", url().toDisplayString(QUrl::PreferLocalFile)) , i18n("Failed to create backup copy.") , KGuiItem(i18n("Try to Save Nevertheless")) , KStandardGuiItem::cancel(), QStringLiteral("Backup Failed Warning")) != KMessageBox::Continue)) { return false; } return true; } void KTextEditor::DocumentPrivate::readDirConfig() { if (!url().isLocalFile()) { return; } /** * first search .kateconfig upwards * with recursion guard */ QSet seenDirectories; QDir dir (QFileInfo(localFilePath()).absolutePath()); while (!seenDirectories.contains (dir.absolutePath ())) { /** * fill recursion guard */ seenDirectories.insert (dir.absolutePath ()); // try to open config file in this dir QFile f(dir.absolutePath () + QLatin1String("/.kateconfig")); if (f.open(QIODevice::ReadOnly)) { QTextStream stream(&f); uint linesRead = 0; QString line = stream.readLine(); while ((linesRead < 32) && !line.isNull()) { readVariableLine(line); line = stream.readLine(); linesRead++; } return; } /** * else: cd up, if possible or abort */ if (!dir.cdUp()) { break; } } #if EDITORCONFIG_FOUND // if there wasn’t any .kateconfig file and KTextEditor was compiled with // EditorConfig support, try to load document config from a .editorconfig // file, if such is provided EditorConfig editorConfig(this); editorConfig.parse(); #endif } void KTextEditor::DocumentPrivate::activateDirWatch(const QString &useFileName) { QString fileToUse = useFileName; if (fileToUse.isEmpty()) { fileToUse = localFilePath(); } QFileInfo fileInfo = QFileInfo(fileToUse); if (fileInfo.isSymLink()) { // Monitor the actual data and not the symlink fileToUse = fileInfo.canonicalFilePath(); } // same file as we are monitoring, return if (fileToUse == m_dirWatchFile) { return; } // remove the old watched file deactivateDirWatch(); // add new file if needed if (url().isLocalFile() && !fileToUse.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->addFile(fileToUse); m_dirWatchFile = fileToUse; } } void KTextEditor::DocumentPrivate::deactivateDirWatch() { if (!m_dirWatchFile.isEmpty()) { KTextEditor::EditorPrivate::self()->dirWatch()->removeFile(m_dirWatchFile); } m_dirWatchFile.clear(); } bool KTextEditor::DocumentPrivate::openUrl(const QUrl &url) { if (!m_reloading) { // Reset filetype when opening url m_fileTypeSetByUser = false; } bool res = KTextEditor::Document::openUrl(normalizeUrl(url)); updateDocName(); return res; } bool KTextEditor::DocumentPrivate::closeUrl() { // // file mod on hd // if (!m_reloading && !url().isEmpty()) { if (m_fileChangedDialogsActivated && m_modOnHd) { // make sure to not forget pending mod-on-hd handler delete m_modOnHdHandler; QWidget *parentWidget(dialogParent()); if (!(KMessageBox::warningContinueCancel( parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("Do you really want to continue to close this file? Data loss may occur."), i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(), QStringLiteral("kate_close_modonhd_%1").arg(m_modOnHdReason)) == KMessageBox::Continue)) { /** * reset reloading */ m_reloading = false; return false; } } } // // first call the normal kparts implementation // if (!KParts::ReadWritePart::closeUrl()) { /** * reset reloading */ m_reloading = false; return false; } // Tell the world that we're about to go ahead with the close if (!m_reloading) { emit aboutToClose(this); } /** * delete all KTE::Messages */ if (!m_messageHash.isEmpty()) { QList keys = m_messageHash.keys(); foreach (KTextEditor::Message *message, keys) { delete message; } } /** * we are about to invalidate all cursors/ranges/.. => m_buffer->clear will do so */ emit aboutToInvalidateMovingInterfaceContent(this); // remove file from dirwatch deactivateDirWatch(); // // empty url + fileName // setUrl(QUrl()); setLocalFilePath(QString()); // we are not modified if (m_modOnHd) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // remove all marks clearMarks(); // clear the buffer m_buffer->clear(); // clear undo/redo history m_undoManager->clearUndo(); m_undoManager->clearRedo(); // no, we are no longer modified setModified(false); // we have no longer any hl m_buffer->setHighlight(0); // update all our views foreach (KTextEditor::ViewPrivate *view, m_views) { view->clearSelection(); // fix bug #118588 view->clear(); } // purge swap file if (m_swapfile) { m_swapfile->fileClosed(); } // success return true; } bool KTextEditor::DocumentPrivate::isDataRecoveryAvailable() const { return m_swapfile && m_swapfile->shouldRecover(); } void KTextEditor::DocumentPrivate::recoverData() { if (isDataRecoveryAvailable()) { m_swapfile->recover(); } } void KTextEditor::DocumentPrivate::discardDataRecovery() { if (isDataRecoveryAvailable()) { m_swapfile->discard(); } } void KTextEditor::DocumentPrivate::setReadWrite(bool rw) { if (isReadWrite() == rw) { return; } KParts::ReadWritePart::setReadWrite(rw); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); view->slotReadWriteChanged(); } emit readWriteChanged(this); } void KTextEditor::DocumentPrivate::setModified(bool m) { if (isModified() != m) { KParts::ReadWritePart::setModified(m); foreach (KTextEditor::ViewPrivate *view, m_views) { view->slotUpdateUndo(); } emit modifiedChanged(this); } m_undoManager->setModified(m); } //END //BEGIN Kate specific stuff ;) void KTextEditor::DocumentPrivate::makeAttribs(bool needInvalidate) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->renderer()->updateAttributes(); } if (needInvalidate) { m_buffer->invalidateHighlighting(); } foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagAll(); view->updateView(true); } } // the attributes of a hl have changed, update void KTextEditor::DocumentPrivate::internalHlChanged() { makeAttribs(); } void KTextEditor::DocumentPrivate::addView(KTextEditor::View *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view, static_cast(view)); // apply the view & renderer vars from the file type if (!m_fileType.isEmpty()) { readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(m_fileType).varLine, true); } // apply the view & renderer vars from the file readVariables(true); setActiveView(view); } void KTextEditor::DocumentPrivate::removeView(KTextEditor::View *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); if (activeView() == view) { setActiveView(nullptr); } } void KTextEditor::DocumentPrivate::setActiveView(KTextEditor::View *view) { if (m_activeView == view) { return; } m_activeView = static_cast(view); } bool KTextEditor::DocumentPrivate::ownedView(KTextEditor::ViewPrivate *view) { // do we own the given view? return (m_views.contains(view)); } int KTextEditor::DocumentPrivate::toVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->toVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::toVirtualColumn(const KTextEditor::Cursor &cursor) const { return toVirtualColumn(cursor.line(), cursor.column()); } int KTextEditor::DocumentPrivate::fromVirtualColumn(int line, int column) const { Kate::TextLine textLine = m_buffer->plainLine(line); if (textLine) { return textLine->fromVirtualColumn(column, config()->tabWidth()); } else { return 0; } } int KTextEditor::DocumentPrivate::fromVirtualColumn(const KTextEditor::Cursor &cursor) const { return fromVirtualColumn(cursor.line(), cursor.column()); } bool KTextEditor::DocumentPrivate::typeChars(KTextEditor::ViewPrivate *view, const QString &realChars) { /** * filter out non-printable chars (convert to utf-32 to support surrogate pairs) */ const auto realUcs4Chars = realChars.toUcs4(); QVector ucs4Chars; Q_FOREACH (auto c, realUcs4Chars) if (QChar::isPrint(c) || c == QChar::fromLatin1('\t') || c == QChar::fromLatin1('\n') || c == QChar::fromLatin1('\r')) { ucs4Chars.append(c); } QString chars = QString::fromUcs4(ucs4Chars.data(), ucs4Chars.size()); /** * no printable chars => nothing to insert! */ if (chars.isEmpty()) { return false; } /** * always unfreeze on typing */ view->cursors()->setSecondaryFrozen(false); view->cursors()->removeDuplicateCursors(); /** * auto bracket handling for newly inserted text * remember if we should auto add some */ QChar closingBracket; if (view->config()->autoBrackets() && chars.size() == 1 && !view->cursors()->hasSecondaryCursors()) { /** * we inserted a bracket? * => remember the matching closing one */ closingBracket = matchingEndBracket(chars[0], true); /** * closing bracket for the autobracket we inserted earlier? */ if ( m_currentAutobraceClosingChar == chars[0] && m_currentAutobraceRange ) { // do nothing m_currentAutobraceRange.reset(nullptr); view->cursorRight(); return true; } } /** * selection around => special handling if we want to add auto brackets */ if (view->selection() && !closingBracket.isNull()) { /** * add bracket at start + end of the selection */ KTextEditor::Cursor oldCur = view->cursorPosition(); insertText(view->selectionRange().start(), chars); view->slotTextInserted(view, oldCur, chars); view->setCursorPosition(view->selectionRange().end()); oldCur = view->cursorPosition(); insertText(view->selectionRange().end(), QString(closingBracket)); view->slotTextInserted(view, oldCur, QString(closingBracket)); /** * expand selection */ view->setSelection(KTextEditor::Range(view->selectionRange().start() + Cursor{0, 1}, view->cursorPosition() - Cursor{0, 1})); view->setCursorPosition(view->selectionRange().start()); } /** * else normal handling */ else { editStart(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } auto oldCursors = view->allCursors(); if (view->currentInputMode()->overwrite()) { Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto line = cursor.line(); auto virtualColumn = toVirtualColumn(view->cursorPosition()); Kate::TextLine textLine = m_buffer->plainLine(line); Q_ASSERT(textLine); const int column = fromVirtualColumn(line, virtualColumn); KTextEditor::Range r = KTextEditor::Range(KTextEditor::Cursor(line, column), qMin(chars.length(), textLine->length() - column)); // replace mode needs to know what was removed so it can be restored with backspace #warning fix this: backspace in overwrite mode restores characters // if (oldCur.column() < lineLength(line)) { // QChar removed = characterAt(KTextEditor::Cursor(line, column)); // view->currentInputMode()->overwrittenChar(removed); // } removeText(r); } } Q_FOREACH ( const auto& cursor, view->allCursors() ) { auto adjusted = eventuallyReplaceTabs(cursor, chars); insertText(cursor, adjusted); } /** * auto bracket handling for newly inserted text * we inserted a bracket? * => add the matching closing one to the view + input chars * try to preserve the cursor position */ bool skipAutobrace = closingBracket == QLatin1Char('\''); if ( highlight() && skipAutobrace ) { auto context = highlight()->contextForLocation(this, view->cursorPosition() - Cursor{0, 1}); // skip adding ' in spellchecked areas, because those are text skipAutobrace = !context || highlight()->attributeRequiresSpellchecking(context->attr); } if (!closingBracket.isNull() && !skipAutobrace ) { // add bracket to the view Q_FOREACH ( const auto& cursorPos, view->allCursors() ) { const auto nextChar = view->document()->text({cursorPos, cursorPos + Cursor{0, 1}}).trimmed(); if ( nextChar.isEmpty() || ! nextChar.at(0).isLetterOrNumber() ) { insertText(view->cursorPosition(), QString(closingBracket)); const auto insertedAt(view->cursorPosition()); view->setCursorPosition(cursorPos); m_currentAutobraceRange.reset(newMovingRange({cursorPos - Cursor{0, 1}, insertedAt}, KTextEditor::MovingRange::DoNotExpand)); connect(view, &View::cursorPositionChanged, this, &DocumentPrivate::checkCursorForAutobrace, Qt::UniqueConnection); // add bracket to chars inserted! needed for correct signals + indent chars.append(closingBracket); } } m_currentAutobraceClosingChar = closingBracket; } // end edit session here, to have updated HL in userTypedChar! editEnd(); if ( ! view->cursors()->hasSecondaryCursors() ) { // trigger indentation KTextEditor::Cursor b(view->cursorPosition()); m_indenter->userTypedChar(view, b, chars.isEmpty() ? QChar() : chars.at(chars.length() - 1)); } /** * inform the view about the original inserted chars */ Q_FOREACH ( const auto& oldCur, oldCursors ) { view->slotTextInserted(view, oldCur, chars); } } /** * be done */ return true; } void KTextEditor::DocumentPrivate::checkCursorForAutobrace(KTextEditor::View*, const KTextEditor::Cursor& newPos) { if ( m_currentAutobraceRange && ! m_currentAutobraceRange->toRange().contains(newPos) ) { m_currentAutobraceRange.clear(); } } void KTextEditor::DocumentPrivate::newLine(KTextEditor::ViewPrivate *v) { editStart(); if (!v->config()->persistentSelection() && v->selection()) { v->removeSelectedText(); v->clearSelection(); } // query cursor position Q_FOREACH ( auto c, v->allCursors() ) { if (c.line() > (int)lastLine()) { c.setLine(lastLine()); } if (c.line() < 0) { c.setLine(0); } uint ln = c.line(); Kate::TextLine textLine = plainKateTextLine(ln); if (c.column() > (int)textLine->length()) { c.setColumn(textLine->length()); } // first: wrap line editWrapLine(c.line(), c.column()); } // end edit session here, to have updated HL in userTypedChar! editEnd(); // second: indent the new line, if needed... m_indenter->userTypedChar(v, v->cursorPosition(), QLatin1Char('\n')); } void KTextEditor::DocumentPrivate::transpose(const KTextEditor::Cursor &cursor) { Kate::TextLine textLine = m_buffer->plainLine(cursor.line()); if (!textLine || (textLine->length() < 2)) { return; } uint col = cursor.column(); if (col > 0) { col--; } if ((textLine->length() - col) < 2) { return; } uint line = cursor.line(); QString s; //clever swap code if first character on the line swap right&left //otherwise left & right s.append(textLine->at(col + 1)); s.append(textLine->at(col)); //do the swap // do it right, never ever manipulate a textline editStart(); editRemoveText(line, col, 2); editInsertText(line, col, s); editEnd(); } void KTextEditor::DocumentPrivate::backspace(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { /** * always unfreeze secondary cursors on typing */ view->cursors()->setSecondaryFrozen(false); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); return; } auto col = qMax(c.column(), 0); auto line = qMax(c.line(), 0); if ((col == 0) && (line == 0)) { return; } if (col > 0) { if (!(config()->backspaceIndents())) { // ordinary backspace KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); KTextEditor::Cursor endCursor(line, col); removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode #warning how do we solve this for block mode? // view->setCursorPosition(beginCursor); } else { // backspace indents: erase to next indent position Kate::TextLine textLine = m_buffer->plainLine(line); // don't forget this check!!!! really!!!! if (!textLine) { return; } if ( view->blockSelection() && col > textLine->length() ) { view->clearSelection(false); insertText(c, QStringLiteral(" "), true); removeText({c-KTextEditor::Cursor{0, 1}, c}, true); } int colX = textLine->toVirtualColumn(col, config()->tabWidth()); int pos = textLine->firstChar(); if (pos > 0) { pos = textLine->toVirtualColumn(pos, config()->tabWidth()); } if (pos < 0 || pos >= (int)colX) { // only spaces on left side of cursor indent(KTextEditor::Range(line, 0, line, 0), -1); } else { KTextEditor::Cursor beginCursor(line, view->textLayout(c)->previousCursorPosition(c.column())); KTextEditor::Cursor endCursor(line, col); removeText(KTextEditor::Range(beginCursor, endCursor)); // in most cases cursor is moved by removeText, but we should do it manually // for past-end-of-line cursors in block mode // view->setCursorPosition(beginCursor); } } } else { // col == 0: wrap to previous line if (line >= 1) { Kate::TextLine textLine = m_buffer->plainLine(line - 1); // don't forget this check!!!! really!!!! if (!textLine) { return; } if (config()->wordWrap() && textLine->endsWith(QLatin1String(" "))) { // gg: in hard wordwrap mode, backspace must also eat the trailing space removeText(KTextEditor::Range(line - 1, textLine->length() - 1, line, 0)); } else { removeText(KTextEditor::Range(line - 1, textLine->length(), line, 0)); } } } if ( m_currentAutobraceRange ) { const auto r = m_currentAutobraceRange->toRange(); if ( r.columnWidth() == 1 && view->cursorPosition() == r.start() ) { // start parenthesis removed and range length is 1, remove end as well del(view, view->cursorPosition()); m_currentAutobraceRange.clear(); } } } void KTextEditor::DocumentPrivate::del(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &c) { if (!view->config()->persistentSelection() && view->selection()) { if (view->blockSelection() && view->selection() && toVirtualColumn(view->selectionRange().start()) == toVirtualColumn(view->selectionRange().end())) { // Remove one character after selection line KTextEditor::Range range = view->selectionRange(); range.setEnd(KTextEditor::Cursor(range.end().line(), range.end().column() + 1)); view->setSelection(range); } view->removeSelectedText(); return; } if (c.column() < (int) m_buffer->plainLine(c.line())->length()) { KTextEditor::Cursor endCursor(c.line(), view->textLayout(c)->nextCursorPosition(c.column())); removeText(KTextEditor::Range(c, endCursor)); } else if (c.line() < lastLine()) { removeText(KTextEditor::Range(c.line(), c.column(), c.line() + 1, 0)); } } void KTextEditor::DocumentPrivate::paste(KTextEditor::ViewPrivate *view, const QString &text) { Q_ASSERT(false); // TODO this function should go away static const QChar newLineChar(QLatin1Char('\n')); QString s = text; if (s.isEmpty()) { return; } int lines = s.count(newLineChar); auto paste_text_at = [this, view, s, lines](const KTextEditor::Cursor& pos) { editStart(); #warning TODO handle overwrite mode /** if (config()->ovr()) { QStringList pasteLines = s.split(newLineChar); if (!view->blockSelection()) { int endColumn = (pasteLines.count() == 1 ? pos.column() : 0) + pasteLines.last().length(); removeText(KTextEditor::Range(pos, pos.line() + pasteLines.count() - 1, endColumn)); } else { int maxi = qMin(pos.line() + pasteLines.count(), this->lines()); for (int i = pos.line(); i < maxi; ++i) { int pasteLength = pasteLines.at(i - pos.line()).length(); removeText(KTextEditor::Range(i, pos.column(), i, qMin(pasteLength + pos.column(), lineLength(i)))); } } } **/ insertText(pos, s, view->blockSelection()); editEnd(); // move cursor right for block select, as the user is moved right internal // even in that case, but user expects other behavior in block selection // mode ! // just let cursor stay, that was it before I changed to moving ranges! if (view->blockSelection()) { view->setCursorPositionInternal(pos); } if (config()->indentPastedText()) { KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0), KTextEditor::Cursor(pos.line() + lines, 0)); m_indenter->indent(view, range); } if (!view->blockSelection()) { emit charactersSemiInteractivelyInserted(pos, s); } }; m_undoManager->undoSafePoint(); editStart(); if (!view->config()->persistentSelection() && view->selection()) { auto pos = view->selectionRange().start(); if (view->blockSelection()) { pos = rangeOnLine(view->selectionRange(), pos.line()).start(); if (lines == 0) { s += newLineChar; s = s.repeated(view->selectionRange().numberOfLines() + 1); s.chop(1); } } view->removeSelectedText(); } editEnd(); Q_FOREACH ( const auto& cursor, view->allCursors() ) { paste_text_at(cursor); } m_undoManager->undoSafePoint(); } void KTextEditor::DocumentPrivate::indent(KTextEditor::Range range, int change) { if (!isReadWrite()) { return; } editStart(); m_indenter->changeIndent(range, change); editEnd(); } void KTextEditor::DocumentPrivate::align(KTextEditor::ViewPrivate *view, const KTextEditor::Range &range) { m_indenter->indent(view, range); } void KTextEditor::DocumentPrivate::insertTab(KTextEditor::ViewPrivate *view, const KTextEditor::Cursor &) { if (!isReadWrite()) { return; } editStart(); Q_FOREACH ( auto c, view->allCursors() ) { int lineLen = line(c.line()).length(); if (!view->config()->persistentSelection() && view->selection()) { view->removeSelectedText(); } else if (view->currentInputMode()->overwrite() && c.column() < lineLen) { KTextEditor::Range r = KTextEditor::Range(c, 1); // replace mode needs to know what was removed so it can be restored with backspace QChar removed = line(c.line()).at(r.start().column()); view->currentInputMode()->overwrittenChar(removed); removeText(r); } editInsertText(c.line(), c.column(), QStringLiteral("\t")); } editEnd(); } /* Remove a given string at the beginning of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromBeginning(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->startsWith(str); if (!there) { cursor.setColumn(textline->firstChar()); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Remove a given string at the end of the current line. */ bool KTextEditor::DocumentPrivate::removeStringFromEnd(int line, const QString &str) { Kate::TextLine textline = m_buffer->plainLine(line); KTextEditor::Cursor cursor(line, 0); bool there = textline->endsWith(str); if (there) { cursor.setColumn(textline->length() - str.length()); } else { cursor.setColumn(textline->lastChar() - str.length() + 1); there = textline->matchesAt(cursor.column(), str); } if (there) { // Remove some chars removeText(KTextEditor::Range(cursor, str.length())); } return there; } /* Replace tabs by spaces in the given string, if enabled. */ QString KTextEditor::DocumentPrivate::eventuallyReplaceTabs(const KTextEditor::Cursor &cursorPos, const QString &str) const { const bool replacetabs = config()->replaceTabsDyn(); if ( ! replacetabs ) { return str; } const int indentWidth = config()->indentationWidth(); static const QLatin1Char tabChar('\t'); int column = cursorPos.column(); // The result will always be at least as long as the input QString result; result.reserve(str.size()); Q_FOREACH (const QChar ch, str) { if (ch == tabChar) { // Insert only enough spaces to align to the next indentWidth column // This fixes bug #340212 int spacesToInsert = indentWidth - (column % indentWidth); result += QStringLiteral(" ").repeated(spacesToInsert); column += spacesToInsert; } else { // Just keep all other typed characters as-is result += ch; ++column; } } return result; } /* Add to the current line a comment line mark at the beginning. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSingleLine(int line, int attrib) { QString commentLineMark = highlight()->getCommentSingleLineStart(attrib); int pos = -1; if (highlight()->getCommentSingleLinePosition(attrib) == KateHighlighting::CSLPosColumn0) { pos = 0; commentLineMark += QLatin1Char(' '); } else { const Kate::TextLine l = kateTextLine(line); pos = l->firstChar(); } if (pos >= 0) { insertText(KTextEditor::Cursor(line, pos), commentLineMark); } } /* Remove from the current line a comment line mark at the beginning if there is one. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSingleLine(int line, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); editStart(); // Try to remove the long comment mark first bool removed = (removeStringFromBeginning(line, longCommentMark) || removeStringFromBeginning(line, shortCommentMark)); editEnd(); return removed; } /* Add to the current line a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSingleLine(int line, int attrib) { const QString startCommentMark = highlight()->getCommentStart(attrib) + QLatin1Char(' '); const QString stopCommentMark = QLatin1Char(' ') + highlight()->getCommentEnd(attrib); editStart(); // Add the start comment mark insertText(KTextEditor::Cursor(line, 0), startCommentMark); // Go to the end of the line const int col = m_buffer->plainLine(line)->length(); // Add the stop comment mark insertText(KTextEditor::Cursor(line, col), stopCommentMark); editEnd(); } /* Remove from the current line a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSingleLine(int line, int attrib) { const QString shortStartCommentMark = highlight()->getCommentStart(attrib); const QString longStartCommentMark = shortStartCommentMark + QLatin1Char(' '); const QString shortStopCommentMark = highlight()->getCommentEnd(attrib); const QString longStopCommentMark = QLatin1Char(' ') + shortStopCommentMark; editStart(); // Try to remove the long start comment mark first const bool removedStart = (removeStringFromBeginning(line, longStartCommentMark) || removeStringFromBeginning(line, shortStartCommentMark)); // Try to remove the long stop comment mark first const bool removedStop = removedStart && (removeStringFromEnd(line, longStopCommentMark) || removeStringFromEnd(line, shortStopCommentMark)); editEnd(); return (removedStart || removedStop); } /* Add to the current selection a start comment mark at the beginning and a stop comment mark at the end. */ void KTextEditor::DocumentPrivate::addStartStopCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); KTextEditor::Range range = view->selectionRange(); if ((range.end().column() == 0) && (range.end().line() > 0)) { range.setEnd(KTextEditor::Cursor(range.end().line() - 1, lineLength(range.end().line() - 1))); } editStart(); if (!view->blockSelection()) { insertText(range.end(), endComment); insertText(range.start(), startComment); } else { for (int line = range.start().line(); line <= range.end().line(); line++) { KTextEditor::Range subRange = rangeOnLine(range, line); insertText(subRange.end(), endComment); insertText(subRange.start(), startComment); } } editEnd(); // selection automatically updated (MovingRange) } /* Add to the current selection a comment line mark at the beginning of each line. */ void KTextEditor::DocumentPrivate::addStartLineCommentToSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString commentLineMark = highlight()->getCommentSingleLineStart(attrib) + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); // if end of selection is in column 0 in last line, omit the last line if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { //insertText (z, 0, commentLineMark); addStartLineCommentToSingleLine(z, attrib); } editEnd(); // selection automatically updated (MovingRange) } bool KTextEditor::DocumentPrivate::nextNonSpaceCharPos(int &line, int &col) { for (; line < (int)m_buffer->count(); line++) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->nextNonSpaceChar(col); if (col != -1) { return true; // Next non-space char found } col = 0; } // No non-space char found line = -1; col = -1; return false; } bool KTextEditor::DocumentPrivate::previousNonSpaceCharPos(int &line, int &col) { while (true) { Kate::TextLine textLine = m_buffer->plainLine(line); if (!textLine) { break; } col = textLine->previousNonSpaceChar(col); if (col != -1) { return true; } if (line == 0) { return false; } --line; col = textLine->length(); } // No non-space char found line = -1; col = -1; return false; } /* Remove from the selection a start comment mark at the beginning and a stop comment mark at the end. */ bool KTextEditor::DocumentPrivate::removeStartStopCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); int sl = qMax (0, view->selectionRange().start().line()); int el = qMin (view->selectionRange().end().line(), lastLine()); int sc = view->selectionRange().start().column(); int ec = view->selectionRange().end().column(); // The selection ends on the char before selectEnd if (ec != 0) { --ec; } else if (el > 0) { --el; ec = m_buffer->plainLine(el)->length() - 1; } const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/ bool remove = nextNonSpaceCharPos(sl, sc) && m_buffer->plainLine(sl)->matchesAt(sc, startComment) && previousNonSpaceCharPos(el, ec) && ((ec - endCommentLen + 1) >= 0) && m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1)); removeText(KTextEditor::Range(sl, sc, sl, sc + startCommentLen)); editEnd(); // selection automatically updated (MovingRange) } return remove; } bool KTextEditor::DocumentPrivate::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start, const KTextEditor::Cursor &end, int attrib) { const QString startComment = highlight()->getCommentStart(attrib); const QString endComment = highlight()->getCommentEnd(attrib); const int startCommentLen = startComment.length(); const int endCommentLen = endComment.length(); const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment) && m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen, endComment); if (remove) { editStart(); removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column())); removeText(KTextEditor::Range(start, startCommentLen)); editEnd(); } return remove; } /* Remove from the beginning of each line of the selection a start comment line mark. */ bool KTextEditor::DocumentPrivate::removeStartLineCommentFromSelection(KTextEditor::ViewPrivate *view, int attrib) { const QString shortCommentMark = highlight()->getCommentSingleLineStart(attrib); const QString longCommentMark = shortCommentMark + QLatin1Char(' '); int sl = view->selectionRange().start().line(); int el = view->selectionRange().end().line(); if ((view->selectionRange().end().column() == 0) && (el > 0)) { el--; } bool removed = false; editStart(); // For each line of the selection for (int z = el; z >= sl; z--) { // Try to remove the long comment mark first removed = (removeStringFromBeginning(z, longCommentMark) || removeStringFromBeginning(z, shortCommentMark) || removed); } editEnd(); // selection automatically updated (MovingRange) return removed; } /* Comment or uncomment the selection or the current line if there is no selection. */ void KTextEditor::DocumentPrivate::comment(KTextEditor::ViewPrivate *v, uint line, uint column, int change) { // skip word wrap bug #105373 const bool skipWordWrap = wordWrap(); if (skipWordWrap) { setWordWrap(false); } bool hassel = v->selection(); int c = 0; if (hassel) { c = v->selectionRange().start().column(); } int startAttrib = 0; Kate::TextLine ln = kateTextLine(line); if (c < ln->length()) { startAttrib = ln->attribute(c); } else if (!ln->contextStack().isEmpty()) { startAttrib = highlight()->attribute(ln->contextStack().last()); } bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart(startAttrib).isEmpty()); bool hasStartStopCommentMark = (!(highlight()->getCommentStart(startAttrib).isEmpty()) && !(highlight()->getCommentEnd(startAttrib).isEmpty())); if (change > 0) { // comment if (!hassel) { if (hasStartLineCommentMark) { addStartLineCommentToSingleLine(line, startAttrib); } else if (hasStartStopCommentMark) { addStartStopCommentToSingleLine(line, startAttrib); } } else { // anders: prefer single line comment to avoid nesting probs // If the selection starts after first char in the first line // or ends before the last char of the last line, we may use // multiline comment markers. // TODO We should try to detect nesting. // - if selection ends at col 0, most likely she wanted that // line ignored const KTextEditor::Range sel = v->selectionRange(); if (hasStartStopCommentMark && (!hasStartLineCommentMark || ( (sel.start().column() > m_buffer->plainLine(sel.start().line())->firstChar()) || (sel.end().column() > 0 && sel.end().column() < (m_buffer->plainLine(sel.end().line())->length())) ))) { addStartStopCommentToSelection(v, startAttrib); } else if (hasStartLineCommentMark) { addStartLineCommentToSelection(v, startAttrib); } } } else { // uncomment bool removed = false; if (!hassel) { removed = (hasStartLineCommentMark && removeStartLineCommentFromSingleLine(line, startAttrib)) || (hasStartStopCommentMark && removeStartStopCommentFromSingleLine(line, startAttrib)); } else { // anders: this seems like it will work with above changes :) removed = (hasStartStopCommentMark && removeStartStopCommentFromSelection(v, startAttrib)) || (hasStartLineCommentMark && removeStartLineCommentFromSelection(v, startAttrib)); } // recursive call for toggle comment if (!removed && change == 0) { comment(v, line, column, 1); } } if (skipWordWrap) { setWordWrap(true); // see begin of function ::comment (bug #105373) } } void KTextEditor::DocumentPrivate::transform(KTextEditor::ViewPrivate *v, const KTextEditor::Cursor &c, KTextEditor::DocumentPrivate::TextTransform t) { if (v->selection()) { editStart(); // cache the selection and cursor, so we can be sure to restore. KTextEditor::Range selection = v->selectionRange(); KTextEditor::Range range(selection.start(), 0); while (range.start().line() <= selection.end().line()) { int start = 0; int end = lineLength(range.start().line()); if (range.start().line() == selection.start().line() || v->blockSelection()) { start = selection.start().column(); } if (range.start().line() == selection.end().line() || v->blockSelection()) { end = selection.end().column(); } if (start > end) { int swapCol = start; start = end; end = swapCol; } range.setStart(KTextEditor::Cursor(range.start().line(), start)); range.setEnd(KTextEditor::Cursor(range.end().line(), end)); QString s = text(range); QString old = s; if (t == Uppercase) { s = s.toUpper(); } else if (t == Lowercase) { s = s.toLower(); } else { // Capitalize Kate::TextLine l = m_buffer->plainLine(range.start().line()); int p(0); while (p < s.length()) { // If bol or the character before is not in a word, up this one: // 1. if both start and p is 0, upper char. // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper // 3. if p-1 is not in a word, upper. if ((! range.start().column() && ! p) || ((range.start().line() == selection.start().line() || v->blockSelection()) && ! p && ! highlight()->isInWord(l->at(range.start().column() - 1))) || (p && ! highlight()->isInWord(s.at(p - 1))) ) { s[p] = s.at(p).toUpper(); } p++; } } if (s != old) { removeText(range); insertText(range.start(), s); } range.setBothLines(range.start().line() + 1); } editEnd(); // restore selection & cursor v->setSelection(selection); v->setCursorPosition(c); } else { // no selection editStart(); // get cursor KTextEditor::Cursor cursor = c; QString old = text(KTextEditor::Range(cursor, 1)); QString s; switch (t) { case Uppercase: s = old.toUpper(); break; case Lowercase: s = old.toLower(); break; case Capitalize: { Kate::TextLine l = m_buffer->plainLine(cursor.line()); while (cursor.column() > 0 && highlight()->isInWord(l->at(cursor.column() - 1), l->attribute(cursor.column() - 1))) { cursor.setColumn(cursor.column() - 1); } old = text(KTextEditor::Range(cursor, 1)); s = old.toUpper(); } break; default: break; } removeText(KTextEditor::Range(cursor, 1)); insertText(cursor, s); editEnd(); } } void KTextEditor::DocumentPrivate::joinLines(uint first, uint last) { // if ( first == last ) last += 1; editStart(); int line(first); while (first < last) { // Normalize the whitespace in the joined lines by making sure there's // always exactly one space between the joined lines // This cannot be done in editUnwrapLine, because we do NOT want this // behavior when deleting from the start of a line, just when explicitly // calling the join command Kate::TextLine l = kateTextLine(line); Kate::TextLine tl = kateTextLine(line + 1); if (!l || !tl) { editEnd(); return; } int pos = tl->firstChar(); if (pos >= 0) { if (pos != 0) { editRemoveText(line + 1, 0, pos); } if (!(l->length() == 0 || l->at(l->length() - 1).isSpace())) { editInsertText(line + 1, 0, QLatin1String(" ")); } } else { // Just remove the whitespace and let Kate handle the rest editRemoveText(line + 1, 0, tl->length()); } editUnWrapLine(line); first++; } editEnd(); } void KTextEditor::DocumentPrivate::tagLines(int start, int end) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->tagLines(start, end, true); } } void KTextEditor::DocumentPrivate::repaintViews(bool paintOnlyDirty) { foreach (KTextEditor::ViewPrivate *view, m_views) { view->repaintText(paintOnlyDirty); } } /* Bracket matching uses the following algorithm: If in overwrite mode, match the bracket currently underneath the cursor. Otherwise, if the character to the left is a bracket, match it. Otherwise if the character to the right of the cursor is a bracket, match it. Otherwise, don't match anything. */ KTextEditor::Range KTextEditor::DocumentPrivate::findMatchingBracket(const KTextEditor::Cursor &start, int maxLines) { if (maxLines < 0) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = m_buffer->plainLine(start.line()); if (!textLine) { return KTextEditor::Range::invalid(); } KTextEditor::Range range(start, start); const QChar right = textLine->at(range.start().column()); const QChar left = textLine->at(range.start().column() - 1); QChar bracket; if (config()->ovr()) { if (isBracket(right)) { bracket = right; } else { return KTextEditor::Range::invalid(); } } else if (isBracket(right)) { bracket = right; } else if (isBracket(left)) { range.setStart(KTextEditor::Cursor(range.start().line(), range.start().column() - 1)); bracket = left; } else { return KTextEditor::Range::invalid(); } const QChar opposite = matchingBracket(bracket, false); if (opposite.isNull()) { return KTextEditor::Range::invalid(); } const int searchDir = isStartBracket(bracket) ? 1 : -1; uint nesting = 0; const int minLine = qMax(range.start().line() - maxLines, 0); const int maxLine = qMin(range.start().line() + maxLines, documentEnd().line()); range.setEnd(range.start()); KTextEditor::DocumentCursor cursor(this); cursor.setPosition(range.start()); int validAttr = kateTextLine(cursor.line())->attribute(cursor.column()); while (cursor.line() >= minLine && cursor.line() <= maxLine) { if (!cursor.move(searchDir)) { return KTextEditor::Range::invalid(); } Kate::TextLine textLine = kateTextLine(cursor.line()); if (textLine->attribute(cursor.column()) == validAttr) { // Check for match QChar c = textLine->at(cursor.column()); if (c == opposite) { if (nesting == 0) { if (searchDir > 0) { // forward range.setEnd(cursor.toCursor()); } else { range.setStart(cursor.toCursor()); } return range; } nesting--; } else if (c == bracket) { nesting++; } } } return KTextEditor::Range::invalid(); } // helper: remove \r and \n from visible document name (bug #170876) inline static QString removeNewLines(const QString &str) { QString tmp(str); return tmp.replace(QLatin1String("\r\n"), QLatin1String(" ")) .replace(QLatin1Char('\r'), QLatin1Char(' ')) .replace(QLatin1Char('\n'), QLatin1Char(' ')); } void KTextEditor::DocumentPrivate::updateDocName() { // if the name is set, and starts with FILENAME, it should not be changed! if (! url().isEmpty() && (m_docName == removeNewLines(url().fileName()) || m_docName.startsWith(removeNewLines(url().fileName()) + QLatin1String(" (")))) { return; } int count = -1; foreach (KTextEditor::DocumentPrivate *doc, KTextEditor::EditorPrivate::self()->kateDocuments()) { if ((doc != this) && (doc->url().fileName() == url().fileName())) if (doc->m_docNameNumber > count) { count = doc->m_docNameNumber; } } m_docNameNumber = count + 1; QString oldName = m_docName; m_docName = removeNewLines(url().fileName()); m_isUntitled = m_docName.isEmpty(); if (m_isUntitled) { m_docName = i18n("Untitled"); } if (m_docNameNumber > 0) { m_docName = QString(m_docName + QLatin1String(" (%1)")).arg(m_docNameNumber + 1); } /** * avoid to emit this, if name doesn't change! */ if (oldName != m_docName) { emit documentNameChanged(this); } } void KTextEditor::DocumentPrivate::slotModifiedOnDisk(KTextEditor::View * /*v*/) { if (url().isEmpty() || !m_modOnHd) { return; } if (!m_fileChangedDialogsActivated || m_modOnHdHandler) { return; } // don't ask the user again and again the same thing if (m_modOnHdReason == m_prevModOnHdReason) { return; } m_prevModOnHdReason = m_modOnHdReason; m_modOnHdHandler = new KateModOnHdPrompt(this, m_modOnHdReason, reasonedMOHString()); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::saveAsTriggered, this, &DocumentPrivate::onModOnHdSaveAs); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::reloadTriggered, this, &DocumentPrivate::onModOnHdReload); connect(m_modOnHdHandler.data(), &KateModOnHdPrompt::ignoreTriggered, this, &DocumentPrivate::onModOnHdIgnore); } void KTextEditor::DocumentPrivate::onModOnHdSaveAs() { m_modOnHd = false; QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (!res.isEmpty() && checkOverwrite(res, parentWidget)) { if (! saveAs(res)) { KMessageBox::error(parentWidget, i18n("Save failed")); m_modOnHd = true; } else { delete m_modOnHdHandler; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); } } else { // the save as dialog was canceled, we are still modified on disk m_modOnHd = true; } } void KTextEditor::DocumentPrivate::onModOnHdReload() { m_modOnHd = false; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, false, OnDiskUnmodified); documentReload(); delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::onModOnHdIgnore() { // ignore as long as m_prevModOnHdReason == m_modOnHdReason delete m_modOnHdHandler; } void KTextEditor::DocumentPrivate::setModifiedOnDisk(ModifiedOnDiskReason reason) { m_modOnHdReason = reason; m_modOnHd = (reason != OnDiskUnmodified); emit modifiedOnDisk(this, (reason != OnDiskUnmodified), reason); } class KateDocumentTmpMark { public: QString line; KTextEditor::Mark mark; }; void KTextEditor::DocumentPrivate::setModifiedOnDiskWarning(bool on) { m_fileChangedDialogsActivated = on; } bool KTextEditor::DocumentPrivate::documentReload() { if (url().isEmpty()) { return false; } // typically, the message for externally modified files is visible. Since it // does not make sense showing an additional dialog, just hide the message. delete m_modOnHdHandler; if (m_modOnHd && m_fileChangedDialogsActivated) { QWidget *parentWidget(dialogParent()); int i = KMessageBox::warningYesNoCancel (parentWidget, reasonedMOHString() + QLatin1String("\n\n") + i18n("What do you want to do?"), i18n("File Was Changed on Disk"), KGuiItem(i18n("&Reload File"), QStringLiteral("view-refresh")), KGuiItem(i18n("&Ignore Changes"), QStringLiteral("dialog-warning"))); if (i != KMessageBox::Yes) { if (i == KMessageBox::No) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; return false; } } emit aboutToReload(this); QList tmp; for (QHash::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i) { KateDocumentTmpMark m; m.line = line(i.value()->line); m.mark = *i.value(); tmp.append(m); } const QString oldMode = mode(); const bool byUser = m_fileTypeSetByUser; const QString hl_mode = highlightingMode(); m_storedVariables.clear(); // save cursor positions for all views QHash cursorPositions; for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); cursorPositions.insert(v, v->cursorPosition()); } m_reloading = true; KTextEditor::DocumentPrivate::openUrl(url()); // reset some flags only valid for one reload! m_userSetEncodingForNextReload = false; // restore cursor positions for all views for (auto it = m_views.constBegin(); it != m_views.constEnd(); ++it) { auto v = it.value(); setActiveView(v); v->setCursorPosition(cursorPositions.value(v)); if (v->isVisible()) { v->repaintText(false); } } for (int z = 0; z < tmp.size(); z++) { if (z < (int)lines()) { if (line(tmp.at(z).mark.line) == tmp.at(z).line) { setMark(tmp.at(z).mark.line, tmp.at(z).mark.type); } } } if (byUser) { setMode(oldMode); } setHighlightingMode(hl_mode); emit reloaded(this); return true; } bool KTextEditor::DocumentPrivate::documentSave() { if (!url().isValid() || !isReadWrite()) { return documentSaveAs(); } return save(); } bool KTextEditor::DocumentPrivate::documentSaveAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveAsWithEncoding(const QString &encoding) { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } setEncoding(encoding); return saveAs(saveUrl); } bool KTextEditor::DocumentPrivate::documentSaveCopyAs() { const QUrl saveUrl = QFileDialog::getSaveFileUrl(dialogParent(), i18n("Save Copy of File"), url(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (saveUrl.isEmpty() || !checkOverwrite(saveUrl, dialogParent())) { return false; } QTemporaryFile file; if (!file.open()) { return false; } if (!m_buffer->saveFile(file.fileName())) { KMessageBox::error(dialogParent(), i18n("The document could not be saved, as it was not possible to write to %1.\n\nCheck that you have write access to this file or that enough disk space is available.", this->url().toDisplayString(QUrl::PreferLocalFile))); return false; } // get the right permissions, start with safe default KIO::StatJob *statJob = KIO::stat(url(), KIO::StatJob::SourceSide, 2); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); int permissions = -1; if (statJob->exec()) { permissions = KFileItem(statJob->statResult(), url()).permissions(); } // KIO move, important: allow overwrite, we checked above! KIO::FileCopyJob *job = KIO::file_copy(QUrl::fromLocalFile(file.fileName()), saveUrl, permissions, KIO::Overwrite); KJobWidgets::setWindow(job, QApplication::activeWindow()); return job->exec(); } void KTextEditor::DocumentPrivate::setWordWrap(bool on) { config()->setWordWrap(on); } bool KTextEditor::DocumentPrivate::wordWrap() const { return config()->wordWrap(); } void KTextEditor::DocumentPrivate::setWordWrapAt(uint col) { config()->setWordWrapAt(col); } unsigned int KTextEditor::DocumentPrivate::wordWrapAt() const { return config()->wordWrapAt(); } void KTextEditor::DocumentPrivate::setPageUpDownMovesCursor(bool on) { config()->setPageUpDownMovesCursor(on); } bool KTextEditor::DocumentPrivate::pageUpDownMovesCursor() const { return config()->pageUpDownMovesCursor(); } //END bool KTextEditor::DocumentPrivate::setEncoding(const QString &e) { return m_config->setEncoding(e); } QString KTextEditor::DocumentPrivate::encoding() const { return m_config->encoding(); } void KTextEditor::DocumentPrivate::updateConfig() { m_undoManager->updateConfig(); // switch indenter if needed and update config.... m_indenter->setMode(m_config->indentationMode()); m_indenter->updateConfig(); // set tab width there, too m_buffer->setTabWidth(config()->tabWidth()); // update all views, does tagAll and updateView... foreach (KTextEditor::ViewPrivate *view, m_views) { view->updateDocumentConfig(); } // update on-the-fly spell checking as spell checking defaults might have changes if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); } emit configChanged(); } //BEGIN Variable reader // "local variable" feature by anders, 2003 /* TODO add config options (how many lines to read, on/off) add interface for plugins/apps to set/get variables add view stuff */ void KTextEditor::DocumentPrivate::readVariables(bool onlyViewAndRenderer) { if (!onlyViewAndRenderer) { m_config->configStart(); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } // read a number of lines in the top/bottom of the document for (int i = 0; i < qMin(9, lines()); ++i) { readVariableLine(line(i), onlyViewAndRenderer); } if (lines() > 10) { for (int i = qMax(10, lines() - 10); i < lines(); i++) { readVariableLine(line(i), onlyViewAndRenderer); } } if (!onlyViewAndRenderer) { m_config->configEnd(); } foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } void KTextEditor::DocumentPrivate::readVariableLine(QString t, bool onlyViewAndRenderer) { - static const QRegExp kvLine(QStringLiteral("kate:(.*)")); - static const QRegExp kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); - static const QRegExp kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); - static const QRegExp kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)")); + static const QRegularExpression kvLine(QStringLiteral("kate:(.*)")); + static const QRegularExpression kvLineWildcard(QStringLiteral("kate-wildcard\\((.*)\\):(.*)")); + static const QRegularExpression kvLineMime(QStringLiteral("kate-mimetype\\((.*)\\):(.*)")); + static const QRegularExpression kvVar(QStringLiteral("([\\w\\-]+)\\s+([^;]+)")); // simple check first, no regex // no kate inside, no vars, simple... if (!t.contains(QLatin1String("kate"))) { return; } // found vars, if any QString s; // now, try first the normal ones - if (kvLine.indexIn(t) > -1) { - s = kvLine.cap(1); + auto match = kvLine.match(t); + if (match.hasMatch()) { + s = match.captured(1); //qCDebug(LOG_KTE) << "normal variable line kate: matched: " << s; - } else if (kvLineWildcard.indexIn(t) > -1) { // regex given - const QStringList wildcards(kvLineWildcard.cap(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); + } else if ((match = kvLineWildcard.match(t)).hasMatch()) { // regex given + const QStringList wildcards(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); const QString nameOfFile = url().fileName(); bool found = false; foreach (const QString &pattern, wildcards) { QRegExp wildcard(pattern, Qt::CaseSensitive, QRegExp::Wildcard); found = wildcard.exactMatch(nameOfFile); } // nothing usable found... if (!found) { return; } - s = kvLineWildcard.cap(2); + s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-wildcard: matched: " << s; - } else if (kvLineMime.indexIn(t) > -1) { // mime-type given - const QStringList types(kvLineMime.cap(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); + } else if ((match = kvLineMime.match(t)).hasMatch()) { // mime-type given + const QStringList types(match.captured(1).split(QLatin1Char(';'), QString::SkipEmptyParts)); // no matching type found if (!types.contains(mimeType())) { return; } - s = kvLineMime.cap(2); + s = match.captured(2); //qCDebug(LOG_KTE) << "guarded variable line kate-mimetype: matched: " << s; } else { // nothing found return; } - QStringList vvl; // view variable names - vvl << QStringLiteral("dynamic-word-wrap") << QStringLiteral("dynamic-word-wrap-indicators") - << QStringLiteral("line-numbers") << QStringLiteral("icon-border") << QStringLiteral("folding-markers") - << QStringLiteral("folding-preview") - << QStringLiteral("bookmark-sorting") << QStringLiteral("auto-center-lines") - << QStringLiteral("icon-bar-color") - << QStringLiteral("scrollbar-minimap") - << QStringLiteral("scrollbar-preview") + // view variable names + static const QStringList vvl { + QStringLiteral("dynamic-word-wrap") + , QStringLiteral("dynamic-word-wrap-indicators") + , QStringLiteral("line-numbers") + , QStringLiteral("icon-border") + , QStringLiteral("folding-markers") + , QStringLiteral("folding-preview") + , QStringLiteral("bookmark-sorting") + , QStringLiteral("auto-center-lines") + , QStringLiteral("icon-bar-color") + , QStringLiteral("scrollbar-minimap") + , QStringLiteral("scrollbar-preview") // renderer - << QStringLiteral("background-color") << QStringLiteral("selection-color") - << QStringLiteral("current-line-color") << QStringLiteral("bracket-highlight-color") - << QStringLiteral("word-wrap-marker-color") - << QStringLiteral("font") << QStringLiteral("font-size") << QStringLiteral("scheme"); + , QStringLiteral("background-color") + , QStringLiteral("selection-color") + , QStringLiteral("current-line-color") + , QStringLiteral("bracket-highlight-color") + , QStringLiteral("word-wrap-marker-color") + , QStringLiteral("font") + , QStringLiteral("font-size") + , QStringLiteral("scheme") + }; int spaceIndent = -1; // for backward compatibility; see below bool replaceTabsSet = false; - int p(0); + int startPos(0); QString var, val; - while ((p = kvVar.indexIn(s, p)) > -1) { - p += kvVar.matchedLength(); - var = kvVar.cap(1); - val = kvVar.cap(2).trimmed(); + while ((match = kvVar.match(s, startPos)).hasMatch()) { + startPos = match.capturedEnd(0); + var = match.captured(1); + val = match.captured(2).trimmed(); bool state; // store booleans here int n; // store ints here // only apply view & renderer config stuff if (onlyViewAndRenderer) { if (vvl.contains(var)) { // FIXME define above setViewVariable(var, val); } } else { // BOOL SETTINGS if (var == QLatin1String("word-wrap") && checkBoolValue(val, &state)) { setWordWrap(state); // ??? FIXME CHECK } // KateConfig::configFlags // FIXME should this be optimized to only a few calls? how? else if (var == QLatin1String("backspace-indents") && checkBoolValue(val, &state)) { m_config->setBackspaceIndents(state); } else if (var == QLatin1String("indent-pasted-text") && checkBoolValue(val, &state)) { m_config->setIndentPastedText(state); } else if (var == QLatin1String("replace-tabs") && checkBoolValue(val, &state)) { m_config->setReplaceTabsDyn(state); replaceTabsSet = true; // for backward compatibility; see below } else if (var == QLatin1String("remove-trailing-space") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'remove-trailing-space'. " "Please replace with 'remove-trailing-spaces modified;', see " "http://docs.kde.org/stable/en/applications/kate/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 1 : 0); } else if (var == QLatin1String("replace-trailing-space-save") && checkBoolValue(val, &state)) { qCWarning(LOG_KTE) << i18n("Using deprecated modeline 'replace-trailing-space-save'. " "Please replace with 'remove-trailing-spaces all;', see " "http://docs.kde.org/stable/en/applications/kate/config-variables.html#variable-remove-trailing-spaces"); m_config->setRemoveSpaces(state ? 2 : 0); } else if (var == QLatin1String("overwrite-mode") && checkBoolValue(val, &state)) { m_config->setOvr(state); } else if (var == QLatin1String("keep-extra-spaces") && checkBoolValue(val, &state)) { m_config->setKeepExtraSpaces(state); } else if (var == QLatin1String("tab-indents") && checkBoolValue(val, &state)) { m_config->setTabIndents(state); } else if (var == QLatin1String("show-tabs") && checkBoolValue(val, &state)) { m_config->setShowTabs(state); } else if (var == QLatin1String("show-trailing-spaces") && checkBoolValue(val, &state)) { m_config->setShowSpaces(state); } else if (var == QLatin1String("space-indent") && checkBoolValue(val, &state)) { // this is for backward compatibility; see below spaceIndent = state; } else if (var == QLatin1String("smart-home") && checkBoolValue(val, &state)) { m_config->setSmartHome(state); } else if (var == QLatin1String("newline-at-eof") && checkBoolValue(val, &state)) { m_config->setNewLineAtEof(state); } // INTEGER SETTINGS else if (var == QLatin1String("tab-width") && checkIntValue(val, &n)) { m_config->setTabWidth(n); } else if (var == QLatin1String("indent-width") && checkIntValue(val, &n)) { m_config->setIndentationWidth(n); } else if (var == QLatin1String("indent-mode")) { m_config->setIndentationMode(val); } else if (var == QLatin1String("word-wrap-column") && checkIntValue(val, &n) && n > 0) { // uint, but hard word wrap at 0 will be no fun ;) m_config->setWordWrapAt(n); } // STRING SETTINGS else if (var == QLatin1String("eol") || var == QLatin1String("end-of-line")) { - QStringList l; - l << QStringLiteral("unix") << QStringLiteral("dos") << QStringLiteral("mac"); + const QStringList l{ QStringLiteral("unix"), QStringLiteral("dos"), QStringLiteral("mac") }; if ((n = l.indexOf(val.toLower())) != -1) { /** * set eol + avoid that it is overwritten by auto-detection again! * this fixes e.g. .kateconfig files with // kate: eol dos; to work, bug 365705 */ m_config->setEol(n); m_config->setAllowEolDetection(false); } } else if (var == QLatin1String("bom") || var == QLatin1String("byte-order-mark") || var == QLatin1String("byte-order-marker")) { if (checkBoolValue(val, &state)) { m_config->setBom(state); } } else if (var == QLatin1String("remove-trailing-spaces")) { val = val.toLower(); if (val == QLatin1String("1") || val == QLatin1String("modified") || val == QLatin1String("mod") || val == QLatin1String("+")) { m_config->setRemoveSpaces(1); } else if (val == QLatin1String("2") || val == QLatin1String("all") || val == QLatin1String("*")) { m_config->setRemoveSpaces(2); } else { m_config->setRemoveSpaces(0); } } else if (var == QLatin1String("syntax") || var == QLatin1String("hl")) { setHighlightingMode(val); } else if (var == QLatin1String("mode")) { setMode(val); } else if (var == QLatin1String("encoding")) { setEncoding(val); } else if (var == QLatin1String("default-dictionary")) { setDefaultDictionary(val); } else if (var == QLatin1String("automatic-spell-checking") && checkBoolValue(val, &state)) { onTheFlySpellCheckingEnabled(state); } // VIEW SETTINGS else if (vvl.contains(var)) { setViewVariable(var, val); } else { m_storedVariables.insert(var, val); } } } // Backward compatibility // If space-indent was set, but replace-tabs was not set, we assume // that the user wants to replace tabulators and set that flag. // If both were set, replace-tabs has precedence. // At this point spaceIndent is -1 if it was never set, // 0 if it was set to off, and 1 if it was set to on. // Note that if onlyViewAndRenderer was requested, spaceIndent is -1. if (!replaceTabsSet && spaceIndent >= 0) { m_config->setReplaceTabsDyn(spaceIndent > 0); } } void KTextEditor::DocumentPrivate::setViewVariable(QString var, QString val) { KTextEditor::ViewPrivate *v; bool state; int n; QColor c; foreach (v, m_views) { if (var == QLatin1String("auto-brackets") && checkBoolValue(val, &state)) { v->config()->setAutoBrackets(state); } else if (var == QLatin1String("dynamic-word-wrap") && checkBoolValue(val, &state)) { v->config()->setDynWordWrap(state); } else if (var == QLatin1String("persistent-selection") && checkBoolValue(val, &state)) { v->config()->setPersistentSelection(state); } else if (var == QLatin1String("block-selection") && checkBoolValue(val, &state)) { v->setBlockSelection(state); } //else if ( var = "dynamic-word-wrap-indicators" ) else if (var == QLatin1String("line-numbers") && checkBoolValue(val, &state)) { v->config()->setLineNumbers(state); } else if (var == QLatin1String("icon-border") && checkBoolValue(val, &state)) { v->config()->setIconBar(state); } else if (var == QLatin1String("folding-markers") && checkBoolValue(val, &state)) { v->config()->setFoldingBar(state); } else if (var == QLatin1String("folding-preview") && checkBoolValue(val, &state)) { v->config()->setFoldingPreview(state); } else if (var == QLatin1String("auto-center-lines") && checkIntValue(val, &n)) { v->config()->setAutoCenterLines(n); } else if (var == QLatin1String("icon-bar-color") && checkColorValue(val, c)) { v->renderer()->config()->setIconBarColor(c); } else if (var == QLatin1String("scrollbar-minimap") && checkBoolValue(val, &state)) { v->config()->setScrollBarMiniMap(state); } else if (var == QLatin1String("scrollbar-preview") && checkBoolValue(val, &state)) { v->config()->setScrollBarPreview(state); } // RENDERER else if (var == QLatin1String("background-color") && checkColorValue(val, c)) { v->renderer()->config()->setBackgroundColor(c); } else if (var == QLatin1String("selection-color") && checkColorValue(val, c)) { v->renderer()->config()->setSelectionColor(c); } else if (var == QLatin1String("current-line-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedLineColor(c); } else if (var == QLatin1String("bracket-highlight-color") && checkColorValue(val, c)) { v->renderer()->config()->setHighlightedBracketColor(c); } else if (var == QLatin1String("word-wrap-marker-color") && checkColorValue(val, c)) { v->renderer()->config()->setWordWrapMarkerColor(c); } else if (var == QLatin1String("font") || (checkIntValue(val, &n) && var == QLatin1String("font-size"))) { QFont _f(v->renderer()->config()->font()); if (var == QLatin1String("font")) { _f.setFamily(val); _f.setFixedPitch(QFont(val).fixedPitch()); } else { _f.setPointSize(n); } v->renderer()->config()->setFont(_f); } else if (var == QLatin1String("scheme")) { v->renderer()->config()->setSchema(val); } } } bool KTextEditor::DocumentPrivate::checkBoolValue(QString val, bool *result) { val = val.trimmed().toLower(); - static const QStringList trueValues = QStringList() << QStringLiteral("1") << QStringLiteral("on") << QStringLiteral("true"); + static const QStringList trueValues{ QStringLiteral("1"), QStringLiteral("on"), QStringLiteral("true") }; if (trueValues.contains(val)) { *result = true; return true; } - static const QStringList falseValues = QStringList() << QStringLiteral("0") << QStringLiteral("off") << QStringLiteral("false"); + static const QStringList falseValues{ QStringLiteral("0"), QStringLiteral("off"), QStringLiteral("false") }; if (falseValues.contains(val)) { *result = false; return true; } return false; } bool KTextEditor::DocumentPrivate::checkIntValue(QString val, int *result) { bool ret(false); *result = val.toInt(&ret); return ret; } bool KTextEditor::DocumentPrivate::checkColorValue(QString val, QColor &c) { c.setNamedColor(val); return c.isValid(); } // KTextEditor::variable QString KTextEditor::DocumentPrivate::variable(const QString &name) const { return m_storedVariables.value(name, QString()); } void KTextEditor::DocumentPrivate::setVariable(const QString &name, const QString &value) { QString s = QStringLiteral("kate: "); s.append(name); s.append(QLatin1Char(' ')); s.append(value); readVariableLine(s); } //END void KTextEditor::DocumentPrivate::slotModOnHdDirty(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified)) { m_modOnHd = true; m_modOnHdReason = OnDiskModified; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdCreated(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated)) { m_modOnHd = true; m_modOnHdReason = OnDiskCreated; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotModOnHdDeleted(const QString &path) { if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted)) { m_modOnHd = true; m_modOnHdReason = OnDiskDeleted; if (!m_modOnHdTimer.isActive()) { m_modOnHdTimer.start(); } } } void KTextEditor::DocumentPrivate::slotDelayedHandleModOnHd() { // compare git hash with the one we have (if we have one) const QByteArray oldDigest = checksum(); if (!oldDigest.isEmpty() && !url().isEmpty() && url().isLocalFile()) { /** * if current checksum == checksum of new file => unmodified */ if (m_modOnHdReason != OnDiskDeleted && createDigest() && oldDigest == checksum()) { m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; } #if LIBGIT2_FOUND /** * if still modified, try to take a look at git * skip that, if document is modified! * only do that, if the file is still there, else reload makes no sense! */ if (m_modOnHd && !isModified() && QFile::exists(url().toLocalFile())) { /** * try to discover the git repo of this file * libgit2 docs state that UTF-8 is the right encoding, even on windows * I hope that is correct! */ git_repository *repository = nullptr; const QByteArray utf8Path = url().toLocalFile().toUtf8(); if (git_repository_open_ext(&repository, utf8Path.constData(), 0, nullptr) == 0) { /** * if we have repo, convert the git hash to an OID */ git_oid oid; if (git_oid_fromstr(&oid, oldDigest.toHex().data()) == 0) { /** * finally: is there a blob for this git hash? */ git_blob *blob = nullptr; if (git_blob_lookup(&blob, repository, &oid) == 0) { /** * this hash exists still in git => just reload */ m_modOnHd = false; m_modOnHdReason = OnDiskUnmodified; m_prevModOnHdReason = OnDiskUnmodified; documentReload(); } git_blob_free(blob); } } git_repository_free(repository); } #endif } /** * emit our signal to the outside! */ emit modifiedOnDisk(this, m_modOnHd, m_modOnHdReason); } QByteArray KTextEditor::DocumentPrivate::checksum() const { return m_buffer->digest(); } bool KTextEditor::DocumentPrivate::createDigest() { QByteArray digest; if (url().isLocalFile()) { QFile f(url().toLocalFile()); if (f.open(QIODevice::ReadOnly)) { // init the hash with the git header QCryptographicHash crypto(QCryptographicHash::Sha1); const QString header = QStringLiteral("blob %1").arg(f.size()); crypto.addData(header.toLatin1() + '\0'); while (!f.atEnd()) { crypto.addData(f.read(256 * 1024)); } digest = crypto.result(); } } /** * set new digest */ m_buffer->setDigest(digest); return !digest.isEmpty(); } QString KTextEditor::DocumentPrivate::reasonedMOHString() const { // squeeze path const QString str = KStringHandler::csqueeze(url().toDisplayString(QUrl::PreferLocalFile)); switch (m_modOnHdReason) { case OnDiskModified: return i18n("The file '%1' was modified by another program.", str); break; case OnDiskCreated: return i18n("The file '%1' was created by another program.", str); break; case OnDiskDeleted: return i18n("The file '%1' was deleted by another program.", str); break; default: return QString(); } Q_UNREACHABLE(); return QString(); } void KTextEditor::DocumentPrivate::removeTrailingSpaces() { const int remove = config()->removeSpaces(); if (remove == 0) { return; } // temporarily disable static word wrap (see bug #328900) const bool wordWrapEnabled = config()->wordWrap(); if (wordWrapEnabled) { setWordWrap(false); } editStart(); for (int line = 0; line < lines(); ++line) { Kate::TextLine textline = plainKateTextLine(line); // remove trailing spaces in entire document, remove = 2 // remove trailing spaces of touched lines, remove = 1 // remove trailing spaces of lines saved on disk, remove = 1 if (remove == 2 || textline->markedAsModified() || textline->markedAsSavedOnDisk()) { const int p = textline->lastChar() + 1; const int l = textline->length() - p; if (l > 0) { editRemoveText(line, p, l); } } } editEnd(); // enable word wrap again, if it was enabled (see bug #328900) if (wordWrapEnabled) { setWordWrap(true); // see begin of this function } } void KTextEditor::DocumentPrivate::updateFileType(const QString &newType, bool user) { if (user || !m_fileTypeSetByUser) { if (!newType.isEmpty()) { // remember that we got set by user m_fileTypeSetByUser = user; m_fileType = newType; m_config->configStart(); if (!m_hlSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl.isEmpty()) { int hl(KateHlManager::self()->nameFind(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).hl)); if (hl >= 0) { m_buffer->setHighlight(hl); } } /** * set the indentation mode, if any in the mode... * and user did not set it before! */ if (!m_indenterSetByUser && !KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter.isEmpty()) { config()->setIndentationMode(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).indenter); } // views! KTextEditor::ViewPrivate *v; foreach (v, m_views) { v->config()->configStart(); v->renderer()->config()->configStart(); } bool bom_settings = false; if (m_bomSetByUser) { bom_settings = m_config->bom(); } readVariableLine(KTextEditor::EditorPrivate::self()->modeManager()->fileType(newType).varLine); if (m_bomSetByUser) { m_config->setBom(bom_settings); } m_config->configEnd(); foreach (v, m_views) { v->config()->configEnd(); v->renderer()->config()->configEnd(); } } } // fixme, make this better... emit modeChanged(this); } void KTextEditor::DocumentPrivate::slotQueryClose_save(bool *handled, bool *abortClosing) { *handled = true; *abortClosing = true; if (this->url().isEmpty()) { QWidget *parentWidget(dialogParent()); const QUrl res = QFileDialog::getSaveFileUrl(parentWidget, i18n("Save File"), QUrl(), {}, nullptr, QFileDialog::DontConfirmOverwrite); if (res.isEmpty() || !checkOverwrite(res, parentWidget)) { *abortClosing = true; return; } saveAs(res); *abortClosing = false; } else { save(); *abortClosing = false; } } bool KTextEditor::DocumentPrivate::checkOverwrite(QUrl u, QWidget *parent) { if (!u.isLocalFile()) { return true; } QFileInfo info(u.path()); if (!info.exists()) { return true; } return KMessageBox::Cancel != KMessageBox::warningContinueCancel(parent, i18n("A file named \"%1\" already exists. " "Are you sure you want to overwrite it?", info.fileName()), i18n("Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), QString(), KMessageBox::Options(KMessageBox::Notify | KMessageBox::Dangerous)); } //BEGIN KTextEditor::ConfigInterface // BEGIN ConfigInterface stff QStringList KTextEditor::DocumentPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("backup-on-save-local"), QStringLiteral("backup-on-save-suffix"), QStringLiteral("backup-on-save-prefix"), QStringLiteral("replace-tabs"), QStringLiteral("indent-pasted-text"), QStringLiteral("tab-width"), QStringLiteral("indent-width"), QStringLiteral("on-the-fly-spellcheck"), }; return keys; } QVariant KTextEditor::DocumentPrivate::configValue(const QString &key) { if (key == QLatin1String("backup-on-save-local")) { return m_config->backupFlags() & KateDocumentConfig::LocalFiles; } else if (key == QLatin1String("backup-on-save-remote")) { return m_config->backupFlags() & KateDocumentConfig::RemoteFiles; } else if (key == QLatin1String("backup-on-save-suffix")) { return m_config->backupSuffix(); } else if (key == QLatin1String("backup-on-save-prefix")) { return m_config->backupPrefix(); } else if (key == QLatin1String("replace-tabs")) { return m_config->replaceTabsDyn(); } else if (key == QLatin1String("indent-pasted-text")) { return m_config->indentPastedText(); } else if (key == QLatin1String("tab-width")) { return m_config->tabWidth(); } else if (key == QLatin1String("indent-width")) { return m_config->indentationWidth(); } else if (key == QLatin1String("on-the-fly-spellcheck")) { return isOnTheFlySpellCheckingEnabled(); } // return invalid variant return QVariant(); } void KTextEditor::DocumentPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.type() == QVariant::String) { if (key == QLatin1String("backup-on-save-suffix")) { m_config->setBackupSuffix(value.toString()); } else if (key == QLatin1String("backup-on-save-prefix")) { m_config->setBackupPrefix(value.toString()); } } else if (value.type() == QVariant::Bool) { const bool bValue = value.toBool(); if (key == QLatin1String("backup-on-save-local")) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::LocalFiles; } else { f ^= KateDocumentConfig::LocalFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("backup-on-save-remote")) { uint f = m_config->backupFlags(); if (bValue) { f |= KateDocumentConfig::RemoteFiles; } else { f ^= KateDocumentConfig::RemoteFiles; } m_config->setBackupFlags(f); } else if (key == QLatin1String("replace-tabs")) { m_config->setReplaceTabsDyn(bValue); } else if (key == QLatin1String("indent-pasted-text")) { m_config->setIndentPastedText(bValue); } else if (key == QLatin1String("on-the-fly-spellcheck")) { onTheFlySpellCheckingEnabled(bValue); } } else if (value.canConvert(QVariant::Int)) { if (key == QLatin1String("tab-width")) { config()->setTabWidth(value.toInt()); } else if (key == QLatin1String("indent-width")) { config()->setIndentationWidth(value.toInt()); } } } //END KTextEditor::ConfigInterface KTextEditor::Cursor KTextEditor::DocumentPrivate::documentEnd() const { return KTextEditor::Cursor(lastLine(), lineLength(lastLine())); } bool KTextEditor::DocumentPrivate::replaceText(const KTextEditor::Range &range, const QString &s, bool block) { // TODO more efficient? editStart(); bool changed = removeText(range, block); changed |= insertText(range.start(), s, block); editEnd(); return changed; } KateHighlighting *KTextEditor::DocumentPrivate::highlight() const { return m_buffer->highlight(); } Kate::TextLine KTextEditor::DocumentPrivate::kateTextLine(int i) { m_buffer->ensureHighlighted(i); return m_buffer->plainLine(i); } Kate::TextLine KTextEditor::DocumentPrivate::plainKateTextLine(int i) { return m_buffer->plainLine(i); } bool KTextEditor::DocumentPrivate::isEditRunning() const { return editIsRunning; } void KTextEditor::DocumentPrivate::setUndoMergeAllEdits(bool merge) { if (merge && m_undoMergeAllEdits) { // Don't add another undo safe point: it will override our current one, // meaning we'll need two undo's to get back there - which defeats the object! return; } m_undoManager->undoSafePoint(); m_undoManager->setAllowComplexMerge(merge); m_undoMergeAllEdits = merge; } //BEGIN KTextEditor::MovingInterface KTextEditor::MovingCursor *KTextEditor::DocumentPrivate::newMovingCursor(const KTextEditor::Cursor &position, KTextEditor::MovingCursor::InsertBehavior insertBehavior) { return new Kate::TextCursor(buffer(), position, insertBehavior); } KTextEditor::MovingRange *KTextEditor::DocumentPrivate::newMovingRange(const KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior) { return new Kate::TextRange(buffer(), range, insertBehaviors, emptyBehavior); } qint64 KTextEditor::DocumentPrivate::revision() const { return m_buffer->history().revision(); } qint64 KTextEditor::DocumentPrivate::lastSavedRevision() const { return m_buffer->history().lastSavedRevision(); } void KTextEditor::DocumentPrivate::lockRevision(qint64 revision) { m_buffer->history().lockRevision(revision); } void KTextEditor::DocumentPrivate::unlockRevision(qint64 revision) { m_buffer->history().unlockRevision(revision); } void KTextEditor::DocumentPrivate::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); } void KTextEditor::DocumentPrivate::transformCursor(KTextEditor::Cursor &cursor, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision) { int line = cursor.line(), column = cursor.column(); m_buffer->history().transformCursor(line, column, insertBehavior, fromRevision, toRevision); cursor.setLine(line); cursor.setColumn(column); } void KTextEditor::DocumentPrivate::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision) { m_buffer->history().transformRange(range, insertBehaviors, emptyBehavior, fromRevision, toRevision); } //END //BEGIN KTextEditor::AnnotationInterface void KTextEditor::DocumentPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; emit annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::DocumentPrivate::annotationModel() const { return m_annotationModel; } //END KTextEditor::AnnotationInterface //TAKEN FROM kparts.h bool KTextEditor::DocumentPrivate::queryClose() { if (!isReadWrite() || !isModified()) { return true; } QString docName = documentName(); int res = KMessageBox::warningYesNoCancel(dialogParent(), i18n("The document \"%1\" has been modified.\n" "Do you want to save your changes or discard them?", docName), i18n("Close Document"), KStandardGuiItem::save(), KStandardGuiItem::discard()); bool abortClose = false; bool handled = false; switch (res) { case KMessageBox::Yes : sigQueryClose(&handled, &abortClose); if (!handled) { if (url().isEmpty()) { QUrl url = QFileDialog::getSaveFileUrl(dialogParent()); if (url.isEmpty()) { return false; } saveAs(url); } else { save(); } } else if (abortClose) { return false; } return waitSaveComplete(); case KMessageBox::No : return true; default : // case KMessageBox::Cancel : return false; } } void KTextEditor::DocumentPrivate::slotStarted(KIO::Job *job) { /** * if we are idle before, we are now loading! */ if (m_documentState == DocumentIdle) { m_documentState = DocumentLoading; } /** * if loading: * - remember pre loading read-write mode * if remote load: * - set to read-only * - trigger possible message */ if (m_documentState == DocumentLoading) { /** * remember state */ m_readWriteStateBeforeLoading = isReadWrite(); /** * perhaps show loading message, but wait one second */ if (job) { /** * only read only if really remote file! */ setReadWrite(false); /** * perhaps some message about loading in one second! * remember job pointer, we want to be able to kill it! */ m_loadingJob = job; QTimer::singleShot(1000, this, SLOT(slotTriggerLoadingMessage())); } } } void KTextEditor::DocumentPrivate::slotCompleted() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; } /** * Emit signal that we saved the document, if needed */ if (m_documentState == DocumentSaving || m_documentState == DocumentSavingAs) { emit documentSavedOrUploaded(this, m_documentState == DocumentSavingAs); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotCanceled() { /** * if were loading, reset back to old read-write mode before loading * and kill the possible loading message */ if (m_documentState == DocumentLoading) { setReadWrite(m_readWriteStateBeforeLoading); delete m_loadingMessage; showAndSetOpeningErrorAccess(); updateDocName(); } /** * back to idle mode */ m_documentState = DocumentIdle; m_reloading = false; } void KTextEditor::DocumentPrivate::slotTriggerLoadingMessage() { /** * no longer loading? * no message needed! */ if (m_documentState != DocumentLoading) { return; } /** * create message about file loading in progress */ delete m_loadingMessage; m_loadingMessage = new KTextEditor::Message(i18n("The file %2 is still loading.", url().toDisplayString(QUrl::PreferLocalFile), url().fileName())); m_loadingMessage->setPosition(KTextEditor::Message::TopInView); /** * if around job: add cancel action */ if (m_loadingJob) { QAction *cancel = new QAction(i18n("&Abort Loading"), nullptr); connect(cancel, SIGNAL(triggered()), this, SLOT(slotAbortLoading())); m_loadingMessage->addAction(cancel); } /** * really post message */ postMessage(m_loadingMessage); } void KTextEditor::DocumentPrivate::slotAbortLoading() { /** * no job, no work */ if (!m_loadingJob) { return; } /** * abort loading if any job * signal results! */ m_loadingJob->kill(KJob::EmitResult); m_loadingJob = nullptr; } void KTextEditor::DocumentPrivate::slotUrlChanged(const QUrl &url) { if (m_reloading) { // the URL is temporarily unset and then reset to the previous URL during reload // we do not want to notify the outside about this return; } Q_UNUSED(url); updateDocName(); emit documentUrlChanged(this); } bool KTextEditor::DocumentPrivate::save() { /** * no double save/load * we need to allow DocumentPreSavingAs here as state, as save is called in saveAs! */ if ((m_documentState != DocumentIdle) && (m_documentState != DocumentPreSavingAs)) { return false; } /** * if we are idle, we are now saving */ if (m_documentState == DocumentIdle) { m_documentState = DocumentSaving; } else { m_documentState = DocumentSavingAs; } /** * call back implementation for real work */ return KTextEditor::Document::save(); } bool KTextEditor::DocumentPrivate::saveAs(const QUrl &url) { /** * abort on bad URL * that is done in saveAs below, too * but we must check it here already to avoid messing up * as no signals will be send, then */ if (!url.isValid()) { return false; } /** * no double save/load */ if (m_documentState != DocumentIdle) { return false; } /** * we enter the pre save as phase */ m_documentState = DocumentPreSavingAs; /** * call base implementation for real work */ return KTextEditor::Document::saveAs(normalizeUrl(url)); } QString KTextEditor::DocumentPrivate::defaultDictionary() const { return m_defaultDictionary; } QList > KTextEditor::DocumentPrivate::dictionaryRanges() const { return m_dictionaryRanges; } void KTextEditor::DocumentPrivate::clearDictionaryRanges() { for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end(); ++i) { delete(*i).first; } m_dictionaryRanges.clear(); if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(); } emit dictionaryRangesPresent(false); } void KTextEditor::DocumentPrivate::setDictionary(const QString &newDictionary, const KTextEditor::Range &range) { KTextEditor::Range newDictionaryRange = range; if (!newDictionaryRange.isValid() || newDictionaryRange.isEmpty()) { return; } QList > newRanges; // all ranges is 'm_dictionaryRanges' are assumed to be mutually disjoint for (QList >::iterator i = m_dictionaryRanges.begin(); i != m_dictionaryRanges.end();) { qCDebug(LOG_KTE) << "new iteration" << newDictionaryRange; if (newDictionaryRange.isEmpty()) { break; } QPair pair = *i; QString dictionarySet = pair.second; KTextEditor::MovingRange *dictionaryRange = pair.first; qCDebug(LOG_KTE) << *dictionaryRange << dictionarySet; if (dictionaryRange->contains(newDictionaryRange) && newDictionary == dictionarySet) { qCDebug(LOG_KTE) << "dictionaryRange contains newDictionaryRange"; return; } if (newDictionaryRange.contains(*dictionaryRange)) { delete dictionaryRange; i = m_dictionaryRanges.erase(i); qCDebug(LOG_KTE) << "newDictionaryRange contains dictionaryRange"; continue; } KTextEditor::Range intersection = dictionaryRange->toRange().intersect(newDictionaryRange); if (!intersection.isEmpty() && intersection.isValid()) { if (dictionarySet == newDictionary) { // we don't have to do anything for 'intersection' // except cut off the intersection QList remainingRanges = KateSpellCheckManager::rangeDifference(newDictionaryRange, intersection); Q_ASSERT(remainingRanges.size() == 1); newDictionaryRange = remainingRanges.first(); ++i; qCDebug(LOG_KTE) << "dictionarySet == newDictionary"; continue; } QList remainingRanges = KateSpellCheckManager::rangeDifference(*dictionaryRange, intersection); for (QList::iterator j = remainingRanges.begin(); j != remainingRanges.end(); ++j) { KTextEditor::MovingRange *remainingRange = newMovingRange(*j, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); remainingRange->setFeedback(this); newRanges.push_back(QPair(remainingRange, dictionarySet)); } i = m_dictionaryRanges.erase(i); delete dictionaryRange; } else { ++i; } } m_dictionaryRanges += newRanges; if (!newDictionaryRange.isEmpty() && !newDictionary.isEmpty()) { // we don't add anything for the default dictionary KTextEditor::MovingRange *newDictionaryMovingRange = newMovingRange(newDictionaryRange, KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); newDictionaryMovingRange->setFeedback(this); m_dictionaryRanges.push_back(QPair(newDictionaryMovingRange, newDictionary)); } if (m_onTheFlyChecker && !newDictionaryRange.isEmpty()) { m_onTheFlyChecker->refreshSpellCheck(newDictionaryRange); } emit dictionaryRangesPresent(!m_dictionaryRanges.isEmpty()); } void KTextEditor::DocumentPrivate::revertToDefaultDictionary(const KTextEditor::Range &range) { setDictionary(QString(), range); } void KTextEditor::DocumentPrivate::setDefaultDictionary(const QString &dict) { if (m_defaultDictionary == dict) { return; } m_defaultDictionary = dict; if (m_onTheFlyChecker) { m_onTheFlyChecker->updateConfig(); refreshOnTheFlyCheck(); } emit defaultDictionaryChanged(this); } void KTextEditor::DocumentPrivate::onTheFlySpellCheckingEnabled(bool enable) { if (isOnTheFlySpellCheckingEnabled() == enable) { return; } if (enable) { Q_ASSERT(m_onTheFlyChecker == nullptr); m_onTheFlyChecker = new KateOnTheFlyChecker(this); } else { delete m_onTheFlyChecker; m_onTheFlyChecker = nullptr; } foreach (KTextEditor::ViewPrivate *view, m_views) { view->reflectOnTheFlySpellCheckStatus(enable); } } bool KTextEditor::DocumentPrivate::isOnTheFlySpellCheckingEnabled() const { return m_onTheFlyChecker != nullptr; } QString KTextEditor::DocumentPrivate::dictionaryForMisspelledRange(const KTextEditor::Range &range) const { if (!m_onTheFlyChecker) { return QString(); } else { return m_onTheFlyChecker->dictionaryForMisspelledRange(range); } } void KTextEditor::DocumentPrivate::clearMisspellingForWord(const QString &word) { if (m_onTheFlyChecker) { m_onTheFlyChecker->clearMisspellingForWord(word); } } void KTextEditor::DocumentPrivate::refreshOnTheFlyCheck(const KTextEditor::Range &range) { if (m_onTheFlyChecker) { m_onTheFlyChecker->refreshSpellCheck(range); } } void KTextEditor::DocumentPrivate::rangeInvalid(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::rangeEmpty(KTextEditor::MovingRange *movingRange) { deleteDictionaryRange(movingRange); } void KTextEditor::DocumentPrivate::deleteDictionaryRange(KTextEditor::MovingRange *movingRange) { qCDebug(LOG_KTE) << "deleting" << movingRange; auto finder = [=] (const QPair& item) -> bool { return item.first == movingRange; }; auto it = std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder); if (it != m_dictionaryRanges.end()) { m_dictionaryRanges.erase(it); delete movingRange; } Q_ASSERT(std::find_if(m_dictionaryRanges.begin(), m_dictionaryRanges.end(), finder) == m_dictionaryRanges.end()); } bool KTextEditor::DocumentPrivate::containsCharacterEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn; ++col) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); if (!prefixStore.findPrefix(textLine, col).isEmpty()) { return true; } } } return false; } int KTextEditor::DocumentPrivate::computePositionWrtOffsets(const OffsetList &offsetList, int pos) { int previousOffset = 0; for (OffsetList::const_iterator i = offsetList.begin(); i != offsetList.end(); ++i) { if ((*i).first > pos) { break; } previousOffset = (*i).second; } return pos + previousOffset; } QString KTextEditor::DocumentPrivate::decodeCharacters(const KTextEditor::Range &range, KTextEditor::DocumentPrivate::OffsetList &decToEncOffsetList, KTextEditor::DocumentPrivate::OffsetList &encToDecOffsetList) { QString toReturn; KTextEditor::Cursor previous = range.start(); int decToEncCurrentOffset = 0, encToDecCurrentOffset = 0; int i = 0; int newI = 0; KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const KatePrefixStore &prefixStore = highlighting->getCharacterEncodingsPrefixStore(attr); const QHash &characterEncodingsHash = highlighting->getCharacterEncodings(attr); QString matchingPrefix = prefixStore.findPrefix(textLine, col); if (!matchingPrefix.isEmpty()) { toReturn += text(KTextEditor::Range(previous, KTextEditor::Cursor(line, col))); const QChar &c = characterEncodingsHash.value(matchingPrefix); const bool isNullChar = c.isNull(); if (!c.isNull()) { toReturn += c; } i += matchingPrefix.length(); col += matchingPrefix.length(); previous = KTextEditor::Cursor(line, col); decToEncCurrentOffset = decToEncCurrentOffset - (isNullChar ? 0 : 1) + matchingPrefix.length(); encToDecCurrentOffset = encToDecCurrentOffset - matchingPrefix.length() + (isNullChar ? 0 : 1); newI += (isNullChar ? 0 : 1); decToEncOffsetList.push_back(QPair(newI, decToEncCurrentOffset)); encToDecOffsetList.push_back(QPair(i, encToDecCurrentOffset)); continue; } ++col; ++i; ++newI; } ++i; ++newI; } if (previous < range.end()) { toReturn += text(KTextEditor::Range(previous, range.end())); } return toReturn; } void KTextEditor::DocumentPrivate::replaceCharactersByEncoding(const KTextEditor::Range &range) { KateHighlighting *highlighting = highlight(); Kate::TextLine textLine; const int rangeStartLine = range.start().line(); const int rangeStartColumn = range.start().column(); const int rangeEndLine = range.end().line(); const int rangeEndColumn = range.end().column(); for (int line = range.start().line(); line <= rangeEndLine; ++line) { textLine = kateTextLine(line); int startColumn = (line == rangeStartLine) ? rangeStartColumn : 0; int endColumn = (line == rangeEndLine) ? rangeEndColumn : textLine->length(); for (int col = startColumn; col < endColumn;) { int attr = textLine->attribute(col); const QHash &reverseCharacterEncodingsHash = highlighting->getReverseCharacterEncodings(attr); QHash::const_iterator it = reverseCharacterEncodingsHash.find(textLine->at(col)); if (it != reverseCharacterEncodingsHash.end()) { replaceText(KTextEditor::Range(line, col, line, col + 1), *it); col += (*it).length(); continue; } ++col; } } } // // Highlighting information // KTextEditor::Attribute::Ptr KTextEditor::DocumentPrivate::attributeAt(const KTextEditor::Cursor &position) { KTextEditor::Attribute::Ptr attrib(new KTextEditor::Attribute()); KTextEditor::ViewPrivate *view = m_views.empty() ? nullptr : m_views.begin().value(); if (!view) { qCWarning(LOG_KTE) << "ATTENTION: cannot access lineAttributes() without any View (will be fixed eventually)"; return attrib; } Kate::TextLine kateLine = kateTextLine(position.line()); if (!kateLine) { return attrib; } *attrib = *view->renderer()->attribute(kateLine->attribute(position.column())); return attrib; } QStringList KTextEditor::DocumentPrivate::embeddedHighlightingModes() const { return highlight()->getEmbeddedHighlightingModes(); } QString KTextEditor::DocumentPrivate::highlightingModeAt(const KTextEditor::Cursor &position) { Kate::TextLine kateLine = kateTextLine(position.line()); // const QVector< short >& attrs = kateLine->ctxArray(); // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // foreach( short a, attrs ) { // qCDebug(LOG_KTE) << a; // } // qCDebug(LOG_KTE) << "----------------------------------------------------------------------"; // qCDebug(LOG_KTE) << "col: " << position.column() << " lastchar:" << kateLine->lastChar() << " length:" << kateLine->length() << "global mode:" << highlightingMode(); int len = kateLine->length(); int pos = position.column(); if (pos >= len) { const Kate::TextLineData::ContextStack &ctxs = kateLine->contextStack(); int ctxcnt = ctxs.count(); if (ctxcnt == 0) { return highlightingMode(); } int ctx = ctxs.at(ctxcnt - 1); if (ctx == 0) { return highlightingMode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForContext(ctx)); } int attr = kateLine->attribute(pos); if (attr == 0) { return mode(); } return KateHlManager::self()->nameForIdentifier(highlight()->hlKeyForAttrib(attr)); } Kate::SwapFile *KTextEditor::DocumentPrivate::swapFile() { return m_swapfile; } /** * \return \c -1 if \c line or \c column invalid, otherwise one of * standard style attribute number */ int KTextEditor::DocumentPrivate::defStyleNum(int line, int column) { // Validate parameters to prevent out of range access if (line < 0 || line >= lines() || column < 0) { return -1; } // get highlighted line Kate::TextLine tl = kateTextLine(line); // make sure the textline is a valid pointer if (!tl) { return -1; } /** * either get char attribute or attribute of context still active at end of line */ int attribute = 0; if (column < tl->length()) { attribute = tl->attribute(column); } else if (column == tl->length()) { KateHlContext *context = tl->contextStack().isEmpty() ? highlight()->contextNum(0) : highlight()->contextNum(tl->contextStack().back()); attribute = context->attr; } else { return -1; } return highlight()->defaultStyleForAttribute(attribute); } bool KTextEditor::DocumentPrivate::isComment(int line, int column) { const int defaultStyle = defStyleNum(line, column); return defaultStyle == KTextEditor::dsComment; } int KTextEditor::DocumentPrivate::findTouchedLine(int startLine, bool down) { const int offset = down ? 1 : -1; const int lineCount = lines(); while (startLine >= 0 && startLine < lineCount) { Kate::TextLine tl = m_buffer->plainLine(startLine); if (tl && (tl->markedAsModified() || tl->markedAsSavedOnDisk())) { return startLine; } startLine += offset; } return -1; } void KTextEditor::DocumentPrivate::setActiveTemplateHandler(KateTemplateHandler* handler) { // delete any active template handler delete m_activeTemplateHandler.data(); m_activeTemplateHandler = handler; } //BEGIN KTextEditor::MessageInterface bool KTextEditor::DocumentPrivate::postMessage(KTextEditor::Message *message) { // no message -> cancel if (!message) { return false; } // make sure the desired view belongs to this document if (message->view() && message->view()->document() != this) { qCWarning(LOG_KTE) << "trying to post a message to a view of another document:" << message->text(); return false; } message->setParent(this); message->setDocument(this); // if there are no actions, add a close action by default if widget does not auto-hide if (message->actions().count() == 0 && message->autoHide() < 0) { QAction *closeAction = new QAction(QIcon::fromTheme(QStringLiteral("window-close")), i18n("&Close"), nullptr); closeAction->setToolTip(i18n("Close message")); message->addAction(closeAction); } // make sure the message is registered even if no actions and no views exist m_messageHash[message] = QList >(); // reparent actions, as we want full control over when they are deleted foreach (QAction *action, message->actions()) { action->setParent(nullptr); m_messageHash[message].append(QSharedPointer(action)); } // post message to requested view, or to all views if (KTextEditor::ViewPrivate *view = qobject_cast(message->view())) { view->postMessage(message, m_messageHash[message]); } else { foreach (KTextEditor::ViewPrivate *view, m_views) { view->postMessage(message, m_messageHash[message]); } } // also catch if the user manually calls delete message connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*))); return true; } void KTextEditor::DocumentPrivate::messageDestroyed(KTextEditor::Message *message) { // KTE:Message is already in destructor Q_ASSERT(m_messageHash.contains(message)); m_messageHash.remove(message); } //END KTextEditor::MessageInterface void KTextEditor::DocumentPrivate::closeDocumentInApplication() { KTextEditor::EditorPrivate::self()->application()->closeDocument(this); } diff --git a/src/include/ktexteditor/command.h b/src/include/ktexteditor/command.h index a0c3a8c0..50db0b7a 100644 --- a/src/include/ktexteditor/command.h +++ b/src/include/ktexteditor/command.h @@ -1,212 +1,207 @@ /* This file is part of the KDE project Copyright (C) 2005 Christoph Cullmann (cullmann@kde.org) Copyright (C) 2005-2006 Dominik Haumann (dhdev@gmx.de) Copyright (C) 2008 Erlend Hamberg (ehamberg@gmail.com) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTEXTEDITOR_COMMAND_H #define KTEXTEDITOR_COMMAND_H #include #include #include #include class QStringList; class KCompletion; namespace KTextEditor { class View; /** * \brief An Editor command line command. * * \section cmd_intro Introduction * * The Command class represents a command for the editor command line. A * command simply consists of a string, for example \e find. The command * auto-registers itself at the Editor. The Editor itself queries * the command for a list of accepted strings/commands by calling cmds(). * If the command gets invoked the function exec() is called, i.e. you have * to implement the \e reaction in exec(). Whenever the user needs help for * a command help() is called. * * \section cmd_information Command Information * To provide reasonable information about a specific command there are the * following accessor functions for a given command string: * - name() returns a label * - description() returns a descriptive text * - category() returns a category into which the command fits. * * These getters allow KTextEditor implementations to plug commands into menus * and toolbars, so that a user can assign shortcuts. * * \section cmd_completion Command Completion * * The Command optionally can show a completion popup to help the user select * a valid entry as first parameter to the Command. To this end, return a * valid completion item by reiplementing completionObject(). * * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * \section cmd_interactive Interactive Commands * * In case the Command needs to interactively process the text of the parameters, * override wantsToProcessText() by returning @e true and reimplement processText(). * * A typical example of an interative command would be the incremental search. * - * \section cmd_extension Command Extensions - * - * The class RangeCommand enables you to support ranges so that you can apply - * commands on regions of text. - * * \see KTextEditor::CommandInterface * \author Christoph Cullmann \ */ class KTEXTEDITOR_EXPORT Command : public QObject { Q_OBJECT public: /** * Constructor with \p parent. * Will register this command for the commands names given in \p cmds at the global editor instance. */ Command(const QStringList &cmds, QObject *parent = nullptr); /** * Virtual destructor. * Will unregister this command at the global editor instance. */ virtual ~Command(); public: /** * Return a list of strings a command may begin with. * This is the same list the command was constructed with. * A string is the start part of a pure text which can be handled by this * command, i.e. for the command s/sdl/sdf/g the corresponding string is * simply \e s, and for char:1212 simply \e char. * \return list of supported commands */ inline const QStringList &cmds() const { return m_cmds; } /** * Find out if a given command can act on a range. This is used for checking * if a command should be called when the user also gave a range or if an * error should be raised. * - * \return \e true if command supports acting on a range of lines, false if - * not, default implementation returns false + * \return \e true if command supports acting on a range of lines, \e false + * not. The default implementation returns \e false. */ virtual bool supportsRange(const QString &cmd); /** * Execute the command for the given \p view and \p cmd string. * Return the success value and a \p msg for status. As example we * consider a replace command. The replace command would return the number * of replaced strings as \p msg, like "16 replacements made." If an error * occurred in the usage it would return \e false and set the \p msg to * something like "missing argument." or such. * * If a non-invalid range is given, the command shall be executed on that range. * supportsRange() tells if the command supports that. * * \return \e true on success, otherwise \e false */ virtual bool exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) = 0; /** * Shows help for the given \p view and \p cmd string. * If your command has a help text for \p cmd you have to return \e true * and set the \p msg to a meaningful text. The help text is embedded by * the Editor in a Qt::RichText enabled widget, e.g. a QToolTip. * \return \e true if your command has a help text, otherwise \e false */ virtual bool help(KTextEditor::View *view, const QString &cmd, QString &msg) = 0; /** * Return a KCompletion object that will substitute the command line * default one while typing the first argument of the command \p cmdname. * The text will be added to the command separated by one space character. * * Override this method if your command can provide a completion object. * The returned completion object is deleted automatically once it is not needed * anymore. Therefore neither delete the completion object yourself nor return * the same completion object twice. * * The default implementation returns a null pointer (\e nullptr). * * \param view the view the command will work on * \param cmdname the command name associated with this request. * \return a valid completion object or \e nullptr, if a completion object is * not supported */ virtual KCompletion *completionObject(KTextEditor::View *view, const QString &cmdname); /** * Check, whether the command wants to process text interactively for the * given command with name \p cmdname. * If you return true, the command's processText() method is called * whenever the text in the command line changed. * * Reimplement this to return true, if your commands wants to process the * text while typing. * * \param cmdname the command name associated with this query. * \return \e true, if your command wants to process text interactively, * otherwise \e false * \see processText() */ virtual bool wantsToProcessText(const QString &cmdname); /** * This is called by the command line each time the argument text for the * command changed, if wantsToProcessText() returns \e true. * \param view the current view * \param text the current command text typed by the user * \see wantsToProcessText() */ virtual void processText(KTextEditor::View *view, const QString &text); private: /** * the command list this command got constructed with */ const QStringList m_cmds; /** * Private d-pointer */ class CommandPrivate * const d; }; } #endif diff --git a/src/mode/katemodeconfigpage.cpp b/src/mode/katemodeconfigpage.cpp index 653841eb..14ee9168 100644 --- a/src/mode/katemodeconfigpage.cpp +++ b/src/mode/katemodeconfigpage.cpp @@ -1,308 +1,308 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katemodeconfigpage.h" #include "katedocument.h" #include "kateconfig.h" #include "kateview.h" #include "kateglobal.h" #include "kateautoindent.h" #include "katesyntaxmanager.h" #include "katesyntaxdocument.h" #include "ui_filetypeconfigwidget.h" #include #include "katepartdebug.h" -#include +#include #include #include #include #include #include #include #include //END Includes ModeConfigPage::ModeConfigPage(QWidget *parent) : KateConfigPage(parent) { m_lastType = -1; // This will let us have more separation between this page and // the QTabWidget edge (ereslibre) QVBoxLayout *layout = new QVBoxLayout; QWidget *newWidget = new QWidget(this); ui = new Ui::FileTypeConfigWidget(); ui->setupUi(newWidget); ui->cmbHl->addItem(i18n(""), QVariant(QString())); for (int i = 0; i < KateHlManager::self()->highlights(); i++) { if (KateHlManager::self()->hlSection(i).length() > 0) ui->cmbHl->addItem(KateHlManager::self()->hlSection(i) + QLatin1String("/") + KateHlManager::self()->hlNameTranslated(i), QVariant(KateHlManager::self()->hlName(i))); else { ui->cmbHl->addItem(KateHlManager::self()->hlNameTranslated(i), QVariant(KateHlManager::self()->hlName(i))); } } QStringList indentationModes; indentationModes << i18n("Use Default"); indentationModes << KateAutoIndent::listModes(); ui->cmbIndenter->addItems(indentationModes); connect(ui->cmbFiletypes, SIGNAL(activated(int)), this, SLOT(typeChanged(int))); connect(ui->btnNew, SIGNAL(clicked()), this, SLOT(newType())); connect(ui->btnDelete, SIGNAL(clicked()), this, SLOT(deleteType())); ui->btnMimeTypes->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); connect(ui->btnMimeTypes, SIGNAL(clicked()), this, SLOT(showMTDlg())); connect(ui->btnDownload, SIGNAL(clicked()), this, SLOT(hlDownload())); reload(); connect(ui->edtName, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(ui->edtSection, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(ui->edtVariables, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(ui->edtFileExtensions, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(ui->edtMimeTypes, SIGNAL(textChanged(QString)), this, SLOT(slotChanged())); connect(ui->sbPriority, SIGNAL(valueChanged(int)), this, SLOT(slotChanged())); connect(ui->cmbHl, SIGNAL(activated(int)), this, SLOT(slotChanged())); connect(ui->cmbIndenter, SIGNAL(activated(int)), this, SLOT(slotChanged())); layout->addWidget(newWidget); setLayout(layout); } ModeConfigPage::~ModeConfigPage() { qDeleteAll(m_types); delete ui; } void ModeConfigPage::apply() { if (!hasChanged()) { return; } save(); KTextEditor::EditorPrivate::self()->modeManager()->save(m_types); } void ModeConfigPage::reload() { qDeleteAll(m_types); m_types.clear(); // deep copy... foreach (KateFileType *type, KTextEditor::EditorPrivate::self()->modeManager()->list()) { KateFileType *t = new KateFileType(); *t = *type; m_types.append(t); } update(); } void ModeConfigPage::reset() { reload(); } void ModeConfigPage::defaults() { reload(); } void ModeConfigPage::update() { m_lastType = -1; ui->cmbFiletypes->clear(); foreach (KateFileType *type, m_types) { if (!type->sectionTranslated().isEmpty()) { ui->cmbFiletypes->addItem(type->sectionTranslated() + QLatin1String("/") + type->nameTranslated()); } else { ui->cmbFiletypes->addItem(type->nameTranslated()); } } // get current filetype from active view via the host application int currentIndex = 0; KTextEditor::ViewPrivate *kv = qobject_cast(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView()); if (kv) { const QString filetypeName = kv->doc()->fileType(); for (int i = 0; i < m_types.size(); ++i) { if (filetypeName == m_types[i]->name) { currentIndex = i; break; } } } ui->cmbFiletypes->setCurrentIndex(currentIndex); typeChanged(currentIndex); ui->cmbFiletypes->setEnabled(ui->cmbFiletypes->count() > 0); } void ModeConfigPage::deleteType() { int type = ui->cmbFiletypes->currentIndex(); if (type > -1 && type < m_types.count()) { delete m_types[type]; m_types.removeAt(type); update(); } } void ModeConfigPage::newType() { QString newN = i18n("New Filetype"); for (int i = 0; i < m_types.count(); ++i) { KateFileType *type = m_types.at(i); if (type->name == newN) { ui->cmbFiletypes->setCurrentIndex(i); typeChanged(i); return; } } KateFileType *newT = new KateFileType(); newT->priority = 0; newT->name = newN; newT->hlGenerated = false; m_types.prepend(newT); update(); } void ModeConfigPage::save() { if (m_lastType != -1) { if (!m_types[m_lastType]->hlGenerated) { m_types[m_lastType]->name = ui->edtName->text(); m_types[m_lastType]->section = ui->edtSection->text(); } m_types[m_lastType]->varLine = ui->edtVariables->text(); m_types[m_lastType]->wildcards = ui->edtFileExtensions->text().split(QLatin1Char(';'), QString::SkipEmptyParts); m_types[m_lastType]->mimetypes = ui->edtMimeTypes->text().split(QLatin1Char(';'), QString::SkipEmptyParts); m_types[m_lastType]->priority = ui->sbPriority->value(); m_types[m_lastType]->hl = ui->cmbHl->itemData(ui->cmbHl->currentIndex()).toString(); if (ui->cmbIndenter->currentIndex() > 0) { m_types[m_lastType]->indenter = KateAutoIndent::modeName(ui->cmbIndenter->currentIndex() - 1); } else { m_types[m_lastType]->indenter = QString(); } } } void ModeConfigPage::typeChanged(int type) { save(); ui->cmbHl->setEnabled(true); ui->btnDelete->setEnabled(true); ui->edtName->setEnabled(true); ui->edtSection->setEnabled(true); if (type > -1 && type < m_types.count()) { KateFileType *t = m_types.at(type); ui->gbProperties->setTitle(i18n("Properties of %1", ui->cmbFiletypes->currentText())); ui->gbProperties->setEnabled(true); ui->btnDelete->setEnabled(true); ui->edtName->setText(t->nameTranslated()); ui->edtSection->setText(t->sectionTranslated()); ui->edtVariables->setText(t->varLine); ui->edtFileExtensions->setText(t->wildcards.join(QLatin1Char(';'))); ui->edtMimeTypes->setText(t->mimetypes.join(QLatin1Char(';'))); ui->sbPriority->setValue(t->priority); ui->cmbHl->setEnabled(!t->hlGenerated); ui->btnDelete->setEnabled(!t->hlGenerated); ui->edtName->setEnabled(!t->hlGenerated); ui->edtSection->setEnabled(!t->hlGenerated); // activate current hl... for (int i = 0; i < ui->cmbHl->count(); ++i) if (ui->cmbHl->itemData(i).toString() == t->hl) { ui->cmbHl->setCurrentIndex(i); } // activate the right indenter int indenterIndex = 0; if (!t->indenter.isEmpty()) { indenterIndex = KateAutoIndent::modeNumber(t->indenter) + 1; } ui->cmbIndenter->setCurrentIndex(indenterIndex); } else { ui->gbProperties->setTitle(i18n("Properties")); ui->gbProperties->setEnabled(false); ui->btnDelete->setEnabled(false); ui->edtName->clear(); ui->edtSection->clear(); ui->edtVariables->clear(); ui->edtFileExtensions->clear(); ui->edtMimeTypes->clear(); ui->sbPriority->setValue(0); ui->cmbHl->setCurrentIndex(0); ui->cmbIndenter->setCurrentIndex(0); } m_lastType = type; } void ModeConfigPage::showMTDlg() { QString text = i18n("Select the MimeTypes you want for this file type.\nPlease note that this will automatically edit the associated file extensions as well."); - QStringList list = ui->edtMimeTypes->text().split(QRegExp(QLatin1String("\\s*;\\s*")), QString::SkipEmptyParts); + QStringList list = ui->edtMimeTypes->text().split(QRegularExpression(QStringLiteral("\\s*;\\s*")), QString::SkipEmptyParts); KMimeTypeChooserDialog d(i18n("Select Mime Types"), text, list, QStringLiteral("text"), this); if (d.exec() == QDialog::Accepted) { // do some checking, warn user if mime types or patterns are removed. // if the lists are empty, and the fields not, warn. ui->edtFileExtensions->setText(d.chooser()->patterns().join(QLatin1Char(';'))); ui->edtMimeTypes->setText(d.chooser()->mimeTypes().join(QLatin1Char(';'))); } } void ModeConfigPage::hlDownload() { KateHlDownloadDialog diag(this, "hlDownload", true); diag.exec(); } QString ModeConfigPage::name() const { return i18n("Modes && Filetypes"); } diff --git a/src/mode/katemodeconfigpage.h b/src/mode/katemodeconfigpage.h index eaf9b64e..2d26b843 100644 --- a/src/mode/katemodeconfigpage.h +++ b/src/mode/katemodeconfigpage.h @@ -1,67 +1,66 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATE_MODECONFIGPAGE_H__ #define KATE_MODECONFIGPAGE_H__ -#include #include #include #include "katedialogs.h" #include "katemodemanager.h" namespace Ui { class FileTypeConfigWidget; } class ModeConfigPage : public KateConfigPage { Q_OBJECT public: explicit ModeConfigPage(QWidget *parent); ~ModeConfigPage(); QString name() const Q_DECL_OVERRIDE; public Q_SLOTS: void apply() Q_DECL_OVERRIDE; void reload() Q_DECL_OVERRIDE; void reset() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; private Q_SLOTS: void update(); void deleteType(); void newType(); void typeChanged(int type); void showMTDlg(); void save(); void hlDownload(); private: Ui::FileTypeConfigWidget *ui; QList m_types; int m_lastType; }; #endif diff --git a/src/mode/katemodemanager.cpp b/src/mode/katemodemanager.cpp index 1d0d0ecb..bb8f2acc 100644 --- a/src/mode/katemodemanager.cpp +++ b/src/mode/katemodemanager.cpp @@ -1,296 +1,294 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ //BEGIN Includes #include "katemodemanager.h" #include "katewildcardmatcher.h" #include "katedocument.h" #include "kateconfig.h" #include "kateview.h" #include "kateglobal.h" #include "katesyntaxmanager.h" #include "katesyntaxdocument.h" #include "katepartdebug.h" #include -#include - #include //END Includes static QStringList vectorToList(const QVector &v) { QStringList l; l.reserve(v.size()); std::copy(v.begin(), v.end(), std::back_inserter(l)); return l; } KateModeManager::KateModeManager() { update(); } KateModeManager::~KateModeManager() { qDeleteAll(m_types); } bool compareKateFileType(const KateFileType *const left, const KateFileType *const right) { int comparison = left->section.compare(right->section, Qt::CaseInsensitive); if (comparison == 0) { comparison = left->name.compare(right->name, Qt::CaseInsensitive); } return comparison < 0; } // // read the types from config file and update the internal list // void KateModeManager::update() { KConfig config(QStringLiteral("katemoderc"), KConfig::NoGlobals); QStringList g(config.groupList()); qDeleteAll(m_types); m_types.clear(); m_name2Type.clear(); for (int z = 0; z < g.count(); z++) { KConfigGroup cg(&config, g[z]); KateFileType *type = new KateFileType(); type->number = z; type->name = g[z]; type->section = cg.readEntry(QStringLiteral("Section")); type->wildcards = cg.readXdgListEntry(QStringLiteral("Wildcards")); type->mimetypes = cg.readXdgListEntry(QStringLiteral("Mimetypes")); type->priority = cg.readEntry(QStringLiteral("Priority"), 0); type->varLine = cg.readEntry(QStringLiteral("Variables")); type->indenter = cg.readEntry(QStringLiteral("Indenter")); type->hl = cg.readEntry(QStringLiteral("Highlighting")); // only for generated types... type->hlGenerated = cg.readEntry(QStringLiteral("Highlighting Generated"), false); type->version = cg.readEntry(QStringLiteral("Highlighting Version")); // insert into the list + hash... m_types.append(type); m_name2Type.insert(type->name, type); } // try if the hl stuff is up to date... const auto modes = KateHlManager::self()->modeList(); for (int i = 0; i < modes.size(); ++i) { KateFileType *type = nullptr; bool newType = false; if (m_name2Type.contains(modes[i].name())) { type = m_name2Type[modes[i].name()]; } else { newType = true; type = new KateFileType(); type->name = modes[i].name(); type->priority = 0; m_types.append(type); m_name2Type.insert(type->name, type); } if (newType || type->version != QString::number(modes[i].version())) { type->name = modes[i].name(); type->section = modes[i].section(); type->wildcards = vectorToList(modes[i].extensions()); type->mimetypes = vectorToList(modes[i].mimeTypes()); type->priority = modes[i].priority(); type->version = QString::number(modes[i].version()); type->indenter = modes[i].indenter(); type->hl = modes[i].name(); type->hlGenerated = true; } type->translatedName = modes[i].translatedName(); type->translatedSection = modes[i].translatedSection(); } // sort the list... qSort(m_types.begin(), m_types.end(), compareKateFileType); // add the none type... KateFileType *t = new KateFileType(); // DO NOT TRANSLATE THIS, DONE LATER, marked by hlGenerated t->name = QStringLiteral("Normal"); t->hl = QStringLiteral("None"); t->hlGenerated = true; m_types.prepend(t); } // // save the given list to config file + update // void KateModeManager::save(const QList &v) { KConfig katerc(QStringLiteral("katemoderc"), KConfig::NoGlobals); QStringList newg; foreach (const KateFileType *type, v) { KConfigGroup config(&katerc, type->name); config.writeEntry("Section", type->section); config.writeXdgListEntry("Wildcards", type->wildcards); config.writeXdgListEntry("Mimetypes", type->mimetypes); config.writeEntry("Priority", type->priority); config.writeEntry("Indenter", type->indenter); QString varLine = type->varLine; - if (QRegExp(QLatin1String("kate:(.*)")).indexIn(varLine) < 0) { + if (!varLine.contains(QStringLiteral("kate:"))) { varLine.prepend(QLatin1String("kate: ")); } config.writeEntry("Variables", varLine); config.writeEntry("Highlighting", type->hl); // only for generated types... config.writeEntry("Highlighting Generated", type->hlGenerated); config.writeEntry("Highlighting Version", type->version); newg << type->name; } foreach (const QString &groupName, katerc.groupList()) { if (newg.indexOf(groupName) == -1) { katerc.deleteGroup(groupName); } } katerc.sync(); update(); } QString KateModeManager::fileType(KTextEditor::DocumentPrivate *doc, const QString &fileToReadFrom) { if (!doc) { return QString(); } if (m_types.isEmpty()) { return QString(); } QString fileName = doc->url().toString(); int length = doc->url().toString().length(); QString result; // Try wildcards if (! fileName.isEmpty()) { static const QStringList commonSuffixes = QStringLiteral(".orig;.new;~;.bak;.BAK").split(QLatin1Char(';')); if (!(result = wildcardsFind(fileName)).isEmpty()) { return result; } QString backupSuffix = KateDocumentConfig::global()->backupSuffix(); if (fileName.endsWith(backupSuffix)) { if (!(result = wildcardsFind(fileName.left(length - backupSuffix.length()))).isEmpty()) { return result; } } for (QStringList::ConstIterator it = commonSuffixes.begin(); it != commonSuffixes.end(); ++it) { if (*it != backupSuffix && fileName.endsWith(*it)) { if (!(result = wildcardsFind(fileName.left(length - (*it).length()))).isEmpty()) { return result; } } } } // either read the file passed to this function (pre-load) or use the normal mimeType() KF KTextEditor API QString mtName; if (!fileToReadFrom.isEmpty()) { mtName = QMimeDatabase().mimeTypeForFile(fileToReadFrom).name(); } else { mtName = doc->mimeType(); } QList types; foreach (KateFileType *type, m_types) { if (type->mimetypes.indexOf(mtName) > -1) { types.append(type); } } if (!types.isEmpty()) { int pri = -1; QString name; foreach (KateFileType *type, types) { if (type->priority > pri) { pri = type->priority; name = type->name; } } return name; } return QString(); } QString KateModeManager::wildcardsFind(const QString &fileName) { KateFileType *match = nullptr; int minPrio = -1; foreach (KateFileType *type, m_types) { if (type->priority <= minPrio) { continue; } foreach (const QString &wildcard, type->wildcards) { if (KateWildcardMatcher::exactMatch(fileName, wildcard)) { match = type; minPrio = type->priority; break; } } } return (match == nullptr) ? QString() : match->name; } const KateFileType &KateModeManager::fileType(const QString &name) const { for (int i = 0; i < m_types.size(); ++i) if (m_types[i]->name == name) { return *m_types[i]; } static KateFileType notype; return notype; } diff --git a/src/printing/printpainter.cpp b/src/printing/printpainter.cpp index 240de8e2..34dbdaa7 100644 --- a/src/printing/printpainter.cpp +++ b/src/printing/printpainter.cpp @@ -1,720 +1,722 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2002-2010 Anders Lund * * Rewritten based on code of Copyright (c) 2002 Michael Goffioul * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "printpainter.h" #include "katetextfolding.h" #include "katedocument.h" #include "katebuffer.h" #include "kateview.h" #include "katehighlight.h" #include "katepartdebug.h" #include "katetextlayout.h" #include #include #include #include using namespace KatePrinter; class KatePrinter::PageLayout { public: PageLayout() : pageWidth(0) , pageHeight(0) , headerWidth(0) , maxWidth(0) , maxHeight(0) , xstart(0) , innerMargin(0) , selectionOnly(false) , firstline(0) , lastline(0) , headerHeight(0) , headerTagList() , footerHeight(0) , footerTagList() , selectionRange() {} uint pageWidth; uint pageHeight; uint headerWidth; uint maxWidth; uint maxHeight; int xstart; // beginning point for painting lines int innerMargin; bool selectionOnly; uint firstline; uint lastline; // Header/Footer Page uint headerHeight; QStringList headerTagList; uint footerHeight; QStringList footerTagList; KTextEditor::Range selectionRange; }; PrintPainter::PrintPainter(KTextEditor::DocumentPrivate *doc, KTextEditor::ViewPrivate *view) : m_view(view) , m_doc(doc) , m_printGuide(false) , m_printLineNumbers(false) , m_useHeader(false) , m_useFooter(false) , m_useBackground(false) , m_useBox(false) , m_useHeaderBackground(false) , m_useFooterBackground(false) , m_boxMargin(0) , m_boxWidth(1) , m_boxColor(Qt::black) , m_headerBackground(Qt::lightGray) , m_headerForeground(Qt::black) , m_footerBackground(Qt::lightGray) , m_footerForeground(Qt::black) , m_fhFont() , m_headerFormat() , m_footerFormat() { m_folding = new Kate::TextFolding(m_doc->buffer()); m_renderer = new KateRenderer(m_doc, *m_folding, m_view); m_renderer->setPrinterFriendly(true); updateCache(); } PrintPainter::~PrintPainter() { delete m_renderer; delete m_folding; } void PrintPainter::setUseBox(const bool on) { m_useBox = on; setBoxWidth(m_boxWidth); // reset the width } void PrintPainter::setBoxWidth(const int width) { if (m_useBox) { m_boxWidth = width; if (width < 1) { m_boxWidth = 1; } } else { m_boxWidth = 0; } } void PrintPainter::setBoxColor(const QColor &color) { if (color.isValid()) { m_boxColor = color; } } void PrintPainter::setHeaderBackground(const QColor &color) { if (color.isValid()) { m_headerBackground = color; } } void PrintPainter::setHeaderForeground(const QColor &color) { if (color.isValid()) { m_headerForeground = color; } } void PrintPainter::setFooterBackground(const QColor &color) { if (color.isValid()) { m_footerBackground = color; } } void PrintPainter::setFooterForeground(const QColor &color) { if (color.isValid()) { m_footerForeground = color; } } void PrintPainter::setColorScheme(const QString &scheme) { // directly set that for the renderer m_renderer->config()->setSchema(scheme); // changed renderer requires cache updates updateCache(); } void PrintPainter::updateCache() { m_fontHeight = m_renderer->fontHeight(); // figure out the horiizontal space required QString s = QStringLiteral("%1 ").arg(m_doc->lines()); s.fill(QLatin1Char('5'), -1); // some non-fixed fonts haven't equally wide numbers // FIXME calculate which is actually the widest... m_lineNumberWidth = m_renderer->currentFontMetrics().width(s); } void PrintPainter::paint(QPrinter *printer) const { QPainter painter(printer); PageLayout pl; configure(printer, pl); uint lineCount = pl.firstline; uint y = 0; uint currentPage = (printer->fromPage() == 0) ? 1 : printer->fromPage(); bool pageStarted = true; // On to draw something :-) while (lineCount <= pl.lastline) { if (y + m_fontHeight > pl.maxHeight) { if ((int)currentPage == printer->toPage()) { // we've reached the page break of last page to be printed break; } printer->newPage(); painter.resetTransform(); currentPage++; pageStarted = true; y = 0; } if (pageStarted) { qCDebug(LOG_KTE) << "Starting new page," << lineCount << "lines up to now."; paintNewPage(painter, currentPage, y, pl); pageStarted = false; painter.translate(pl.xstart, y); } if (m_printLineNumbers /*&& ! startCol*/) { // don't repeat! paintLineNumber(painter, lineCount, pl); } uint remainder = 0; paintLine(painter, lineCount, y, remainder, pl); if (!remainder) { lineCount++; } } painter.end(); } void PrintPainter::configure(const QPrinter *printer, PageLayout &pl) const { pl.pageHeight = printer->height(); pl.pageWidth = printer->width(); pl.headerWidth = printer->width(); pl.innerMargin = m_useBox ? m_boxMargin : 6; pl.maxWidth = printer->width(); pl.maxHeight = (m_useBox ? printer->height() - pl.innerMargin : printer->height()); pl.selectionOnly = (printer->printRange() == QPrinter::Selection); pl.lastline = m_doc->lastLine(); if (m_view && pl.selectionOnly) { // set a line range from the first selected line to the last pl.selectionRange = m_view->selectionRange(); pl.firstline = pl.selectionRange.start().line(); pl.lastline = pl.selectionRange.end().line(); } if (m_printLineNumbers) { // a small space between the line numbers and the text int _adj = m_renderer->currentFontMetrics().width(QStringLiteral("5")); // adjust available width and set horizontal start point for data pl.maxWidth -= m_lineNumberWidth + _adj; pl.xstart += m_lineNumberWidth + _adj; } if (m_useHeader || m_useFooter) { // Set up a tag map // This retrieves all tags, ued or not, but // none of theese operations should be expensive, // and searcing each tag in the format strings is avoided. QDateTime dt = QDateTime::currentDateTime(); QMap tags; KUser u(KUser::UseRealUserID); tags[QStringLiteral("u")] = u.loginName(); tags[QStringLiteral("d")] = QLocale().toString(dt, QLocale::ShortFormat); tags[QStringLiteral("D")] = QLocale().toString(dt, QLocale::LongFormat); tags[QStringLiteral("h")] = QLocale().toString(dt.time(), QLocale::ShortFormat); tags[QStringLiteral("y")] = QLocale().toString(dt.date(), QLocale::ShortFormat); tags[QStringLiteral("Y")] = QLocale().toString(dt.date(), QLocale::LongFormat); tags[QStringLiteral("f")] = m_doc->url().fileName(); tags[QStringLiteral("U")] = m_doc->url().toString(); if (pl.selectionOnly) { QString s(i18n("(Selection of) ")); tags[QStringLiteral("f")].prepend(s); tags[QStringLiteral("U")].prepend(s); } - QRegExp reTags(QStringLiteral("%([dDfUhuyY])")); // TODO tjeck for "%%" + QRegularExpression reTags(QStringLiteral("%([dDfUhuyY])")); // TODO check for "%%" if (m_useHeader) { pl.headerHeight = QFontMetrics(m_fhFont).height(); if (m_useBox || m_useHeaderBackground) { pl.headerHeight += pl.innerMargin * 2; } else { pl.headerHeight += 1 + QFontMetrics(m_fhFont).leading(); } pl.headerTagList = m_headerFormat; QMutableStringListIterator it(pl.headerTagList); while (it.hasNext()) { QString tag = it.next(); - int pos = reTags.indexIn(tag); + QRegularExpressionMatch match; + int pos = tag.indexOf(reTags, 0, &match); QString rep; while (pos > -1) { - rep = tags[reTags.cap(1)]; + rep = tags[match.captured(1)]; tag.replace((uint)pos, 2, rep); pos += rep.length(); - pos = reTags.indexIn(tag, pos); + pos = tag.indexOf(reTags, pos, &match); } it.setValue(tag); } } if (m_useFooter) { pl.footerHeight = QFontMetrics(m_fhFont).height(); if (m_useBox || m_useFooterBackground) { pl.footerHeight += 2 * pl.innerMargin; } else { pl.footerHeight += 1; // line only } pl.footerTagList = m_footerFormat; QMutableStringListIterator it(pl.footerTagList); while (it.hasNext()) { QString tag = it.next(); - int pos = reTags.indexIn(tag); + QRegularExpressionMatch match; + int pos = tag.indexOf(reTags, 0, &match); QString rep; while (pos > -1) { - rep = tags[reTags.cap(1)]; + rep = tags[match.captured(1)]; tag.replace((uint)pos, 2, rep); pos += rep.length(); - pos = reTags.indexIn(tag, pos); + pos = tag.indexOf(reTags, pos, &match); } it.setValue(tag); } // adjust maxheight, so we can know when/where to print footer pl.maxHeight -= pl.footerHeight; } } // if ( useHeader || useFooter ) if (m_useBackground) { if (!m_useBox) { pl.xstart += pl.innerMargin; pl.maxWidth -= pl.innerMargin * 2; } } if (m_useBox) { // set maxwidth to something sensible pl.maxWidth -= (m_boxWidth + pl.innerMargin) * 2; pl.xstart += m_boxWidth + pl.innerMargin; // maxheight too.. pl.maxHeight -= m_boxWidth; } int pageHeight = pl.maxHeight; if (m_useHeader) { pageHeight -= pl.headerHeight + pl.innerMargin; } if (m_useFooter) { pageHeight -= pl.innerMargin; } const int linesPerPage = pageHeight / m_fontHeight; if (printer->fromPage() > 0) { pl.firstline = (printer->fromPage() - 1) * linesPerPage; } // now that we know the vertical amount of space needed, // it is possible to calculate the total number of pages // if needed, that is if any header/footer tag contains "%P". if (!pl.headerTagList.filter(QStringLiteral("%P")).isEmpty() || !pl.footerTagList.filter(QStringLiteral("%P")).isEmpty()) { qCDebug(LOG_KTE) << "'%P' found! calculating number of pages..."; // calculate total layouted lines in the document int totalLines = 0; // TODO: right now ignores selection printing for (unsigned int i = pl.firstline; i <= pl.lastline; ++i) { KateLineLayoutPtr rangeptr(new KateLineLayout(*m_renderer)); rangeptr->setLine(i); m_renderer->layoutLine(rangeptr, (int)pl.maxWidth, false); totalLines += rangeptr->viewLineCount(); } const int totalPages = (totalLines / linesPerPage) + ((totalLines % linesPerPage) > 0 ? 1 : 0); // TODO: add space for guide if required // if ( useGuide ) // _lt += (guideHeight + (fontHeight /2)) / fontHeight; // substitute both tag lists QString re(QStringLiteral("%P")); QStringList::Iterator it; for (it = pl.headerTagList.begin(); it != pl.headerTagList.end(); ++it) { it->replace(re, QString::number(totalPages)); } for (it = pl.footerTagList.begin(); it != pl.footerTagList.end(); ++it) { (*it).replace(re, QString::number(totalPages)); } } } void PrintPainter::paintNewPage(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const { if (m_useHeader) { paintHeader(painter, currentPage, y, pl); } if (m_useFooter) { paintFooter(painter, currentPage, pl); } if (m_useBackground) { paintBackground(painter, y, pl); } if (m_useBox) { paintBox(painter, y, pl); } if (m_printGuide && currentPage == 1) { paintGuide(painter, y, pl); } } void PrintPainter::paintHeader(QPainter &painter, const uint currentPage, uint &y, const PageLayout &pl) const { painter.save(); painter.setPen(m_headerForeground); painter.setFont(m_fhFont); if (m_useHeaderBackground) { painter.fillRect(0, 0, pl.headerWidth, pl.headerHeight, m_headerBackground); } if (pl.headerTagList.count() == 3) { int valign = (m_useBox || m_useHeaderBackground || m_useBackground) ? Qt::AlignVCenter : Qt::AlignTop; int align = valign | Qt::AlignLeft; int marg = (m_useBox || m_useHeaderBackground) ? pl.innerMargin : 0; if (m_useBox) { marg += m_boxWidth; } QString s; for (int i = 0; i < 3; i++) { s = pl.headerTagList[i]; if (s.indexOf(QLatin1String("%p")) != -1) { s.replace(QLatin1String("%p"), QString::number(currentPage)); } painter.drawText(marg, 0, pl.headerWidth - (marg * 2), pl.headerHeight, align, s); align = valign | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); } } if (!(m_useHeaderBackground || m_useBox || m_useBackground)) { // draw a 1 px (!?) line to separate header from contents painter.drawLine(0, pl.headerHeight - 1, pl.headerWidth, pl.headerHeight - 1); //y += 1; now included in headerHeight } painter.restore(); y += pl.headerHeight + pl.innerMargin; } void PrintPainter::paintFooter(QPainter &painter, const uint currentPage, const PageLayout &pl) const { painter.save(); painter.setPen(m_footerForeground); if (!(m_useFooterBackground || m_useBox || m_useBackground)) {// draw a 1 px (!?) line to separate footer from contents painter.drawLine(0, pl.maxHeight + pl.innerMargin - 1, pl.headerWidth, pl.maxHeight + pl.innerMargin - 1); } if (m_useFooterBackground) { painter.fillRect(0, pl.maxHeight + pl.innerMargin + m_boxWidth, pl.headerWidth, pl.footerHeight, m_footerBackground); } if (pl.footerTagList.count() == 3) { int align = Qt::AlignVCenter | Qt::AlignLeft; int marg = (m_useBox || m_useFooterBackground) ? pl.innerMargin : 0; if (m_useBox) { marg += m_boxWidth; } QString s; for (int i = 0; i < 3; i++) { s = pl.footerTagList[i]; if (s.indexOf(QLatin1String("%p")) != -1) { s.replace(QLatin1String("%p"), QString::number(currentPage)); } painter.drawText(marg, pl.maxHeight + pl.innerMargin, pl.headerWidth - (marg * 2), pl.footerHeight, align, s); align = Qt::AlignVCenter | (i == 0 ? Qt::AlignHCenter : Qt::AlignRight); } } painter.restore(); } void PrintPainter::paintGuide(QPainter &painter, uint &y, const PageLayout &pl) const { // FIXME - this may span more pages... // draw a box unless we have boxes, in which case we end with a box line int _ystart = y; QString _hlName = m_doc->highlight()->name(); QList _attributes; // list of highlight attributes for the legend m_doc->highlight()->getKateExtendedAttributeList(m_renderer->config()->schema(), _attributes); KateAttributeList _defaultAttributes; KateHlManager::self()->getDefaults(m_renderer->config()->schema(), _defaultAttributes); QColor _defaultPen = _defaultAttributes.at(0)->foreground().color(); painter.save(); painter.setPen(_defaultPen); int _marg = 0; if (m_useBox) { _marg += (2 * m_boxWidth) + (2 * pl.innerMargin); } else { if (m_useBackground) { _marg += 2 * pl.innerMargin; } _marg += 1; y += 1 + pl.innerMargin; } // draw a title string QFont _titleFont = m_renderer->config()->font(); _titleFont.setBold(true); painter.setFont(_titleFont); QRect _r; painter.drawText(QRect(_marg, y, pl.pageWidth - (2 * _marg), pl.maxHeight - y), Qt::AlignTop | Qt::AlignHCenter, i18n("Typographical Conventions for %1", _hlName), &_r); const int _w = pl.pageWidth - (_marg * 2) - (pl.innerMargin * 2); const int _x = _marg + pl.innerMargin; y += _r.height() + pl.innerMargin; painter.drawLine(_x, y, _x + _w, y); y += 1 + pl.innerMargin; int _widest(0); foreach (const KTextEditor::Attribute::Ptr &attribute, _attributes) { _widest = qMax(QFontMetrics(attribute->font()).width(attribute->name().section(QLatin1Char(':'), 1, 1)), _widest); } const int _guideCols = _w / (_widest + pl.innerMargin); // draw attrib names using their styles const int _cw = _w / _guideCols; int _i = 0; _titleFont.setUnderline(true); QString _currentHlName; foreach (const KTextEditor::Attribute::Ptr &attribute, _attributes) { QString _hl = attribute->name().section(QLatin1Char(':'), 0, 0); QString _name = attribute->name().section(QLatin1Char(':'), 1, 1); if (_hl != _hlName && _hl != _currentHlName) { _currentHlName = _hl; if (_i % _guideCols) { y += m_fontHeight; } y += pl.innerMargin; painter.setFont(_titleFont); painter.setPen(_defaultPen); painter.drawText(_x, y, _w, m_fontHeight, Qt::AlignTop, _hl + QLatin1Char(' ') + i18n("text")); y += m_fontHeight; _i = 0; } KTextEditor::Attribute _attr = *_defaultAttributes[attribute->defaultStyle()]; _attr += *attribute; painter.setPen(_attr.foreground().color()); painter.setFont(_attr.font()); if (_attr.hasProperty(QTextFormat::BackgroundBrush)) { QRect _rect = QFontMetrics(_attr.font()).boundingRect(_name); _rect.moveTo(_x + ((_i % _guideCols)*_cw), y); painter.fillRect(_rect, _attr.background()); } painter.drawText((_x + ((_i % _guideCols) * _cw)), y, _cw, m_fontHeight, Qt::AlignTop, _name); _i++; if (_i && !(_i % _guideCols)) { y += m_fontHeight; } } if (_i % _guideCols) { y += m_fontHeight;// last row not full } // draw a box around the legend painter.setPen(_defaultPen); if (m_useBox) { painter.fillRect(0, y + pl.innerMargin, pl.headerWidth, m_boxWidth, m_boxColor); } else { _marg -= 1; painter.drawRect(_marg, _ystart, pl.pageWidth - (2 * _marg), y - _ystart + pl.innerMargin); } painter.restore(); y += (m_useBox ? m_boxWidth : 1) + (pl.innerMargin * 2); } void PrintPainter::paintBox(QPainter &painter, uint &y, const PageLayout &pl) const { painter.save(); painter.setPen(QPen(m_boxColor, m_boxWidth)); painter.drawRect(0, 0, pl.pageWidth, pl.pageHeight); if (m_useHeader) { painter.drawLine(0, pl.headerHeight, pl.headerWidth, pl.headerHeight); } else { y += pl.innerMargin; } if (m_useFooter) { // drawline is not trustable, grr. painter.fillRect(0, pl.maxHeight + pl.innerMargin, pl.headerWidth, m_boxWidth, m_boxColor); } painter.restore(); } void PrintPainter::paintBackground(QPainter &painter, const uint y, const PageLayout &pl) const { // If we have a box, or the header/footer has backgrounds, we want to paint // to the border of those. Otherwise just the contents area. int _y = y, _h = pl.maxHeight - y; if (m_useBox) { _y -= pl.innerMargin; _h += 2 * pl.innerMargin; } else { if (m_useHeaderBackground) { _y -= pl.innerMargin; _h += pl.innerMargin; } if (m_useFooterBackground) { _h += pl.innerMargin; } } painter.fillRect(0, _y, pl.pageWidth, _h, m_renderer->config()->backgroundColor()); } void PrintPainter::paintLine(QPainter &painter, const uint line, uint &y, uint &remainder, const PageLayout &pl) const { // HA! this is where we print [part of] a line ;]] KateLineLayoutPtr rangeptr(new KateLineLayout(*m_renderer)); rangeptr->setLine(line); m_renderer->layoutLine(rangeptr, (int)pl.maxWidth, false); // selectionOnly: clip non-selection parts and adjust painter position if needed int _xadjust = 0; if (pl.selectionOnly) { if (m_view && m_view->blockSelection()) { int _x = m_renderer->cursorToX(rangeptr->viewLine(0), pl.selectionRange.start()); int _x1 = m_renderer->cursorToX(rangeptr->viewLine(rangeptr->viewLineCount() - 1), pl.selectionRange.end()); _xadjust = _x; painter.translate(-_xadjust, 0); painter.setClipRegion(QRegion(_x, 0, _x1 - _x, rangeptr->viewLineCount() * m_fontHeight)); } else if (line == pl.firstline || line == pl.lastline) { QRegion region(0, 0, pl.maxWidth, rangeptr->viewLineCount() * m_fontHeight); if (line == pl.firstline) { region = region.subtracted(QRegion(0, 0, m_renderer->cursorToX(rangeptr->viewLine(0), pl.selectionRange.start()), m_fontHeight)); } if (line == pl.lastline) { int _x = m_renderer->cursorToX(rangeptr->viewLine(rangeptr->viewLineCount() - 1), pl.selectionRange.end()); region = region.subtracted(QRegion(_x, 0, pl.maxWidth - _x, m_fontHeight)); } painter.setClipRegion(region); } } // If the line is too long (too many 'viewlines') to fit the remaining vertical space, // clip and adjust the painter position as necessary int _lines = rangeptr->viewLineCount(); // number of "sublines" to paint. int proceedLines = _lines; if (remainder) { proceedLines = qMin((pl.maxHeight - y) / m_fontHeight, remainder); painter.translate(0, -(_lines - int(remainder)) * m_fontHeight + 1); painter.setClipRect(0, (_lines - int(remainder)) * m_fontHeight + 1, pl.maxWidth, proceedLines * m_fontHeight); //### drop the crosspatch in printerfriendly mode??? remainder -= proceedLines; } else if (y + m_fontHeight * _lines > pl.maxHeight) { remainder = _lines - ((pl.maxHeight - y) / m_fontHeight); painter.setClipRect(0, 0, pl.maxWidth, (_lines - int(remainder)) * m_fontHeight + 1); //### drop the crosspatch in printerfriendly mode??? } m_renderer->paintTextLine(painter, rangeptr, 0, (int)pl.maxWidth); painter.setClipping(false); painter.translate(_xadjust, (m_fontHeight * (_lines - remainder))); y += m_fontHeight * proceedLines; } void PrintPainter::paintLineNumber(QPainter &painter, const uint number, const PageLayout &pl) const { const int left = ((m_useBox || m_useBackground) ? pl.innerMargin : 0) - pl.xstart; painter.save(); painter.setFont(m_renderer->config()->font()); painter.setPen(m_renderer->config()->lineNumberColor()); painter.drawText(left, 0, m_lineNumberWidth, m_fontHeight, Qt::AlignRight, QString::number(number + 1)); painter.restore(); } //END PrintPainter diff --git a/src/script/katescript.cpp b/src/script/katescript.cpp index 56d90013..b8fa16ad 100644 --- a/src/script/katescript.cpp +++ b/src/script/katescript.cpp @@ -1,259 +1,255 @@ // This file is part of the KDE libraries // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2009, 2010 Dominik Haumann // Copyright (C) 2010 Joseph Wenninger // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) version 3. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this library; see the file COPYING.LIB. If not, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "katescript.h" #include "katescriptdocument.h" #include "katescriptview.h" #include "katescripthelpers.h" #include "kateview.h" #include "katedocument.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include -//BEGIN conversion functions for Cursors and Ranges - -//END - KateScript::KateScript(const QString &urlOrScript, enum InputType inputType) : m_loaded(false) , m_loadSuccessful(false) , m_url(inputType == InputURL ? urlOrScript : QString()) , m_engine(nullptr) , m_document(nullptr) , m_view(nullptr) , m_inputType(inputType) , m_script(inputType == InputSCRIPT ? urlOrScript : QString()) { } KateScript::~KateScript() { if (m_loadSuccessful) { // remove data... delete m_document; delete m_view; delete m_engine; } } QString KateScript::backtrace(const QJSValue &error, const QString &header) { QString bt; if (!header.isNull()) { bt += header + QLatin1String(":\n"); } if (error.isError()) { bt += error.toString() + QLatin1Char('\n'); } return bt; } void KateScript::displayBacktrace(const QJSValue &error, const QString &header) { if (!m_engine) { std::cerr << "KateScript::displayBacktrace: no engine, cannot display error\n"; return; } std::cerr << "\033[31m" << qPrintable(backtrace(error, header)) << "\033[0m" << '\n'; } void KateScript::clearExceptions() { if (!load()) { return; } } QJSValue KateScript::global(const QString &name) { // load the script if necessary if (!load()) { return QJSValue::UndefinedValue; } return m_engine->globalObject().property(name); } QJSValue KateScript::function(const QString &name) { QJSValue value = global(name); if (!value.isCallable()) { return QJSValue::UndefinedValue; } return value; } bool KateScript::load() { if (m_loaded) { return m_loadSuccessful; } m_loaded = true; m_loadSuccessful = false; // here set to false, and at end of function to true // read the script file into memory QString source; if (m_inputType == InputURL) { if (!Kate::Script::readFile(m_url, source)) { return false; } } else { source = m_script; } // create script engine, register meta types m_engine = new QJSEngine(); // export read & require function and add the require guard object QJSValue functions = m_engine->newQObject(new Kate::ScriptHelper(m_engine)); m_engine->globalObject().setProperty(QStringLiteral("functions"), functions); m_engine->globalObject().setProperty(QStringLiteral("read"), functions.property(QStringLiteral("read"))); m_engine->globalObject().setProperty(QStringLiteral("require"), functions.property(QStringLiteral("require"))); m_engine->globalObject().setProperty(QStringLiteral("require_guard"), m_engine->newObject()); // export debug function m_engine->globalObject().setProperty(QStringLiteral("debug"), functions.property(QStringLiteral("debug"))); // export translation functions m_engine->globalObject().setProperty(QStringLiteral("i18n"), functions.property(QStringLiteral("_i18n"))); m_engine->globalObject().setProperty(QStringLiteral("i18nc"), functions.property(QStringLiteral("_i18nc"))); m_engine->globalObject().setProperty(QStringLiteral("i18np"), functions.property(QStringLiteral("_i18np"))); m_engine->globalObject().setProperty(QStringLiteral("i18ncp"), functions.property(QStringLiteral("_i18ncp"))); // register default styles as ds* global properties m_engine->globalObject().setProperty(QStringLiteral("dsNormal"), KTextEditor::dsNormal); m_engine->globalObject().setProperty(QStringLiteral("dsKeyword"), KTextEditor::dsKeyword); m_engine->globalObject().setProperty(QStringLiteral("dsFunction"), KTextEditor::dsFunction); m_engine->globalObject().setProperty(QStringLiteral("dsVariable"), KTextEditor::dsVariable); m_engine->globalObject().setProperty(QStringLiteral("dsControlFlow"), KTextEditor::dsControlFlow); m_engine->globalObject().setProperty(QStringLiteral("dsOperator"), KTextEditor::dsOperator); m_engine->globalObject().setProperty(QStringLiteral("dsBuiltIn"), KTextEditor::dsBuiltIn); m_engine->globalObject().setProperty(QStringLiteral("dsExtension"), KTextEditor::dsExtension); m_engine->globalObject().setProperty(QStringLiteral("dsPreprocessor"), KTextEditor::dsPreprocessor); m_engine->globalObject().setProperty(QStringLiteral("dsAttribute"), KTextEditor::dsAttribute); m_engine->globalObject().setProperty(QStringLiteral("dsChar"), KTextEditor::dsChar); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialChar"), KTextEditor::dsSpecialChar); m_engine->globalObject().setProperty(QStringLiteral("dsString"), KTextEditor::dsString); m_engine->globalObject().setProperty(QStringLiteral("dsVerbatimString"), KTextEditor::dsVerbatimString); m_engine->globalObject().setProperty(QStringLiteral("dsSpecialString"), KTextEditor::dsSpecialString); m_engine->globalObject().setProperty(QStringLiteral("dsImport"), KTextEditor::dsImport); m_engine->globalObject().setProperty(QStringLiteral("dsDataType"), KTextEditor::dsDataType); m_engine->globalObject().setProperty(QStringLiteral("dsDecVal"), KTextEditor::dsDecVal); m_engine->globalObject().setProperty(QStringLiteral("dsBaseN"), KTextEditor::dsBaseN); m_engine->globalObject().setProperty(QStringLiteral("dsFloat"), KTextEditor::dsFloat); m_engine->globalObject().setProperty(QStringLiteral("dsConstant"), KTextEditor::dsConstant); m_engine->globalObject().setProperty(QStringLiteral("dsComment"), KTextEditor::dsComment); m_engine->globalObject().setProperty(QStringLiteral("dsDocumentation"), KTextEditor::dsDocumentation); m_engine->globalObject().setProperty(QStringLiteral("dsAnnotation"), KTextEditor::dsAnnotation); m_engine->globalObject().setProperty(QStringLiteral("dsCommentVar"), KTextEditor::dsCommentVar); m_engine->globalObject().setProperty(QStringLiteral("dsRegionMarker"), KTextEditor::dsRegionMarker); m_engine->globalObject().setProperty(QStringLiteral("dsInformation"), KTextEditor::dsInformation); m_engine->globalObject().setProperty(QStringLiteral("dsWarning"), KTextEditor::dsWarning); m_engine->globalObject().setProperty(QStringLiteral("dsAlert"), KTextEditor::dsAlert); m_engine->globalObject().setProperty(QStringLiteral("dsOthers"), KTextEditor::dsOthers); m_engine->globalObject().setProperty(QStringLiteral("dsError"), KTextEditor::dsError); // register scripts itself QJSValue result = m_engine->evaluate(source, m_url); if (hasException(result, m_url)) { return false; } // AFTER SCRIPT: set the view/document objects as necessary m_engine->globalObject().setProperty(QStringLiteral("document"), m_engine->newQObject(m_document = new KateScriptDocument(m_engine))); m_engine->globalObject().setProperty(QStringLiteral("view"), m_engine->newQObject(m_view = new KateScriptView(m_engine))); // yip yip! m_loadSuccessful = true; return true; } QJSValue KateScript::evaluate(const QString& program, const FieldMap& env) { if ( !load() ) { qWarning() << "load of script failed:" << program; return QJSValue(); } // Wrap the arguments in a function to avoid poluting the global object QString programWithContext = QStringLiteral("function(") + QStringList(env.keys()).join(QLatin1Char(',')) + QStringLiteral(") { return ") + program + QStringLiteral("}"); QJSValue programFunction = m_engine->evaluate(programWithContext); Q_ASSERT(programFunction.isCallable()); QJSValueList args; for ( auto it = env.begin(); it != env.end(); it++ ) { args << it.value(); } QJSValue result = programFunction.call(args); if (result.isError()) qWarning() << "Error evaluating script: " << result.toString(); return result; } bool KateScript::hasException(const QJSValue &object, const QString &file) { if (object.isError()) { displayBacktrace(object, i18n("Error loading script %1\n", file)); m_errorMessage = i18n("Error loading script %1", file); delete m_engine; m_engine = nullptr; m_loadSuccessful = false; return true; } return false; } bool KateScript::setView(KTextEditor::ViewPrivate *view) { if (!load()) { return false; } // setup the stuff m_document->setDocument(view->doc()); m_view->setView(view); return true; } void KateScript::setGeneralHeader(const KateScriptHeader &generalHeader) { m_generalHeader = generalHeader; } KateScriptHeader &KateScript::generalHeader() { return m_generalHeader; } diff --git a/src/script/katescriptmanager.cpp b/src/script/katescriptmanager.cpp index 618e9f6d..5bf0ca1c 100644 --- a/src/script/katescriptmanager.cpp +++ b/src/script/katescriptmanager.cpp @@ -1,341 +1,341 @@ // This file is part of the KDE libraries // Copyright (C) 2005 Christoph Cullmann // Copyright (C) 2005 Joseph Wenninger // Copyright (C) 2006, 2009 Dominik Haumann // Copyright (C) 2008 Paul Giannaros // Copyright (C) 2010 Joseph Wenninger // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License version 2 as published by the Free Software Foundation. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public License // along with this library; see the file COPYING.LIB. If not, write to // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, // Boston, MA 02110-1301, USA. #include "katescriptmanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kateglobal.h" #include "katecmd.h" #include "katepartdebug.h" KateScriptManager *KateScriptManager::m_instance = nullptr; KateScriptManager::KateScriptManager() - : KTextEditor::Command(QStringList() << QStringLiteral("reload-scripts")) + : KTextEditor::Command({ QStringLiteral("reload-scripts") }) { // use cached info collect(); } KateScriptManager::~KateScriptManager() { qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); m_instance = nullptr; } KateIndentScript *KateScriptManager::indenter(const QString &language) { KateIndentScript *highestPriorityIndenter = nullptr; foreach (KateIndentScript *indenter, m_languageToIndenters.value(language.toLower())) { // don't overwrite if there is already a result with a higher priority if (highestPriorityIndenter && indenter->indentHeader().priority() < highestPriorityIndenter->indentHeader().priority()) { #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Not overwriting indenter for" << language << "as the priority isn't big enough (" << indenter->indentHeader().priority() << '<' << highestPriorityIndenter->indentHeader().priority() << ')'; #endif } else { highestPriorityIndenter = indenter; } } #ifdef DEBUG_SCRIPTMANAGER if (highestPriorityIndenter) { qCDebug(LOG_KTE) << "Found indenter" << highestPriorityIndenter->url() << "for" << language; } else { qCDebug(LOG_KTE) << "No indenter for" << language; } #endif return highestPriorityIndenter; } /** * Small helper: QJsonValue to QStringList */ static QStringList jsonToStringList (const QJsonValue &value) { QStringList list; Q_FOREACH (const QJsonValue &value, value.toArray()) { if (value.isString()) { list.append(value.toString()); } } return list; } void KateScriptManager::collect() { // clear out the old scripts and reserve enough space qDeleteAll(m_indentationScripts); qDeleteAll(m_commandLineScripts); m_indentationScripts.clear(); m_commandLineScripts.clear(); m_languageToIndenters.clear(); m_indentationScriptMap.clear(); /** * now, we search all kinds of known scripts */ - foreach (const QString &type, QStringList() << QLatin1String("indentation") << QLatin1String("commands")) { + for (const QString type : { QLatin1String("indentation"), QLatin1String("commands") }) { // basedir for filesystem lookup const QString basedir = QLatin1String("/katepart5/script/") + type; QStringList dirs; // first writable locations, e.g. stuff the user has provided dirs += QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + basedir; // then resources, e.g. the stuff we ship with us dirs.append(QLatin1String(":/ktexteditor/script/") + type); // then all other locations, this includes global stuff installed by other applications // this will not allow global stuff to overwrite the stuff we ship in our resources to allow to install a more up-to-date ktexteditor lib locally! foreach (const QString &dir, QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation)) { dirs.append(dir + basedir); } QStringList list; foreach (const QString &dir, dirs) { - const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.js")); + const QStringList fileNames = QDir(dir).entryList({ QStringLiteral("*.js") }); foreach (const QString &file, fileNames) { list.append(dir + QLatin1Char('/') + file); } } // iterate through the files and read info out of cache or file, no double loading of same scripts QSet unique; foreach (const QString &fileName, list) { /** * get file basename */ const QString baseName = QFileInfo(fileName).baseName(); /** * only load scripts once, even if multiple installed variants found! */ if (unique.contains(baseName)) continue; /** * remember the script */ unique.insert (baseName); /** * open file or skip it */ QFile file(fileName); if (!file.open(QIODevice::ReadOnly)) { qCDebug(LOG_KTE) << "Script parse error: Cannot open file " << qPrintable(fileName) << '\n'; continue; } /** * search json header or skip this file */ QByteArray fileContent = file.readAll(); int startOfJson = fileContent.indexOf ('{'); if (startOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find start of json header at start of file " << qPrintable(fileName) << '\n'; continue; } int endOfJson = fileContent.indexOf("\n};", startOfJson); if (endOfJson < 0) { // as fallback, check also mac os line ending endOfJson = fileContent.indexOf("\r};", startOfJson); } if (endOfJson < 0) { qCDebug(LOG_KTE) << "Script parse error: Cannot find end of json header at start of file " << qPrintable(fileName) << '\n'; continue; } endOfJson += 2; //we want the end including the } but not the ; /** * parse json header or skip this file */ QJsonParseError error; const QJsonDocument metaInfo (QJsonDocument::fromJson(fileContent.mid(startOfJson, endOfJson-startOfJson), &error)); if (error.error || !metaInfo.isObject()) { qCDebug(LOG_KTE) << "Script parse error: Cannot parse json header at start of file " << qPrintable(fileName) << error.errorString() << endOfJson << fileContent.mid(endOfJson-25, 25).replace('\n', ' '); continue; } /** * remember type */ KateScriptHeader generalHeader; if (type == QLatin1String("indentation")) { generalHeader.setScriptType(Kate::IndentationScript); } else if (type == QLatin1String("commands")) { generalHeader.setScriptType(Kate::CommandLineScript); } else { // should never happen, we dictate type by directory Q_ASSERT(false); } const QJsonObject metaInfoObject = metaInfo.object(); generalHeader.setLicense(metaInfoObject.value(QStringLiteral("license")).toString()); generalHeader.setAuthor(metaInfoObject.value(QStringLiteral("author")).toString()); generalHeader.setRevision(metaInfoObject.value(QStringLiteral("revision")).toInt()); generalHeader.setKateVersion(metaInfoObject.value(QStringLiteral("kate-version")).toString()); // now, cast accordingly based on type switch (generalHeader.scriptType()) { case Kate::IndentationScript: { KateIndentScriptHeader indentHeader; indentHeader.setName(metaInfoObject.value(QStringLiteral("name")).toString()); indentHeader.setBaseName(baseName); if (indentHeader.name().isNull()) { qCDebug(LOG_KTE) << "Script value error: No name specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping indenter" << '\n'; continue; } // required style? indentHeader.setRequiredStyle(metaInfoObject.value(QStringLiteral("required-syntax-style")).toString()); // which languages does this support? QStringList indentLanguages = jsonToStringList(metaInfoObject.value(QStringLiteral("indent-languages"))); if (!indentLanguages.isEmpty()) { indentHeader.setIndentLanguages(indentLanguages); } else { indentHeader.setIndentLanguages(QStringList() << indentHeader.name()); #ifdef DEBUG_SCRIPTMANAGER qCDebug(LOG_KTE) << "Script value warning: No indent-languages specified for indent " << "script " << qPrintable(fileName) << ". Using the name (" << qPrintable(indentHeader.name()) << ")\n"; #endif } // priority indentHeader.setPriority(metaInfoObject.value(QStringLiteral("priority")).toInt()); KateIndentScript *script = new KateIndentScript(fileName, indentHeader); script->setGeneralHeader(generalHeader); foreach (const QString &language, indentHeader.indentLanguages()) { m_languageToIndenters[language.toLower()].push_back(script); } m_indentationScriptMap.insert(indentHeader.baseName(), script); m_indentationScripts.append(script); break; } case Kate::CommandLineScript: { KateCommandLineScriptHeader commandHeader; commandHeader.setFunctions(jsonToStringList(metaInfoObject.value(QStringLiteral("functions")))); commandHeader.setActions(metaInfoObject.value(QStringLiteral("actions")).toArray()); if (commandHeader.functions().isEmpty()) { qCDebug(LOG_KTE) << "Script value error: No functions specified in script meta data: " << qPrintable(fileName) << '\n' << "-> skipping script" << '\n'; continue; } KateCommandLineScript *script = new KateCommandLineScript(fileName, commandHeader); script->setGeneralHeader(generalHeader); m_commandLineScripts.push_back(script); break; } case Kate::UnknownScript: default: qCDebug(LOG_KTE) << "Script value warning: Unknown type ('" << qPrintable(type) << "'): " << qPrintable(fileName) << '\n'; } } } #ifdef DEBUG_SCRIPTMANAGER // XX Test if (indenter("Python")) { qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("triggerCharacters").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->global("blafldsjfklas").isValid() << "\n"; qCDebug(LOG_KTE) << "Python: " << indenter("Python")->function("indent").isValid() << "\n"; } if (indenter("C")) { qCDebug(LOG_KTE) << "C: " << qPrintable(indenter("C")->url()) << "\n"; } if (indenter("lisp")) { qCDebug(LOG_KTE) << "LISP: " << qPrintable(indenter("Lisp")->url()) << "\n"; } #endif } void KateScriptManager::reload() { collect(); emit reloaded(); } /// Kate::Command stuff bool KateScriptManager::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &) { QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.first()); args.removeFirst(); if (!view) { errorMsg = i18n("Could not access view"); return false; } if (cmd == QLatin1String("reload-scripts")) { reload(); return true; } else { errorMsg = i18n("Command not found: %1", cmd); return false; } } bool KateScriptManager::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view) if (cmd == QLatin1String("reload-scripts")) { msg = i18n("Reload all JavaScript files (indenters, command line scripts, etc)."); return true; } else { msg = i18n("Command not found: %1", cmd); return false; } } diff --git a/src/syntax/katehighlightingcmds.h b/src/syntax/katehighlightingcmds.h index 94d3a0e2..a0031e98 100644 --- a/src/syntax/katehighlightingcmds.h +++ b/src/syntax/katehighlightingcmds.h @@ -1,68 +1,68 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2014 Christoph Rüßler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KATE_HIGHLIGHT_RELOAD_H #define KATE_HIGHLIGHT_RELOAD_H #include namespace KateCommands { class Highlighting : public KTextEditor::Command { Highlighting() - : KTextEditor::Command(QStringList() << QStringLiteral("reload-highlighting") << QStringLiteral("edit-highlighting")) + : KTextEditor::Command({ QStringLiteral("reload-highlighting"), QStringLiteral("edit-highlighting") }) { } static Highlighting* m_instance; public: ~Highlighting() { m_instance = nullptr; } static Highlighting *self() { if (m_instance == nullptr) { m_instance = new Highlighting(); } return m_instance; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range::invalid()) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; }; } #endif diff --git a/src/utils/katecmds.cpp b/src/utils/katecmds.cpp index 3ec29201..f671eea2 100644 --- a/src/utils/katecmds.cpp +++ b/src/utils/katecmds.cpp @@ -1,593 +1,597 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "katecmds.h" #include "katedocument.h" #include "kateview.h" #include "kateautoindent.h" #include "katetextline.h" #include "katesyntaxmanager.h" #include "katerenderer.h" #include "katecmd.h" #include "katepartdebug.h" #include -#include +#include //BEGIN CoreCommands KateCommands::CoreCommands *KateCommands::CoreCommands::m_instance = nullptr; // this returns wheather the string s could be converted to // a bool value, one of on|off|1|0|true|false. the argument val is // set to the extracted value in case of success static bool getBoolArg(const QString &t, bool *val) { bool res(false); QString s = t.toLower(); res = (s == QLatin1String("on") || s == QLatin1String("1") || s == QLatin1String("true")); if (res) { *val = true; return true; } res = (s == QLatin1String("off") || s == QLatin1String("0") || s == QLatin1String("false")); if (res) { *val = false; return true; } return false; } bool KateCommands::CoreCommands::help(KTextEditor::View *, const QString &cmd, QString &msg) { QString realcmd = cmd.trimmed(); if (realcmd == QLatin1String("indent")) { msg = i18n("

indent

" "

Indents the selected lines or the current line

"); return true; } else if (realcmd == QLatin1String("unindent")) { msg = i18n("

unindent

" "

Unindents the selected lines or current line.

"); return true; } else if (realcmd == QLatin1String("cleanindent")) { msg = i18n("

cleanindent

" "

Cleans up the indentation of the selected lines or current line according to the indentation settings in the document.

"); return true; } else if (realcmd == QLatin1String("comment")) { msg = i18n("

comment

" "

Inserts comment markers to make the selection or selected lines or current line a comment according to the text format as defined by the syntax highlight definition for the document.

"); return true; } else if (realcmd == QLatin1String("uncomment")) { msg = i18n("

uncomment

" "

Removes comment markers from the selection or selected lines or current line according to the text format as defined by the syntax highlight definition for the document.

"); return true; } else if (realcmd == QLatin1String("goto")) { msg = i18n("

goto line number

" "

This command navigates to the specified line number.

"); return true; } else if (realcmd == QLatin1String("set-indent-pasted-text")) { msg = i18n("

set-indent-pasted-text enable

" "

If enabled, indentation of text pasted from the clipboard is adjusted using the current indenter.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("kill-line")) { msg = i18n("Deletes the current line."); return true; } else if (realcmd == QLatin1String("set-tab-width")) { msg = i18n("

set-tab-width width

" "

Sets the tab width to the number width

"); return true; } else if (realcmd == QLatin1String("set-replace-tab")) { msg = i18n("

set-replace-tab enable

" "

If enabled, tabs are replaced with spaces as you type.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-show-tabs")) { msg = i18n("

set-show-tabs enable

" "

If enabled, TAB characters and trailing whitespace will be visualized by a small dot.

" "

Possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-remove-trailing-spaces")) { msg = i18n("

set-remove-trailing-spaces mode

" "

Removes the trailing spaces in the document depending on the mode.

" "

Possible values:" "

    " "
  • none: never remove trailing spaces.
  • " "
  • modified: remove trailing spaces only of modified lines.
  • " "
  • all: remove trailing spaces in the entire document.
  • " "

"); return true; } else if (realcmd == QLatin1String("set-indent-width")) { msg = i18n("

set-indent-width width

" "

Sets the indentation width to the number width. Used only if you are indenting with spaces.

"); return true; } else if (realcmd == QLatin1String("set-indent-mode")) { msg = i18n("

set-indent-mode mode

" "

The mode parameter is a value as seen in the Tools - Indentation menu

"); return true; } else if (realcmd == QLatin1String("set-auto-indent")) { msg = i18n("

set-auto-indent enable

" "

Enable or disable autoindentation.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-line-numbers")) { msg = i18n("

set-line-numbers enable

" "

Sets the visibility of the line numbers pane.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-folding-markers")) { msg = i18n("

set-folding-markers enable

" "

Sets the visibility of the folding markers pane.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-icon-border")) { msg = i18n("

set-icon-border enable

" "

Sets the visibility of the icon border.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-word-wrap")) { msg = i18n("

set-word-wrap enable

" "

Enables dynamic word wrap according to enable

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-word-wrap-column")) { msg = i18n("

set-word-wrap-column width

" "

Sets the line width for hard wrapping to width. This is used if you are having your text wrapped automatically.

"); return true; } else if (realcmd == QLatin1String("set-replace-tabs-save")) { msg = i18n("

set-replace-tabs-save enable

" "

When enabled, tabs will be replaced with whitespace whenever the document is saved.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("set-highlight")) { msg = i18n("

set-highlight highlight

" "

Sets the syntax highlighting system for the document. The argument must be a valid highlight name, as seen in the Tools → Highlighting menu. This command provides an autocompletion list for its argument.

"); return true; } else if (realcmd == QLatin1String("set-mode")) { msg = i18n("

set-mode mode

" "

Sets the mode as seen in Tools - Mode

"); return true; } else if (realcmd == QLatin1String("set-show-indent")) { msg = i18n("

set-show-indent enable

" "

If enabled, indentation will be visualized by a vertical dotted line.

" "

possible true values: 1 on true
" "possible false values: 0 off false

"); return true; } else if (realcmd == QLatin1String("print")) { msg = i18n("

Open the Print dialog to print the current document.

"); return true; } else { return false; } } bool KateCommands::CoreCommands::exec(KTextEditor::View *view, const QString &_cmd, QString &errorMsg, const KTextEditor::Range &range) { #define KCC_ERR(s) { errorMsg=s; return false; } // cast it hardcore, we know that it is really a kateview :) KTextEditor::ViewPrivate *v = static_cast(view); if (! v) { KCC_ERR(i18n("Could not access view")); } //create a list of args - QStringList args(_cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)); + QStringList args(_cmd.split(QRegularExpression(QLatin1String("\\s+")), QString::SkipEmptyParts)); QString cmd(args.takeFirst()); // ALL commands that takes no arguments. if (cmd == QLatin1String("indent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), 1); } v->doc()->editEnd(); } else { v->indent(); } return true; } else if (cmd == QLatin1String("unindent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), -1); } v->doc()->editEnd(); } else { v->unIndent(); } return true; } else if (cmd == QLatin1String("cleanindent")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->indent(KTextEditor::Range(line, 0, line, 0), 0); } v->doc()->editEnd(); } else { v->cleanIndent(); } return true; } else if (cmd == QLatin1String("fold")) { return (v->textFolding().newFoldingRange(range.isValid() ? range : v->selectionRange(), Kate::TextFolding::Persistent | Kate::TextFolding::Folded) != -1); } else if (cmd == QLatin1String("tfold")) { return (v->textFolding().newFoldingRange(range.isValid() ? range : v->selectionRange(), Kate::TextFolding::Folded) != -1); } else if (cmd == QLatin1String("unfold")) { QVector > startingRanges = v->textFolding().foldingRangesStartingOnLine(v->cursorPosition().line()); bool unfolded = false; for (int i = 0; i < startingRanges.size(); ++i) { if (startingRanges[i].second & Kate::TextFolding::Folded) { unfolded = v->textFolding().unfoldRange(startingRanges[i].first) || unfolded; } } return unfolded; } else if (cmd == QLatin1String("comment")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->comment(v, line, 0, 1); } v->doc()->editEnd(); } else { v->comment(); } return true; } else if (cmd == QLatin1String("uncomment")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->comment(v, line, 0, -1); } v->doc()->editEnd(); } else { v->uncomment(); } return true; } else if (cmd == QLatin1String("kill-line")) { if (range.isValid()) { v->doc()->editStart(); for (int line = range.start().line(); line <= range.end().line(); line++) { v->doc()->removeLine(range.start().line()); } v->doc()->editEnd(); } else { v->killLine(); } return true; } else if (cmd == QLatin1String("print")) { v->print(); return true; } // ALL commands that take a string argument else if (cmd == QLatin1String("set-indent-mode") || cmd == QLatin1String("set-highlight") || cmd == QLatin1String("set-mode")) { // need at least one item, otherwise args.first() crashes if (args.isEmpty()) { KCC_ERR(i18n("Missing argument. Usage: %1 ", cmd)); } if (cmd == QLatin1String("set-indent-mode")) { v->doc()->config()->setIndentationMode(args.join(QLatin1Char(' '))); v->doc()->rememberUserDidSetIndentationMode(); return true; } else if (cmd == QLatin1String("set-highlight")) { if (v->doc()->setHighlightingMode(args.join(QLatin1Char(' ')))) { static_cast(v->doc())->setDontChangeHlOnSave(); return true; } KCC_ERR(i18n("No such highlighting '%1'", args.first())); } else if (cmd == QLatin1String("set-mode")) { if (v->doc()->setMode(args.first())) { return true; } KCC_ERR(i18n("No such mode '%1'", args.first())); } } // ALL commands that takes exactly one integer argument. else if (cmd == QLatin1String("set-tab-width") || cmd == QLatin1String("set-indent-width") || cmd == QLatin1String("set-word-wrap-column") || cmd == QLatin1String("goto")) { // find a integer value > 0 if (args.isEmpty()) { KCC_ERR(i18n("Missing argument. Usage: %1 ", cmd)); } bool ok; int val(args.first().toInt(&ok, 10)); // use base 10 even if the string starts with '0' if (!ok) KCC_ERR(i18n("Failed to convert argument '%1' to integer.", args.first())); if (cmd == QLatin1String("set-tab-width")) { if (val < 1) { KCC_ERR(i18n("Width must be at least 1.")); } v->doc()->config()->setTabWidth(val); } else if (cmd == QLatin1String("set-indent-width")) { if (val < 1) { KCC_ERR(i18n("Width must be at least 1.")); } v->doc()->config()->setIndentationWidth(val); } else if (cmd == QLatin1String("set-word-wrap-column")) { if (val < 2) { KCC_ERR(i18n("Column must be at least 1.")); } v->doc()->setWordWrapAt(val); } else if (cmd == QLatin1String("goto")) { if (args.first().at(0) == QLatin1Char('-') || args.first().at(0) == QLatin1Char('+')) { // if the number starts with a minus or plus sign, add/subract the number val = v->cursorPosition().line() + val; } else { val--; // convert given line number to the internal representation of line numbers } // constrain cursor to the range [0, number of lines] if (val < 0) { val = 0; } else if (val > v->doc()->lines() - 1) { val = v->doc()->lines() - 1; } v->setCursorPosition(KTextEditor::Cursor(val, 0)); return true; } return true; } // ALL commands that takes 1 boolean argument. else if (cmd == QLatin1String("set-icon-border") || cmd == QLatin1String("set-folding-markers") || cmd == QLatin1String("set-indent-pasted-text") || cmd == QLatin1String("set-line-numbers") || cmd == QLatin1String("set-replace-tabs") || cmd == QLatin1String("set-show-tabs") || cmd == QLatin1String("set-word-wrap") || cmd == QLatin1String("set-wrap-cursor") || cmd == QLatin1String("set-replace-tabs-save") || cmd == QLatin1String("set-show-indent")) { if (args.isEmpty()) { KCC_ERR(i18n("Usage: %1 on|off|1|0|true|false", cmd)); } bool enable = false; KateDocumentConfig *const config = v->doc()->config(); if (getBoolArg(args.first(), &enable)) { if (cmd == QLatin1String("set-icon-border")) { v->setIconBorder(enable); } else if (cmd == QLatin1String("set-folding-markers")) { v->setFoldingMarkersOn(enable); } else if (cmd == QLatin1String("set-line-numbers")) { v->setLineNumbersOn(enable); } else if (cmd == QLatin1String("set-show-indent")) { v->renderer()->setShowIndentLines(enable); } else if (cmd == QLatin1String("set-indent-pasted-text")) { config->setIndentPastedText(enable); } else if (cmd == QLatin1String("set-replace-tabs")) { config->setReplaceTabsDyn(enable); } else if (cmd == QLatin1String("set-show-tabs")) { config->setShowTabs(enable); } else if (cmd == QLatin1String("set-show-trailing-spaces")) { config->setShowSpaces(enable); } else if (cmd == QLatin1String("set-word-wrap")) { v->doc()->setWordWrap(enable); } return true; } else KCC_ERR(i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false", args.first(), cmd)); } else if (cmd == QLatin1String("set-remove-trailing-spaces")) { // need at least one item, otherwise args.first() crashes if (args.count() != 1) { KCC_ERR(i18n("Usage: set-remove-trailing-spaces 0|-|none or 1|+|mod|modified or 2|*|all")); } QString tmp = args.first().toLower().trimmed(); if (tmp == QLatin1String("1") || tmp == QLatin1String("modified") || tmp == QLatin1String("mod") || tmp == QLatin1String("+")) { v->doc()->config()->setRemoveSpaces(1); } else if (tmp == QLatin1String("2") || tmp == QLatin1String("all") || tmp == QLatin1String("*")) { v->doc()->config()->setRemoveSpaces(2); } else { v->doc()->config()->setRemoveSpaces(0); } } // unlikely.. KCC_ERR(i18n("Unknown command '%1'", cmd)); } bool KateCommands::CoreCommands::supportsRange(const QString &range) { static QStringList l; if (l.isEmpty()) l << QStringLiteral("indent") << QStringLiteral("unindent") << QStringLiteral("cleanindent") << QStringLiteral("comment") << QStringLiteral("uncomment") << QStringLiteral("kill-line") << QStringLiteral("fold") << QStringLiteral("tfold"); return l.contains(range); } KCompletion *KateCommands::CoreCommands::completionObject(KTextEditor::View *view, const QString &cmd) { Q_UNUSED(view) if (cmd == QLatin1String("set-highlight")) { QStringList l; for (int i = 0; i < KateHlManager::self()->highlights(); i++) { l << KateHlManager::self()->hlName(i); } KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } else if (cmd == QLatin1String("set-remove-trailing-spaces")) { QStringList l; l << QStringLiteral("none") << QStringLiteral("modified") << QStringLiteral("all"); KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } else if (cmd == QLatin1String("set-indent-mode")) { QStringList l = KateAutoIndent::listIdentifiers(); KateCmdShellCompletion *co = new KateCmdShellCompletion(); co->setItems(l); co->setIgnoreCase(true); return co; } return nullptr; } //END CoreCommands //BEGIN Character KateCommands::Character *KateCommands::Character::m_instance = nullptr; bool KateCommands::Character::help(class KTextEditor::View *, const QString &cmd, QString &msg) { if (cmd.trimmed() == QLatin1String("char")) { msg = i18n("

char identifier

" "

This command allows you to insert literal characters by their numerical identifier, in decimal, octal or hexadecimal form.

" "

Examples:

    " "
  • char 234
  • " "
  • char 0x1234
  • " "

"); return true; } return false; } bool KateCommands::Character::exec(KTextEditor::View *view, const QString &_cmd, QString &, const KTextEditor::Range &) { QString cmd = _cmd; // hex, octal, base 9+1 - QRegExp num(QLatin1String("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$")); - if (num.indexIn(cmd) == -1) { + QRegularExpression num(QLatin1String("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,5})$")); + QRegularExpressionMatch match = num.match(cmd); + if (!match.hasMatch()) { return false; } - cmd = num.cap(1); + cmd = match.captured(1); // identify the base unsigned short int number = 0; int base = 10; - if (cmd[0] == QLatin1Char('x') || cmd.startsWith(QLatin1String("0x"))) { - cmd.remove(QRegExp(QLatin1String("^0?x"))); + if (cmd.startsWith(QLatin1Char('x'))) { + cmd.remove(0, 1); + base = 16; + } else if (cmd.startsWith(QStringLiteral("0x"))) { + cmd.remove(0, 2); base = 16; } else if (cmd[0] == QLatin1Char('0')) { base = 8; } bool ok; number = cmd.toUShort(&ok, base); if (!ok || number == 0) { return false; } if (number <= 255) { char buf[2]; buf[0] = (char)number; buf[1] = 0; view->document()->insertText(view->cursorPosition(), QString::fromLatin1(buf)); } else { // do the unicode thing QChar c(number); view->document()->insertText(view->cursorPosition(), QString(&c, 1)); } return true; } //END Character //BEGIN Date KateCommands::Date *KateCommands::Date::m_instance = nullptr; bool KateCommands::Date::help(class KTextEditor::View *, const QString &cmd, QString &msg) { if (cmd.trimmed() == QLatin1String("date")) { msg = i18n("

date or date format

" "

Inserts a date/time string as defined by the specified format, or the format yyyy-MM-dd hh:mm:ss if none is specified.

" "

Possible format specifiers are:" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "
dThe day as number without a leading zero (1-31).
ddThe day as number with a leading zero (01-31).
dddThe abbreviated localized day name (e.g. 'Mon'..'Sun').
ddddThe long localized day name (e.g. 'Monday'..'Sunday').
MThe month as number without a leading zero (1-12).
MMThe month as number with a leading zero (01-12).
MMMThe abbreviated localized month name (e.g. 'Jan'..'Dec').
yyThe year as two digit number (00-99).
yyyyThe year as four digit number (1752-8000).
hThe hour without a leading zero (0..23 or 1..12 if AM/PM display).
hhThe hour with a leading zero (00..23 or 01..12 if AM/PM display).
mThe minute without a leading zero (0..59).
mmThe minute with a leading zero (00..59).
sThe second without a leading zero (0..59).
ssThe second with a leading zero (00..59).
zThe milliseconds without leading zeroes (0..999).
zzzThe milliseconds with leading zeroes (000..999).
APUse AM/PM display. AP will be replaced by either \"AM\" or \"PM\".
apUse am/pm display. ap will be replaced by either \"am\" or \"pm\".

"); return true; } return false; } bool KateCommands::Date::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &) { if (!cmd.startsWith(QLatin1String("date"))) { return false; } if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length() - 5)).length() > 0) { view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length() - 5))); } else { view->document()->insertText(view->cursorPosition(), QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"))); } return true; } //END Date diff --git a/src/utils/katecmds.h b/src/utils/katecmds.h index 03d16abc..b7000bc2 100644 --- a/src/utils/katecmds.h +++ b/src/utils/katecmds.h @@ -1,193 +1,212 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_CMDS_H__ #define __KATE_CMDS_H__ #include #include class KCompletion; /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * This KTextEditor::Command provides access to a lot of the core functionality * of kate part, settings, utilities, navigation etc. * it needs to get a kateview pointer, it will cast the kate::view pointer * hard to kateview */ class CoreCommands : public KTextEditor::Command { CoreCommands() - : KTextEditor::Command(QStringList() << QStringLiteral("indent") << QStringLiteral("unindent") << QStringLiteral("cleanindent") << QStringLiteral("fold") << QStringLiteral("tfold") << QStringLiteral("unfold") - << QStringLiteral("comment") << QStringLiteral("uncomment") << QStringLiteral("goto") << QStringLiteral("kill-line") - << QStringLiteral("set-tab-width") << QStringLiteral("set-replace-tabs") << QStringLiteral("set-show-tabs") - << QStringLiteral("set-indent-width") - << QStringLiteral("set-indent-mode") << QStringLiteral("set-auto-indent") - << QStringLiteral("set-line-numbers") << QStringLiteral("set-folding-markers") << QStringLiteral("set-icon-border") - << QStringLiteral("set-indent-pasted-text") << QStringLiteral("set-word-wrap") << QStringLiteral("set-word-wrap-column") - << QStringLiteral("set-replace-tabs-save") << QStringLiteral("set-remove-trailing-spaces") - << QStringLiteral("set-highlight") << QStringLiteral("set-mode") << QStringLiteral("set-show-indent") - << QStringLiteral("print")) + : KTextEditor::Command({ + QStringLiteral("indent") + , QStringLiteral("unindent") + , QStringLiteral("cleanindent") + , QStringLiteral("fold") + , QStringLiteral("tfold") + , QStringLiteral("unfold") + , QStringLiteral("comment") + , QStringLiteral("uncomment") + , QStringLiteral("goto") + , QStringLiteral("kill-line") + , QStringLiteral("set-tab-width") + , QStringLiteral("set-replace-tabs") + , QStringLiteral("set-show-tabs") + , QStringLiteral("set-indent-width") + , QStringLiteral("set-indent-mode") + , QStringLiteral("set-auto-indent") + , QStringLiteral("set-line-numbers") + , QStringLiteral("set-folding-markers") + , QStringLiteral("set-icon-border") + , QStringLiteral("set-indent-pasted-text") + , QStringLiteral("set-word-wrap") + , QStringLiteral("set-word-wrap-column") + , QStringLiteral("set-replace-tabs-save") + , QStringLiteral("set-remove-trailing-spaces") + , QStringLiteral("set-highlight") + , QStringLiteral("set-mode") + , QStringLiteral("set-show-indent") + , QStringLiteral("print") }) { } - + static CoreCommands *m_instance; public: ~CoreCommands() { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg); /** * execute command on given range * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @param range range to execute command on * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; bool supportsRange(const QString &range) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; /** override from KTextEditor::Command */ KCompletion *completionObject(KTextEditor::View *, const QString &) Q_DECL_OVERRIDE; static CoreCommands *self() { if (m_instance == nullptr) { m_instance = new CoreCommands(); } return m_instance; } }; /** * insert a unicode or ascii character * base 9+1: 1234 * hex: 0x1234 or x1234 * octal: 01231 * * prefixed with "char:" **/ class Character : public KTextEditor::Command { Character() - : KTextEditor::Command(QStringList() << QStringLiteral("char")) + : KTextEditor::Command({ QStringLiteral("char") }) { } static Character *m_instance; public: ~Character() { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; static Character *self() { if (m_instance == nullptr) { m_instance = new Character(); } return m_instance; } }; /** * insert the current date/time in the given format */ class Date : public KTextEditor::Command { Date() - : KTextEditor::Command(QStringList() << QStringLiteral("date")) + : KTextEditor::Command({ QStringLiteral("date") }) { } static Date *m_instance; public: ~Date() { m_instance = nullptr; } /** * execute command * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &range = KTextEditor::Range(-1, -0, -1, 0)) Q_DECL_OVERRIDE; /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE; static Date *self() { if (m_instance == nullptr) { m_instance = new Date(); } return m_instance; } }; } // namespace KateCommands #endif diff --git a/src/utils/kateglobal.cpp b/src/utils/kateglobal.cpp index b41f1452..d1ed531d 100644 --- a/src/utils/kateglobal.cpp +++ b/src/utils/kateglobal.cpp @@ -1,531 +1,527 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2009 Erlend Hamberg * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kateglobal.h" #include "config.h" #include #include "katedocument.h" #include "kateview.h" #include "katerenderer.h" #include "katecmd.h" #include "katecmds.h" #include "katesedcmd.h" #include "katehighlightingcmds.h" #include "katemodemanager.h" #include "kateschema.h" #include "kateschemaconfig.h" #include "kateconfig.h" #include "katescriptmanager.h" #include "katebuffer.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "spellcheck/spellcheck.h" #include "katepartdebug.h" #include "katedefaultcolors.h" #include "katenormalinputmodefactory.h" #include "kateviinputmodefactory.h" #include #include #include #include #include #include #include #include #include #include #include #if LIBGIT2_FOUND #include #endif -#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) // logging category for this framework, default: log stuff >= warning Q_LOGGING_CATEGORY(LOG_KTE, "org.kde.ktexteditor", QtWarningMsg) -#else -Q_LOGGING_CATEGORY(LOG_KTE, "org.kde.ktexteditor") -#endif //BEGIN unit test mode static bool kateUnitTestMode = false; void KTextEditor::EditorPrivate::enableUnitTestMode() { kateUnitTestMode = true; } bool KTextEditor::EditorPrivate::unitTestMode() { return kateUnitTestMode; } //END unit test mode KTextEditor::EditorPrivate::EditorPrivate(QPointer &staticInstance) : KTextEditor::Editor (this) , m_aboutData(QStringLiteral("katepart"), i18n("Kate Part"), QStringLiteral(KTEXTEDITOR_VERSION_STRING), i18n("Embeddable editor component"), KAboutLicense::LGPL_V2, i18n("(c) 2000-2017 The Kate Authors"), QString(), QStringLiteral("http://kate-editor.org")) , m_dummyApplication(nullptr) , m_application(&m_dummyApplication) , m_dummyMainWindow(nullptr) , m_defaultColors(new KateDefaultColors()) , m_searchHistoryModel(nullptr) , m_replaceHistoryModel(nullptr) { // remember this staticInstance = this; #if QT_VERSION < QT_VERSION_CHECK(5, 9, 1) // disable the QML JIT compiler as a protection against an unknown bug // in Qt's V4 engine which can provoke a crash in certain of our scripts. // See https://bugreports.qt.io/browse/QTBUG-63045 // https://bugs.kde.org/show_bug.cgi?id=384404 // and https://bugs.kde.org/show_bug.cgi?id=385413 qputenv("QV4_FORCE_INTERPRETER", QByteArrayLiteral("1")); qCDebug(LOG_KTE) << "QV4_FORCE_INTERPRETER set to 1"; #endif // init libgit2, we require at least 0.22 which has this function! #if LIBGIT2_FOUND git_libgit2_init(); #endif /** * register some datatypes */ qRegisterMetaType("KTextEditor::Cursor"); qRegisterMetaType("KTextEditor::Document*"); qRegisterMetaType("KTextEditor::View*"); // // fill about data // m_aboutData.addAuthor(i18n("Christoph Cullmann"), i18n("Maintainer"), QStringLiteral("cullmann@kde.org"), QStringLiteral("http://www.cullmann.io")); m_aboutData.addAuthor(i18n("Dominik Haumann"), i18n("Core Developer"), QStringLiteral("dhaumann@kde.org")); m_aboutData.addAuthor(i18n("Milian Wolff"), i18n("Core Developer"), QStringLiteral("mail@milianw.de"), QStringLiteral("http://milianw.de")); m_aboutData.addAuthor(i18n("Joseph Wenninger"), i18n("Core Developer"), QStringLiteral("jowenn@kde.org"), QStringLiteral("http://stud3.tuwien.ac.at/~e9925371")); m_aboutData.addAuthor(i18n("Erlend Hamberg"), i18n("Vi Input Mode"), QStringLiteral("ehamberg@gmail.com"), QStringLiteral("http://hamberg.no/erlend")); m_aboutData.addAuthor(i18n("Bernhard Beschow"), i18n("Developer"), QStringLiteral("bbeschow@cs.tu-berlin.de"), QStringLiteral("https://user.cs.tu-berlin.de/~bbeschow")); m_aboutData.addAuthor(i18n("Anders Lund"), i18n("Core Developer"), QStringLiteral("anders@alweb.dk"), QStringLiteral("http://www.alweb.dk")); m_aboutData.addAuthor(i18n("Michel Ludwig"), i18n("On-the-fly spell checking"), QStringLiteral("michel.ludwig@kdemail.net")); m_aboutData.addAuthor(i18n("Pascal Létourneau"), i18n("Large scale bug fixing"), QStringLiteral("pascal.letourneau@gmail.com")); m_aboutData.addAuthor(i18n("Hamish Rodda"), i18n("Core Developer"), QStringLiteral("rodda@kde.org")); m_aboutData.addAuthor(i18n("Waldo Bastian"), i18n("The cool buffersystem"), QStringLiteral("bastian@kde.org")); m_aboutData.addAuthor(i18n("Charles Samuels"), i18n("The Editing Commands"), QStringLiteral("charles@kde.org")); m_aboutData.addAuthor(i18n("Matt Newell"), i18n("Testing, ..."), QStringLiteral("newellm@proaxis.com")); m_aboutData.addAuthor(i18n("Michael Bartl"), i18n("Former Core Developer"), QStringLiteral("michael.bartl1@chello.at")); m_aboutData.addAuthor(i18n("Michael McCallum"), i18n("Core Developer"), QStringLiteral("gholam@xtra.co.nz")); m_aboutData.addAuthor(i18n("Michael Koch"), i18n("KWrite port to KParts"), QStringLiteral("koch@kde.org")); m_aboutData.addAuthor(i18n("Christian Gebauer"), QString(), QStringLiteral("gebauer@kde.org")); m_aboutData.addAuthor(i18n("Simon Hausmann"), QString(), QStringLiteral("hausmann@kde.org")); m_aboutData.addAuthor(i18n("Glen Parker"), i18n("KWrite Undo History, Kspell integration"), QStringLiteral("glenebob@nwlink.com")); m_aboutData.addAuthor(i18n("Scott Manson"), i18n("KWrite XML Syntax highlighting support"), QStringLiteral("sdmanson@alltel.net")); m_aboutData.addAuthor(i18n("John Firebaugh"), i18n("Patches and more"), QStringLiteral("jfirebaugh@kde.org")); m_aboutData.addAuthor(i18n("Andreas Kling"), i18n("Developer"), QStringLiteral("kling@impul.se")); m_aboutData.addAuthor(i18n("Mirko Stocker"), i18n("Various bugfixes"), QStringLiteral("me@misto.ch"), QStringLiteral("http://misto.ch/")); m_aboutData.addAuthor(i18n("Matthew Woehlke"), i18n("Selection, KColorScheme integration"), QStringLiteral("mw_triad@users.sourceforge.net")); m_aboutData.addAuthor(i18n("Sebastian Pipping"), i18n("Search bar back- and front-end"), QStringLiteral("webmaster@hartwork.org"), QStringLiteral("http://www.hartwork.org/")); m_aboutData.addAuthor(i18n("Jochen Wilhelmy"), i18n("Original KWrite Author"), QStringLiteral("digisnap@cs.tu-berlin.de")); m_aboutData.addAuthor(i18n("Gerald Senarclens de Grancy"), i18n("QA and Scripting"), QStringLiteral("oss@senarclens.eu"), QStringLiteral("http://find-santa.eu/")); m_aboutData.addCredit(i18n("Matteo Merli"), i18n("Highlighting for RPM Spec-Files, Perl, Diff and more"), QStringLiteral("merlim@libero.it")); m_aboutData.addCredit(i18n("Rocky Scaletta"), i18n("Highlighting for VHDL"), QStringLiteral("rocky@purdue.edu")); m_aboutData.addCredit(i18n("Yury Lebedev"), i18n("Highlighting for SQL"), QString()); m_aboutData.addCredit(i18n("Chris Ross"), i18n("Highlighting for Ferite"), QString()); m_aboutData.addCredit(i18n("Nick Roux"), i18n("Highlighting for ILERPG"), QString()); m_aboutData.addCredit(i18n("Carsten Niehaus"), i18n("Highlighting for LaTeX"), QString()); m_aboutData.addCredit(i18n("Per Wigren"), i18n("Highlighting for Makefiles, Python"), QString()); m_aboutData.addCredit(i18n("Jan Fritz"), i18n("Highlighting for Python"), QString()); m_aboutData.addCredit(i18n("Daniel Naber")); m_aboutData.addCredit(i18n("Roland Pabel"), i18n("Highlighting for Scheme"), QString()); m_aboutData.addCredit(i18n("Cristi Dumitrescu"), i18n("PHP Keyword/Datatype list"), QString()); m_aboutData.addCredit(i18n("Carsten Pfeiffer"), i18n("Very nice help"), QString()); m_aboutData.addCredit(i18n("Bruno Massa"), i18n("Highlighting for Lua"), QStringLiteral("brmassa@gmail.com")); m_aboutData.addCredit(i18n("All people who have contributed and I have forgotten to mention")); m_aboutData.setTranslator(i18nc("NAME OF TRANSLATORS", "Your names"), i18nc("EMAIL OF TRANSLATORS", "Your emails")); /** * set the new Kate mascot */ m_aboutData.setProgramLogo (QImage(QStringLiteral(":/ktexteditor/mascot.png"))); // // dir watch // m_dirWatch = new KDirWatch(); // // command manager // m_cmdManager = new KateCmd(); // // hl manager // m_hlManager = new KateHlManager(); // // mode man // m_modeManager = new KateModeManager(); // // schema man // m_schemaManager = new KateSchemaManager(); // // input mode factories // KateAbstractInputModeFactory *fact; fact = new KateNormalInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::NormalInputMode, fact); #if BUILD_VIMODE fact = new KateViInputModeFactory(); m_inputModeFactories.insert(KTextEditor::View::ViInputMode, fact); #endif // // spell check manager // m_spellCheckManager = new KateSpellCheckManager(); // config objects m_globalConfig = new KateGlobalConfig(); m_documentConfig = new KateDocumentConfig(); m_viewConfig = new KateViewConfig(); m_rendererConfig = new KateRendererConfig(); // create script manager (search scripts) m_scriptManager = KateScriptManager::self(); // // init the cmds // m_cmds.push_back(KateCommands::CoreCommands::self()); m_cmds.push_back(KateCommands::Character::self()); m_cmds.push_back(KateCommands::Date::self()); m_cmds.push_back(KateCommands::SedReplace::self()); m_cmds.push_back(KateCommands::Highlighting::self()); // global word completion model m_wordCompletionModel = new KateWordCompletionModel(this); // global keyword completion model m_keywordCompletionModel = new KateKeywordCompletionModel (this); // tap to QApplication object for color palette changes qApp->installEventFilter(this); } KTextEditor::EditorPrivate::~EditorPrivate() { delete m_globalConfig; delete m_documentConfig; delete m_viewConfig; delete m_rendererConfig; delete m_modeManager; delete m_schemaManager; delete m_dirWatch; // cu managers delete m_scriptManager; delete m_hlManager; delete m_spellCheckManager; // cu model delete m_wordCompletionModel; // delete the commands before we delete the cmd manager qDeleteAll(m_cmds); delete m_cmdManager; qDeleteAll(m_inputModeFactories); // shutdown libgit2, we require at least 0.22 which has this function! #if LIBGIT2_FOUND git_libgit2_shutdown(); #endif } KTextEditor::Document *KTextEditor::EditorPrivate::createDocument(QObject *parent) { KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate(false, false, nullptr, parent); emit documentCreated(this, doc); return doc; } //END KTextEditor::Editor config stuff void KTextEditor::EditorPrivate::configDialog(QWidget *parent) { QPointer kd = new KPageDialog(parent); kd->setWindowTitle(i18n("Configure")); kd->setFaceType(KPageDialog::List); kd->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Apply | QDialogButtonBox::Help); QList editorPages; for (int i = 0; i < configPages(); ++i) { QFrame *page = new QFrame(); KTextEditor::ConfigPage *cp = configPage(i, page); KPageWidgetItem *item = kd->addPage(page, cp->name()); item->setHeader(cp->fullName()); item->setIcon(cp->icon()); QVBoxLayout *topLayout = new QVBoxLayout(page); topLayout->setMargin(0); connect(kd->button(QDialogButtonBox::Apply), SIGNAL(clicked()), cp, SLOT(apply())); topLayout->addWidget(cp); editorPages.append(cp); } if (kd->exec() && kd) { KateGlobalConfig::global()->configStart(); KateDocumentConfig::global()->configStart(); KateViewConfig::global()->configStart(); KateRendererConfig::global()->configStart(); for (int i = 0; i < editorPages.count(); ++i) { editorPages.at(i)->apply(); } KateGlobalConfig::global()->configEnd(); KateDocumentConfig::global()->configEnd(); KateViewConfig::global()->configEnd(); KateRendererConfig::global()->configEnd(); } delete kd; } int KTextEditor::EditorPrivate::configPages() const { return 4; } KTextEditor::ConfigPage *KTextEditor::EditorPrivate::configPage(int number, QWidget *parent) { switch (number) { case 0: return new KateViewDefaultsConfig(parent); case 1: return new KateSchemaConfigPage(parent); case 2: return new KateEditConfigTab(parent); case 3: return new KateSaveConfigTab(parent); default: break; } return nullptr; } /** * Cleanup the KTextEditor::EditorPrivate during QCoreApplication shutdown */ static void cleanupGlobal() { /** * delete if there */ delete KTextEditor::EditorPrivate::self(); } KTextEditor::EditorPrivate *KTextEditor::EditorPrivate::self() { /** * remember the static instance in a QPointer */ static bool inited = false; static QPointer staticInstance; /** * just return it, if already inited */ if (inited) { return staticInstance.data(); } /** * start init process */ inited = true; /** * now create the object and store it */ new KTextEditor::EditorPrivate(staticInstance); /** * register cleanup * let use be deleted during QCoreApplication shutdown */ qAddPostRoutine(cleanupGlobal); /** * return instance */ return staticInstance.data(); } void KTextEditor::EditorPrivate::registerDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (!m_documents.contains(doc)); m_documents.insert(doc, doc); } void KTextEditor::EditorPrivate::deregisterDocument(KTextEditor::DocumentPrivate *doc) { Q_ASSERT (m_documents.contains(doc)); m_documents.remove(doc); } void KTextEditor::EditorPrivate::registerView(KTextEditor::ViewPrivate *view) { Q_ASSERT (!m_views.contains(view)); m_views.insert(view); } void KTextEditor::EditorPrivate::deregisterView(KTextEditor::ViewPrivate *view) { Q_ASSERT (m_views.contains(view)); m_views.remove(view); } KTextEditor::Command *KTextEditor::EditorPrivate::queryCommand(const QString &cmd) const { return m_cmdManager->queryCommand(cmd); } QList KTextEditor::EditorPrivate::commands() const { return m_cmdManager->commands(); } QStringList KTextEditor::EditorPrivate::commandList() const { return m_cmdManager->commandList(); } void KTextEditor::EditorPrivate::updateColorPalette() { // update default color cache m_defaultColors.reset(new KateDefaultColors()); // reload the global schema (triggers reload for every view as well) m_rendererConfig->reloadSchema(); // force full update of all view caches and colors m_rendererConfig->updateConfig(); } void KTextEditor::EditorPrivate::updateClipboardHistory(const QVector &text) { /** * empty => nop */ if (text.isEmpty()) { return; } /** * remember in history * cut after 10 entries */ m_clipboardHistory.prepend(text); if (m_clipboardHistory.size() > 10) { m_clipboardHistory.removeLast(); } /** * notify about change */ emit clipboardHistoryChanged(); } bool KTextEditor::EditorPrivate::eventFilter(QObject *obj, QEvent *event) { if (obj == qApp && event->type() == QEvent::ApplicationPaletteChange) { // only update the color once for the event that belongs to the qApp updateColorPalette(); } return false; // always continue processing } QList< KateAbstractInputModeFactory *> KTextEditor::EditorPrivate::inputModeFactories() { return m_inputModeFactories.values(); } QStringListModel *KTextEditor::EditorPrivate::searchHistoryModel() { if (!m_searchHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Search History"), QStringList()); m_searchHistoryModel = new QStringListModel(history, this); } return m_searchHistoryModel; } QStringListModel *KTextEditor::EditorPrivate::replaceHistoryModel() { if (!m_replaceHistoryModel) { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); const QStringList history = cg.readEntry(QStringLiteral("Replace History"), QStringList()); m_replaceHistoryModel = new QStringListModel(history, this); } return m_replaceHistoryModel; } void KTextEditor::EditorPrivate::saveSearchReplaceHistoryModels() { KConfigGroup cg(KSharedConfig::openConfig(), "KTextEditor::Search"); if (m_searchHistoryModel) { cg.writeEntry(QStringLiteral("Search History"), m_searchHistoryModel->stringList()); } if (m_replaceHistoryModel) { cg.writeEntry(QStringLiteral("Replace History"), m_replaceHistoryModel->stringList()); } } diff --git a/src/utils/katesedcmd.h b/src/utils/katesedcmd.h index af6dd8e7..a2075178 100644 --- a/src/utils/katesedcmd.h +++ b/src/utils/katesedcmd.h @@ -1,145 +1,145 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2003-2005 Anders Lund * Copyright (C) 2001-2010 Christoph Cullmann * Copyright (C) 2001 Charles Samuels * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef __KATE_SED_CMD_H__ #define __KATE_SED_CMD_H__ #include #include "kateregexpsearch.h" #include #include namespace KTextEditor { class DocumentPrivate; class ViewPrivate; } /** * The KateCommands namespace collects subclasses of KTextEditor::Command * for specific use in kate. */ namespace KateCommands { /** * Support vim/sed style search and replace * @author Charles Samuels **/ class SedReplace : public KTextEditor::Command { static SedReplace *m_instance; protected: - SedReplace() : KTextEditor::Command(QStringList() << QStringLiteral("s") << QStringLiteral("%s") << QStringLiteral("$s")) + SedReplace() : KTextEditor::Command({ QStringLiteral("s"), QStringLiteral("%s"), QStringLiteral("$s") }) { } public: ~SedReplace() { m_instance = nullptr; } /** * Execute command. Valid command strings are: * - s/search/replace/ find @c search, replace it with @c replace * on this line * - \%s/search/replace/ do the same to the whole file * - s/search/replace/i do the search and replace case insensitively * - $s/search/replace/ do the search are replacement to the * selection only * * @note $s/// is currently unsupported * @param view view to use for execution * @param cmd cmd string * @param errorMsg error to return if no success * @return success */ bool exec(class KTextEditor::View *view, const QString &cmd, QString &errorMsg, const KTextEditor::Range &r) Q_DECL_OVERRIDE; bool supportsRange(const QString &) Q_DECL_OVERRIDE { return true; } /** This command does not have help. @see KTextEditor::Command::help */ bool help(class KTextEditor::View *, const QString &, QString &) Q_DECL_OVERRIDE { return false; } static SedReplace *self() { if (m_instance == nullptr) { m_instance = new SedReplace(); } return m_instance; } /** * Parses @c sedReplaceString to see if it is a valid sed replace expression (e.g. "s/find/replace/gi"). * If it is, returns true and sets @c delimiter to the delimiter used in the expression; * @c destFindBeginPos and @c destFindEndPos to the positions in * @c sedReplaceString of the * begin and end of the "find" term; and @c destReplaceBeginPos and @c destReplaceEndPos to the begin * and end positions of the "replace" term. */ static bool parse(const QString &sedReplaceString, QString &destDelim, int &destFindBeginPos, int &destFindEndPos, int &destReplaceBeginPos, int &destReplaceEndPos); class InteractiveSedReplacer { public: InteractiveSedReplacer(KTextEditor::DocumentPrivate *doc, const QString &findPattern, const QString &replacePattern, bool caseSensitive, bool onlyOnePerLine, int startLine, int endLine); /** * Will return invalid Range if there are no further matches. */ KTextEditor::Range currentMatch(); void skipCurrentMatch(); void replaceCurrentMatch(); void replaceAllRemaining(); QString currentMatchReplacementConfirmationMessage(); QString finalStatusReportMessage(); private: const QString m_findPattern; const QString m_replacePattern; bool m_onlyOnePerLine; int m_endLine; KTextEditor::DocumentPrivate *m_doc; KateRegExpSearch m_regExpSearch; int m_numReplacementsDone; int m_numLinesTouched; int m_lastChangedLineNum; KTextEditor::Cursor m_currentSearchPos; const QVector fullCurrentMatch(); QString replacementTextForCurrentMatch(); }; protected: virtual bool interactiveSedReplace(KTextEditor::ViewPrivate *kateView, QSharedPointer interactiveSedReplace); }; } // namespace KateCommands #endif diff --git a/src/variableeditor/variablelineedit.cpp b/src/variableeditor/variablelineedit.cpp index 264e277b..30c8e04a 100644 --- a/src/variableeditor/variablelineedit.cpp +++ b/src/variableeditor/variablelineedit.cpp @@ -1,377 +1,377 @@ /* This file is part of the KDE project Copyright (C) 2011 Dominik Haumann Copyright (C) 2013 Gerald Senarclens de Grancy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "variablelineedit.h" #include "variableitem.h" #include "variablelistview.h" #include "kateautoindent.h" #include "katesyntaxmanager.h" #include "kateschema.h" #include "kateview.h" #include "katedocument.h" #include "kateglobal.h" #include "katerenderer.h" #include #include #include #include #include #include #include #include #include #include VariableLineEdit::VariableLineEdit(QWidget *parent) : QWidget(parent) { m_listview = nullptr; QHBoxLayout *hl = new QHBoxLayout(); hl->setMargin(0); hl->setSpacing(QApplication::style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); setLayout(hl); m_lineedit = new QLineEdit(this); m_button = new QToolButton(this); m_button->setIcon(QIcon::fromTheme(QStringLiteral("tools-wizard"))); m_button->setToolTip(i18n("Show list of valid variables.")); hl->addWidget(m_lineedit); hl->addWidget(m_button); m_popup = new QFrame(nullptr, Qt::Popup); m_popup->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); QVBoxLayout *l = new QVBoxLayout(m_popup); l->setSpacing(0); l->setMargin(0); m_popup->setLayout(l); // forward text changed signal connect(m_lineedit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString))); // open popup on button click connect(m_button, SIGNAL(clicked()), this, SLOT(editVariables())); } VariableLineEdit::~VariableLineEdit() { } void VariableLineEdit::editVariables() { m_listview = new VariableListView(m_lineedit->text(), m_popup); addKateItems(m_listview); connect(m_listview, SIGNAL(aboutToHide()), this, SLOT(updateVariableLine())); m_popup->layout()->addWidget(m_listview); if (layoutDirection() == Qt::LeftToRight) { QPoint topLeft = mapToGlobal(m_lineedit->geometry().bottomLeft()); const int w = m_button->geometry().right() - m_lineedit->geometry().left(); const int h = 300; //(w * 2) / 4; m_popup->setGeometry(QRect(topLeft, QSize(w, h))); } else { QPoint topLeft = mapToGlobal(m_button->geometry().bottomLeft()); const int w = m_lineedit->geometry().right() - m_button->geometry().left(); const int h = 300; //(w * 2) / 4; m_popup->setGeometry(QRect(topLeft, QSize(w, h))); } m_popup->show(); } void VariableLineEdit::updateVariableLine() { QString variables = m_listview->variableLine(); m_lineedit->setText(variables); m_popup->layout()->removeWidget(m_listview); m_listview->deleteLater(); m_listview = nullptr; } void VariableLineEdit::addKateItems(VariableListView *listview) { VariableItem *item = nullptr; // If a current active doc is available KTextEditor::ViewPrivate *activeView = nullptr; KTextEditor::DocumentPrivate *activeDoc = nullptr; KateDocumentConfig *docConfig = KateDocumentConfig::global(); KateViewConfig *viewConfig = KateViewConfig::global(); KateRendererConfig *rendererConfig = KateRendererConfig::global(); if ((activeView = qobject_cast(KTextEditor::EditorPrivate::self()->application()->activeMainWindow()->activeView()))) { activeDoc = activeView->doc(); viewConfig = activeView->config(); docConfig = activeDoc->config(); rendererConfig = activeView->renderer()->config(); } // Add 'auto-brackets' to list item = new VariableBoolItem(QStringLiteral("auto-brackets"), false); item->setHelpText(i18nc("short translation please", "Enable automatic insertion of brackets.")); listview->addItem(item); // Add 'auto-center-lines' to list item = new VariableIntItem(QStringLiteral("auto-center-lines"), viewConfig->autoCenterLines()); static_cast(item)->setRange(1, 100); item->setHelpText(i18nc("short translation please", "Set the number of autocenter lines.")); listview->addItem(item); // Add 'background-color' to list item = new VariableColorItem(QStringLiteral("background-color"), rendererConfig->backgroundColor()); item->setHelpText(i18nc("short translation please", "Set the document background color.")); listview->addItem(item); // Add 'backspace-indents' to list item = new VariableBoolItem(QStringLiteral("backspace-indents"), docConfig->backspaceIndents()); item->setHelpText(i18nc("short translation please", "Pressing backspace in leading whitespace unindents.")); listview->addItem(item); // Add 'block-selection' to list item = new VariableBoolItem(QStringLiteral("block-selection"), false); if (activeView) { static_cast(item)->setValue(activeView->blockSelection()); } item->setHelpText(i18nc("short translation please", "Enable block selection mode.")); listview->addItem(item); // Add 'byte-order-mark' (bom) to list item = new VariableBoolItem(QStringLiteral("byte-order-mark"), docConfig->bom()); item->setHelpText(i18nc("short translation please", "Enable the byte order mark (BOM) when saving Unicode files.")); listview->addItem(item); // Add 'bracket-highlight-color' to list item = new VariableColorItem(QStringLiteral("bracket-highlight-color"), rendererConfig->highlightedBracketColor()); item->setHelpText(i18nc("short translation please", "Set the color for the bracket highlight.")); listview->addItem(item); // Add 'current-line-color' to list item = new VariableColorItem(QStringLiteral("current-line-color"), rendererConfig->highlightedLineColor()); item->setHelpText(i18nc("short translation please", "Set the background color for the current line.")); listview->addItem(item); // Add 'default-dictionary' to list Sonnet::Speller speller; item = new VariableSpellCheckItem(QStringLiteral("default-dictionary"), speller.defaultLanguage()); item->setHelpText(i18nc("short translation please", "Set the default dictionary used for spell checking.")); listview->addItem(item); // Add 'dynamic-word-wrap' to list item = new VariableBoolItem(QStringLiteral("dynamic-word-wrap"), viewConfig->dynWordWrap()); item->setHelpText(i18nc("short translation please", "Enable dynamic word wrap of long lines.")); listview->addItem(item); // Add 'end-of-line' (eol) to list - item = new VariableStringListItem(QStringLiteral("end-of-line"), QStringList() << QStringLiteral("unix") << QStringLiteral("mac") << QStringLiteral("dos"), docConfig->eolString()); + item = new VariableStringListItem(QStringLiteral("end-of-line"), { QStringLiteral("unix"), QStringLiteral("mac"), QStringLiteral("dos") }, docConfig->eolString()); item->setHelpText(i18nc("short translation please", "Sets the end of line mode.")); listview->addItem(item); // Add 'folding-markers' to list item = new VariableBoolItem(QStringLiteral("folding-markers"), viewConfig->foldingBar()); item->setHelpText(i18nc("short translation please", "Enable folding markers in the editor border.")); listview->addItem(item); // Add 'folding-preview' to list item = new VariableBoolItem(QStringLiteral("folding-preview"), viewConfig->foldingPreview()); item->setHelpText(i18nc("short translation please", "Enable folding preview in the editor border.")); listview->addItem(item); // Add 'font-size' to list item = new VariableIntItem(QStringLiteral("font-size"), rendererConfig->font().pointSize()); static_cast(item)->setRange(4, 128); item->setHelpText(i18nc("short translation please", "Set the point size of the document font.")); listview->addItem(item); // Add 'font' to list item = new VariableFontItem(QStringLiteral("font"), rendererConfig->font()); item->setHelpText(i18nc("short translation please", "Set the font of the document.")); listview->addItem(item); // Add 'syntax' (hl) to list /* Prepare list of highlighting modes */ const int count = KateHlManager::self()->highlights(); QStringList hl; for (int z = 0; z < count; ++z) { hl << KateHlManager::self()->hlName(z); } item = new VariableStringListItem(QStringLiteral("syntax"), hl, hl.at(0)); if (activeDoc) { static_cast(item)->setValue(activeDoc->highlightingMode()); } item->setHelpText(i18nc("short translation please", "Set the syntax highlighting.")); listview->addItem(item); // Add 'icon-bar-color' to list item = new VariableColorItem(QStringLiteral("icon-bar-color"), rendererConfig->iconBarColor()); item->setHelpText(i18nc("short translation please", "Set the icon bar color.")); listview->addItem(item); // Add 'icon-border' to list item = new VariableBoolItem(QStringLiteral("icon-border"), viewConfig->iconBar()); item->setHelpText(i18nc("short translation please", "Enable the icon border in the editor view.")); listview->addItem(item); // Add 'indent-mode' to list item = new VariableStringListItem(QStringLiteral("indent-mode"), KateAutoIndent::listIdentifiers(), docConfig->indentationMode()); item->setHelpText(i18nc("short translation please", "Set the auto indentation style.")); listview->addItem(item); // Add 'indent-pasted-text' to list item = new VariableBoolItem(QStringLiteral("indent-pasted-text"), docConfig->indentPastedText()); item->setHelpText(i18nc("short translation please", "Adjust indentation of text pasted from the clipboard.")); listview->addItem(item); // Add 'indent-width' to list item = new VariableIntItem(QStringLiteral("indent-width"), docConfig->indentationWidth()); static_cast(item)->setRange(1, 16); item->setHelpText(i18nc("short translation please", "Set the indentation depth for each indent level.")); listview->addItem(item); // Add 'keep-extra-spaces' to list item = new VariableBoolItem(QStringLiteral("keep-extra-spaces"), docConfig->keepExtraSpaces()); item->setHelpText(i18nc("short translation please", "Allow odd indentation level (no multiple of indent width).")); listview->addItem(item); // Add 'line-numbers' to list item = new VariableBoolItem(QStringLiteral("line-numbers"), viewConfig->lineNumbers()); item->setHelpText(i18nc("short translation please", "Show line numbers.")); listview->addItem(item); // Add 'newline-at-eof' to list item = new VariableBoolItem(QStringLiteral("newline-at-eof"), docConfig->ovr()); item->setHelpText(i18nc("short translation please", "Insert newline at end of file on save.")); listview->addItem(item); // Add 'overwrite-mode' to list item = new VariableBoolItem(QStringLiteral("overwrite-mode"), docConfig->ovr()); item->setHelpText(i18nc("short translation please", "Enable overwrite mode in the document.")); listview->addItem(item); // Add 'persistent-selection' to list item = new VariableBoolItem(QStringLiteral("persistent-selection"), viewConfig->persistentSelection()); item->setHelpText(i18nc("short translation please", "Enable persistent text selection.")); listview->addItem(item); // Add 'replace-tabs-save' to list item = new VariableBoolItem(QStringLiteral("replace-tabs-save"), false); item->setHelpText(i18nc("short translation please", "Replace tabs with spaces when saving the document.")); listview->addItem(item); // Add 'replace-tabs' to list item = new VariableBoolItem(QStringLiteral("replace-tabs"), docConfig->replaceTabsDyn()); item->setHelpText(i18nc("short translation please", "Replace tabs with spaces.")); listview->addItem(item); // Add 'remove-trailing-spaces' to list item = new VariableRemoveSpacesItem(QStringLiteral("remove-trailing-spaces"), docConfig->removeSpaces()); item->setHelpText(i18nc("short translation please", "Remove trailing spaces when saving the document.")); listview->addItem(item); // Add 'scrollbar-minimap' to list item = new VariableBoolItem(QStringLiteral("scrollbar-minimap"), viewConfig->scrollBarMiniMap()); item->setHelpText(i18nc("short translation please", "Show scrollbar minimap.")); listview->addItem(item); // Add 'scrollbar-preview' to list item = new VariableBoolItem(QStringLiteral("scrollbar-preview"), viewConfig->scrollBarPreview()); item->setHelpText(i18nc("short translation please", "Show scrollbar preview.")); listview->addItem(item); // Add 'scheme' to list QStringList schemas; Q_FOREACH (const KateSchema &schema, KTextEditor::EditorPrivate::self()->schemaManager()->list()) { schemas.append(schema.rawName); } item = new VariableStringListItem(QStringLiteral("scheme"), schemas, rendererConfig->schema()); item->setHelpText(i18nc("short translation please", "Set the color scheme.")); listview->addItem(item); // Add 'selection-color' to list item = new VariableColorItem(QStringLiteral("selection-color"), rendererConfig->selectionColor()); item->setHelpText(i18nc("short translation please", "Set the text selection color.")); listview->addItem(item); // Add 'show-tabs' to list item = new VariableBoolItem(QStringLiteral("show-tabs"), docConfig->showTabs()); item->setHelpText(i18nc("short translation please", "Visualize tabs and trailing spaces.")); listview->addItem(item); // Add 'smart-home' to list item = new VariableBoolItem(QStringLiteral("smart-home"), docConfig->smartHome()); item->setHelpText(i18nc("short translation please", "Enable smart home navigation.")); listview->addItem(item); // Add 'tab-indents' to list item = new VariableBoolItem(QStringLiteral("tab-indents"), docConfig->tabIndentsEnabled()); item->setHelpText(i18nc("short translation please", "Pressing TAB key indents.")); listview->addItem(item); // Add 'tab-width' to list item = new VariableIntItem(QStringLiteral("tab-width"), docConfig->tabWidth()); static_cast(item)->setRange(1, 16); item->setHelpText(i18nc("short translation please", "Set the tab display width.")); listview->addItem(item); // Add 'undo-steps' to list item = new VariableIntItem(QStringLiteral("undo-steps"), 0); static_cast(item)->setRange(0, 100); item->setHelpText(i18nc("short translation please", "Set the number of undo steps to remember (0 equals infinity).")); listview->addItem(item); // Add 'word-wrap-column' to list item = new VariableIntItem(QStringLiteral("word-wrap-column"), docConfig->wordWrapAt()); static_cast(item)->setRange(20, 200); item->setHelpText(i18nc("short translation please", "Set the word wrap column.")); listview->addItem(item); // Add 'word-wrap-marker-color' to list item = new VariableColorItem(QStringLiteral("word-wrap-marker-color"), rendererConfig->wordWrapMarkerColor()); item->setHelpText(i18nc("short translation please", "Set the word wrap marker color.")); listview->addItem(item); // Add 'word-wrap' to list item = new VariableBoolItem(QStringLiteral("word-wrap"), docConfig->wordWrap()); item->setHelpText(i18nc("short translation please", "Enable word wrap while typing text.")); listview->addItem(item); } void VariableLineEdit::setText(const QString &text) { m_lineedit->setText(text); } void VariableLineEdit::clear() { m_lineedit->clear(); } QString VariableLineEdit::text() { return m_lineedit->text(); } diff --git a/src/view/kateview.cpp b/src/view/kateview.cpp index c8ddef86..267613ab 100644 --- a/src/view/kateview.cpp +++ b/src/view/kateview.cpp @@ -1,3757 +1,3757 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Michel Ludwig Copyright (C) 2007 Mirko Stocker Copyright (C) 2003 Hamish Rodda Copyright (C) 2002 John Firebaugh Copyright (C) 2001-2004 Christoph Cullmann Copyright (C) 2001-2010 Joseph Wenninger Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //BEGIN includes #include "kateview.h" #include "kateviewinternal.h" #include "kateviewhelpers.h" #include "katerenderer.h" #include "katedocument.h" #include "kateundomanager.h" #include "kateglobal.h" #include "katehighlight.h" #include "katehighlightmenu.h" #include "katedialogs.h" #include "katetextline.h" #include "kateschema.h" #include "katebookmarks.h" #include "kateconfig.h" #include "katemodemenu.h" #include "kateautoindent.h" #include "katecompletionwidget.h" #include "katewordcompletion.h" #include "katekeywordcompletion.h" #include "katelayoutcache.h" #include "spellcheck/spellcheck.h" #include "spellcheck/spellcheckdialog.h" #include "spellcheck/spellingmenu.h" #include "katebuffer.h" #include "script/katescriptmanager.h" #include "script/katescriptaction.h" #include "export/exporter.h" #include "katemessagewidget.h" #include "katetemplatehandler.h" #include "katepartdebug.h" #include "printing/kateprinter.h" #include "katestatusbar.h" #include "kateabstractinputmode.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define VIEW_RANGE_DEBUG //END includes namespace { bool hasCommentInFirstLine(KTextEditor::DocumentPrivate* doc) { const Kate::TextLine& line = doc->kateTextLine(0); Q_ASSERT(line); return doc->isComment(0, line->firstChar()); } } void KTextEditor::ViewPrivate::blockFix(KTextEditor::Range &range) { if (range.start().column() > range.end().column()) { int tmp = range.start().column(); range.setStart(KTextEditor::Cursor(range.start().line(), range.end().column())); range.setEnd(KTextEditor::Cursor(range.end().line(), tmp)); } } KTextEditor::ViewPrivate::ViewPrivate(KTextEditor::DocumentPrivate *doc, QWidget *parent, KTextEditor::MainWindow *mainWindow) : KTextEditor::View (this, parent) , m_completionWidget(nullptr) , m_annotationModel(nullptr) , m_hasWrap(false) , m_doc(doc) , m_textFolding(doc->buffer()) , m_config(new KateViewConfig(this)) , m_renderer(new KateRenderer(doc, m_textFolding, this)) , m_viewInternal(new KateViewInternal(this)) , m_spell(new KateSpellCheckDialog(this)) , m_bookmarks(new KateBookmarks(this)) , m_topSpacer(new QSpacerItem(0,0)) , m_leftSpacer(new QSpacerItem(0,0)) , m_rightSpacer(new QSpacerItem(0,0)) , m_bottomSpacer(new QSpacerItem(0,0)) , m_startingUp(true) , m_updatingDocumentConfig(false) , m_bottomViewBar(nullptr) , m_gotoBar(nullptr) , m_dictionaryBar(nullptr) , m_spellingMenu(new KateSpellingMenu(this)) , m_userContextMenuSet(false) , m_delayedUpdateTriggered(false) , m_lineToUpdateMin(-1) , m_lineToUpdateMax(-1) , m_floatTopMessageWidget(nullptr) , m_floatBottomMessageWidget(nullptr) , m_mainWindow(mainWindow ? mainWindow : KTextEditor::EditorPrivate::self()->dummyMainWindow()) // use dummy window if no window there! , m_statusBar(nullptr) , m_temporaryAutomaticInvocationDisabled(false) , m_autoFoldedFirstLine(false) , m_clipboard(cursors()) { // queued connect to collapse view updates for range changes, INIT THIS EARLY ENOUGH! connect(this, SIGNAL(delayedUpdateOfView()), this, SLOT(slotDelayedUpdateOfView()), Qt::QueuedConnection); KXMLGUIClient::setComponentName(KTextEditor::EditorPrivate::self()->aboutData().componentName(), KTextEditor::EditorPrivate::self()->aboutData().displayName()); KTextEditor::EditorPrivate::self()->registerView(this); /** * try to let the main window, if any, create a view bar for this view */ QWidget *bottomBarParent = m_mainWindow->createViewBar(this); m_bottomViewBar = new KateViewBar(bottomBarParent != nullptr, bottomBarParent ? bottomBarParent : this, this); // ugly workaround: // Force the layout to be left-to-right even on RTL deskstop, as discussed // on the mailing list. This will cause the lines and icons panel to be on // the left, even for Arabic/Hebrew/Farsi/whatever users. setLayoutDirection(Qt::LeftToRight); m_bottomViewBar->installEventFilter(m_viewInternal); // add KateMessageWidget for KTE::MessageInterface immediately above view m_topMessageWidget = new KateMessageWidget(this); m_topMessageWidget->hide(); // add KateMessageWidget for KTE::MessageInterface immediately above view m_bottomMessageWidget = new KateMessageWidget(this); m_bottomMessageWidget->hide(); // add bottom viewbar... if (bottomBarParent) { m_mainWindow->addWidgetToViewBar(this, m_bottomViewBar); } // add layout for floating message widgets to KateViewInternal m_notificationLayout = new QVBoxLayout(m_viewInternal); m_notificationLayout->setContentsMargins(20, 20, 20, 20); m_viewInternal->setLayout(m_notificationLayout); // this really is needed :) m_viewInternal->updateView(); doc->addView(this); setFocusProxy(m_viewInternal); setFocusPolicy(Qt::StrongFocus); setXMLFile(QStringLiteral("katepart5ui.rc")); setupConnections(); setupActions(); // auto word completion new KateWordCompletionView(this, actionCollection()); // update the enabled state of the undo/redo actions... slotUpdateUndo(); /** * create the status bar of this view * do this after action creation, we use some of them! */ toggleStatusBar(); m_startingUp = false; updateConfig(); slotHlChanged(); KCursor::setAutoHideCursor(m_viewInternal, true); // user interaction (scrolling) starts notification auto-hide timer connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_topMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); // user interaction (cursor navigation) starts notification auto-hide timer connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_topMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_bottomMessageWidget, SLOT(startAutoHideTimer())); // folding restoration on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(saveFoldingState())); connect(m_doc, SIGNAL(reloaded(KTextEditor::Document*)), SLOT(applyFoldingState())); // update highlights on scrolling and co connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(createHighlights())); // clear highlights on reload connect(m_doc, SIGNAL(aboutToReload(KTextEditor::Document*)), SLOT(clearHighlights())); // setup layout setupLayout(); } KTextEditor::ViewPrivate::~ViewPrivate() { // invalidate update signal m_delayedUpdateTriggered = false; // remove from xmlgui factory, to be safe if (factory()) { factory()->removeClient(this); } // delete internal view before view bar! delete m_viewInternal; /** * remove view bar again, if needed */ m_mainWindow->deleteViewBar(this); m_bottomViewBar = nullptr; m_doc->removeView(this); delete m_renderer; delete m_config; KTextEditor::EditorPrivate::self()->deregisterView(this); } void KTextEditor::ViewPrivate::toggleStatusBar() { /** * if there, delete it */ if (m_statusBar) { bottomViewBar()->removePermanentBarWidget(m_statusBar); delete m_statusBar; m_statusBar = nullptr; emit statusBarEnabledChanged(this, false); return; } /** * else: create it */ m_statusBar = new KateStatusBar(this); bottomViewBar()->addPermanentBarWidget(m_statusBar); emit statusBarEnabledChanged(this, true); } void KTextEditor::ViewPrivate::setupLayout() { // delete old layout if any if (layout()) { delete layout(); /** * need to recreate spacers because they are deleted with * their belonging layout */ m_topSpacer = new QSpacerItem(0,0); m_leftSpacer = new QSpacerItem(0,0); m_rightSpacer = new QSpacerItem(0,0); m_bottomSpacer = new QSpacerItem(0,0); } // set margins QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; const int margin = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); m_topSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); m_leftSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_rightSpacer->changeSize(margin, 0, QSizePolicy::Fixed, QSizePolicy::Minimum); m_bottomSpacer->changeSize(0, margin, QSizePolicy::Minimum, QSizePolicy::Fixed); // define layout QGridLayout *layout=new QGridLayout(this); layout->setMargin(0); layout->setSpacing(0); const bool frameAroundContents = style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents, &opt, this); if (frameAroundContents) { // top message widget layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 4); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 3, 0, 1, 4); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 1, 4, 3, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 4, 0, 1, 4); // dummy layout->addWidget(m_viewInternal->m_dummy, 4, 4, 1, 1); // bottom message layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_lineScroll->setAutoFillBackground(false); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Window); m_viewInternal->m_columnScroll->setAutoFillBackground(false); } else { // top message widget layout->addWidget(m_topMessageWidget, 0, 0, 1, 5); // top spacer layout->addItem(m_topSpacer, 1, 0, 1, 5); // left spacer layout->addItem(m_leftSpacer, 2, 0, 1, 1); // left border layout->addWidget(m_viewInternal->m_leftBorder, 2, 1, 1, 1); // view layout->addWidget(m_viewInternal, 2, 2, 1, 1); // vertical scrollbar layout->addWidget(m_viewInternal->m_lineScroll, 2, 3, 1, 1); // right spacer layout->addItem(m_rightSpacer, 2, 4, 1, 1); // horizontal scrollbar layout->addWidget(m_viewInternal->m_columnScroll, 3, 1, 1, 2); // dummy layout->addWidget(m_viewInternal->m_dummy, 3, 3, 1, 1); // bottom spacer layout->addItem(m_bottomSpacer, 4, 0, 1, 5); // bottom message layout->addWidget(m_bottomMessageWidget, 5, 0, 1, 5); // bottom viewbar if (m_bottomViewBar->parentWidget() == this) { layout->addWidget(m_bottomViewBar, 6, 0, 1, 5); } // stretch layout->setColumnStretch(2, 1); layout->setRowStretch(2, 1); // adjust scrollbar background m_viewInternal->m_lineScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_lineScroll->setAutoFillBackground(true); m_viewInternal->m_columnScroll->setBackgroundRole(QPalette::Base); m_viewInternal->m_columnScroll->setAutoFillBackground(true); } } void KTextEditor::ViewPrivate::setupConnections() { connect(m_doc, SIGNAL(undoChanged()), this, SLOT(slotUpdateUndo())); connect(m_doc, SIGNAL(highlightingModeChanged(KTextEditor::Document*)), this, SLOT(slotHlChanged())); connect(m_doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); connect(m_viewInternal, SIGNAL(dropEventPass(QDropEvent*)), this, SIGNAL(dropEventPass(QDropEvent*))); connect(m_doc, SIGNAL(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*)), m_viewInternal->m_leftBorder, SLOT(annotationModelChanged(KTextEditor::AnnotationModel*,KTextEditor::AnnotationModel*))); } void KTextEditor::ViewPrivate::goToPreviousEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Previous, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::goToNextEditingPosition() { auto c = doc()->lastEditingPosition(KTextEditor::DocumentPrivate::Next, cursorPosition()); if(c.isValid()){ setCursorPosition(c); } } void KTextEditor::ViewPrivate::setupActions() { KActionCollection *ac = actionCollection(); QAction *a; m_toggleWriteLock = nullptr; m_cut = a = ac->addAction(KStandardAction::Cut, this, SLOT(cut())); a->setWhatsThis(i18n("Cut the selected text and move it to the clipboard")); m_paste = a = ac->addAction(KStandardAction::Paste, this, SLOT(paste())); a->setWhatsThis(i18n("Paste previously copied or cut clipboard contents")); m_copy = a = ac->addAction(KStandardAction::Copy, this, SLOT(copy())); a->setWhatsThis(i18n("Use this command to copy the currently selected text to the system clipboard.")); m_pasteMenu = ac->addAction(QStringLiteral("edit_paste_menu"), new KatePasteMenu(i18n("Clipboard &History"), this)); connect(KTextEditor::EditorPrivate::self(), SIGNAL(clipboardHistoryChanged()), this, SLOT(slotClipboardHistoryChanged())); if (!m_doc->readOnly()) { a = ac->addAction(KStandardAction::Save, m_doc, SLOT(documentSave())); a->setWhatsThis(i18n("Save the current document")); a = m_editUndo = ac->addAction(KStandardAction::Undo, m_doc, SLOT(undo())); a->setWhatsThis(i18n("Revert the most recent editing actions")); a = m_editRedo = ac->addAction(KStandardAction::Redo, m_doc, SLOT(redo())); a->setWhatsThis(i18n("Revert the most recent undo operation")); // Tools > Scripts // stored inside scoped pointer to ensure we destruct it early enough as it does internal cleanups of other child objects m_scriptActionMenu.reset(new KateScriptActionMenu(this, i18n("&Scripts"))); ac->addAction(QStringLiteral("tools_scripts"), m_scriptActionMenu.data()); a = ac->addAction(QStringLiteral("tools_apply_wordwrap")); a->setText(i18n("Apply &Word Wrap")); a->setWhatsThis(i18n("Use this command to wrap all lines of the current document which are longer than the width of the" " current view, to fit into this view.

This is a static word wrap, meaning it is not updated" " when the view is resized.")); connect(a, SIGNAL(triggered(bool)), SLOT(applyWordWrap())); a = ac->addAction(QStringLiteral("tools_cleanIndent")); a->setText(i18n("&Clean Indentation")); a->setWhatsThis(i18n("Use this to clean the indentation of a selected block of text (only tabs/only spaces).

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); connect(a, SIGNAL(triggered(bool)), SLOT(cleanIndent())); a = ac->addAction(QStringLiteral("tools_align")); a->setText(i18n("&Align")); a->setWhatsThis(i18n("Use this to align the current line or block of text to its proper indent level.")); connect(a, SIGNAL(triggered(bool)), SLOT(align())); a = ac->addAction(QStringLiteral("tools_comment")); a->setText(i18n("C&omment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_D)); a->setWhatsThis(i18n("This command comments out the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(comment())); a = ac->addAction(QStringLiteral("Previous Editing Line")); a->setText(i18n("Go to previous editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToPreviousEditingPosition())); a = ac->addAction(QStringLiteral("Next Editing Line")); a->setText(i18n("Go to next editing line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_E)); connect(a, SIGNAL(triggered(bool)), SLOT(goToNextEditingPosition())); a = ac->addAction(QStringLiteral("tools_uncomment")); a->setText(i18n("Unco&mment")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D)); a->setWhatsThis(i18n("This command removes comments from the current line or a selected block of text.

" "The characters for single/multiple line comments are defined within the language's highlighting.")); connect(a, SIGNAL(triggered(bool)), SLOT(uncomment())); a = ac->addAction(QStringLiteral("tools_toggle_comment")); a->setText(i18n("Toggle Comment")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleComment())); a = m_toggleWriteLock = new KToggleAction(i18n("&Read Only Mode"), this); a->setWhatsThis(i18n("Lock/unlock the document for writing")); a->setChecked(!m_doc->isReadWrite()); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWriteLock())); ac->addAction(QStringLiteral("tools_toggle_write_lock"), a); a = ac->addAction(QStringLiteral("tools_uppercase")); a->setText(i18n("Uppercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to uppercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(uppercase())); a = ac->addAction(QStringLiteral("tools_lowercase")); a->setText(i18n("Lowercase")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_U)); a->setWhatsThis(i18n("Convert the selection to lowercase, or the character to the " "right of the cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(lowercase())); a = ac->addAction(QStringLiteral("tools_capitalize")); a->setText(i18n("Capitalize")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U)); a->setWhatsThis(i18n("Capitalize the selection, or the word under the " "cursor if no text is selected.")); connect(a, SIGNAL(triggered(bool)), SLOT(capitalize())); a = ac->addAction(QStringLiteral("tools_join_lines")); a->setText(i18n("Join Lines")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_J)); connect(a, SIGNAL(triggered(bool)), SLOT(joinLines())); a = ac->addAction(QStringLiteral("tools_invoke_code_completion")); a->setText(i18n("Invoke Code Completion")); a->setWhatsThis(i18n("Manually invoke command completion, usually by using a shortcut bound to this action.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Space)); connect(a, SIGNAL(triggered(bool)), SLOT(userInvokedCompletion())); } else { m_cut->setEnabled(false); m_paste->setEnabled(false); m_pasteMenu->setEnabled(false); m_editUndo = nullptr; m_editRedo = nullptr; } a = ac->addAction(KStandardAction::Print, this, SLOT(print())); a->setWhatsThis(i18n("Print the current document.")); a = ac->addAction(KStandardAction::PrintPreview, this, SLOT(printPreview())); a->setWhatsThis(i18n("Show print preview of current document")); a = ac->addAction(QStringLiteral("file_reload")); a->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); a->setText(i18n("Reloa&d")); ac->setDefaultShortcuts(a, KStandardShortcut::reload()); a->setWhatsThis(i18n("Reload the current document from disk.")); connect(a, SIGNAL(triggered(bool)), SLOT(reloadFile())); a = ac->addAction(KStandardAction::SaveAs, m_doc, SLOT(documentSaveAs())); a->setWhatsThis(i18n("Save the current document to disk, with a name of your choice.")); a = new KateViewEncodingAction(m_doc, this, i18n("Save As with Encoding..."), this, true /* special mode for save as */); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); ac->addAction(QStringLiteral("file_save_as_with_encoding"), a); a = ac->addAction(QStringLiteral("file_save_copy_as")); a->setIcon(QIcon::fromTheme(QStringLiteral("document-save-as"))); a->setText(i18n("Save &Copy As...")); a->setWhatsThis(i18n("Save a copy of the current document to disk.")); connect(a, SIGNAL(triggered(bool)), m_doc, SLOT(documentSaveCopyAs())); a = ac->addAction(KStandardAction::GotoLine, this, SLOT(gotoLine())); a->setWhatsThis(i18n("This command opens a dialog and lets you choose a line that you want the cursor to move to.")); a = ac->addAction(QStringLiteral("modified_line_up")); a->setText(i18n("Move to Previous Modified Line")); a->setWhatsThis(i18n("Move upwards to the previous modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toPrevModifiedLine())); a = ac->addAction(QStringLiteral("modified_line_down")); a->setText(i18n("Move to Next Modified Line")); a->setWhatsThis(i18n("Move downwards to the next modified line.")); connect(a, SIGNAL(triggered(bool)), SLOT(toNextModifiedLine())); a = ac->addAction(QStringLiteral("set_confdlg")); a->setText(i18n("&Configure Editor...")); a->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); a->setWhatsThis(i18n("Configure various aspects of this editor.")); connect(a, SIGNAL(triggered(bool)), SLOT(slotConfigDialog())); m_modeAction = new KateModeMenu(i18n("&Mode"), this); ac->addAction(QStringLiteral("tools_mode"), m_modeAction); m_modeAction->setWhatsThis(i18n("Here you can choose which mode should be used for the current document. This will influence the highlighting and folding being used, for example.")); m_modeAction->updateMenu(m_doc); KateHighlightingMenu *menu = new KateHighlightingMenu(i18n("&Highlighting"), this); ac->addAction(QStringLiteral("tools_highlighting"), menu); menu->setWhatsThis(i18n("Here you can choose how the current document should be highlighted.")); menu->updateMenu(m_doc); KateViewSchemaAction *schemaMenu = new KateViewSchemaAction(i18n("&Schema"), this); ac->addAction(QStringLiteral("view_schemas"), schemaMenu); schemaMenu->updateMenu(this); // indentation menu KateViewIndentationAction *indentMenu = new KateViewIndentationAction(m_doc, i18n("&Indentation"), this); ac->addAction(QStringLiteral("tools_indentation"), indentMenu); m_selectAll = a = ac->addAction(KStandardAction::SelectAll, this, SLOT(selectAll())); a->setWhatsThis(i18n("Select the entire text of the current document.")); m_deSelect = a = ac->addAction(KStandardAction::Deselect, this, SLOT(clearSelection())); a->setWhatsThis(i18n("If you have selected something within the current document, this will no longer be selected.")); a = ac->addAction(QStringLiteral("view_inc_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); a->setText(i18n("Enlarge Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomIn()); a->setWhatsThis(i18n("This increases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotIncFontSizes())); a = ac->addAction(QStringLiteral("view_dec_font_sizes")); a->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); a->setText(i18n("Shrink Font")); ac->setDefaultShortcuts(a, KStandardShortcut::zoomOut()); a->setWhatsThis(i18n("This decreases the display font size.")); connect(a, SIGNAL(triggered(bool)), m_viewInternal, SLOT(slotDecFontSizes())); a = m_toggleBlockSelection = new KToggleAction(i18n("Toggle Bl&ock Selection"), this); ac->addAction(QStringLiteral("set_verticalSelect"), a); a->setWhatsThis(i18n("This command allows switching an existing selection between a block of text and a continuous (line-based) selection.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleBlockSelection())); a = ac->addAction(QStringLiteral("selection_to_block"), this); a->setText(i18n("Selection to aligned block")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); a->setWhatsThis(i18n("Transforms a selection to an aligned selection block, filling up lines with space characters as needed.")); connect(a, &QAction::triggered, this, [this]() { toAlignedBlock(true); }); a = ac->addAction(QStringLiteral("switch_next_input_mode")); a->setText(i18n("Switch to Next Input Mode")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); a->setWhatsThis(i18n("Switch to the next input mode.")); connect(a, SIGNAL(triggered(bool)), SLOT(cycleInputMode())); a = m_toggleInsert = new KToggleAction(i18n("Overwr&ite Mode"), this); ac->addAction(QStringLiteral("set_insert"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Insert)); a->setWhatsThis(i18n("Choose whether you want the text you type to be inserted or to overwrite existing text.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleInsert())); KToggleAction *toggleAction; a = m_toggleDynWrap = toggleAction = new KToggleAction(i18n("&Dynamic Word Wrap"), this); ac->addAction(QStringLiteral("view_dynamic_word_wrap"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F10)); a->setWhatsThis(i18n("If this option is checked, the text lines will be wrapped at the view border on the screen.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleDynWordWrap())); a = m_setDynWrapIndicators = new KSelectAction(i18n("Dynamic Word Wrap Indicators"), this); ac->addAction(QStringLiteral("dynamic_word_wrap_indicators"), a); a->setWhatsThis(i18n("Choose when the Dynamic Word Wrap Indicators should be displayed")); connect(m_setDynWrapIndicators, SIGNAL(triggered(int)), this, SLOT(setDynWrapIndicators(int))); - QStringList list2; - list2.append(i18n("&Off")); - list2.append(i18n("Follow &Line Numbers")); - list2.append(i18n("&Always On")); + const QStringList list2{ i18n("&Off"), i18n("Follow &Line Numbers"), i18n("&Always On") }; m_setDynWrapIndicators->setItems(list2); m_setDynWrapIndicators->setEnabled(m_toggleDynWrap->isChecked()); // only synced on real change, later a = toggleAction = m_toggleFoldingMarkers = new KToggleAction(i18n("Show Folding &Markers"), this); ac->addAction(QStringLiteral("view_folding_markers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F9)); a->setWhatsThis(i18n("You can choose if the codefolding marks should be shown, if codefolding is possible.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleFoldingMarkers())); a = m_toggleIconBar = toggleAction = new KToggleAction(i18n("Show &Icon Border"), this); ac->addAction(QStringLiteral("view_border"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F6)); a->setWhatsThis(i18n("Show/hide the icon border.

The icon border shows bookmark symbols, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleIconBorder())); a = toggleAction = m_toggleLineNumbers = new KToggleAction(i18n("Show &Line Numbers"), this); ac->addAction(QStringLiteral("view_line_numbers"), a); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F11)); a->setWhatsThis(i18n("Show/hide the line numbers on the left hand side of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleLineNumbersOn())); a = m_toggleScrollBarMarks = toggleAction = new KToggleAction(i18n("Show Scroll&bar Marks"), this); ac->addAction(QStringLiteral("view_scrollbar_marks"), a); a->setWhatsThis(i18n("Show/hide the marks on the vertical scrollbar.

The marks show bookmarks, for instance.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMarks())); a = m_toggleScrollBarMiniMap = toggleAction = new KToggleAction(i18n("Show Scrollbar Mini-Map"), this); ac->addAction(QStringLiteral("view_scrollbar_minimap"), a); a->setWhatsThis(i18n("Show/hide the mini-map on the vertical scrollbar.

The mini-map shows an overview of the whole document.")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMap())); // a = m_toggleScrollBarMiniMapAll = toggleAction = new KToggleAction(i18n("Show the whole document in the Mini-Map"), this); // ac->addAction(QLatin1String("view_scrollbar_minimap_all"), a); // a->setWhatsThis(i18n("Display the whole document in the mini-map.

With this option set the whole document will be visible in the mini-map.")); // connect(a, SIGNAL(triggered(bool)), SLOT(toggleScrollBarMiniMapAll())); // connect(m_toggleScrollBarMiniMap, SIGNAL(triggered(bool)), m_toggleScrollBarMiniMapAll, SLOT(setEnabled(bool))); a = toggleAction = m_toggleWWMarker = new KToggleAction(i18n("Show Static &Word Wrap Marker"), this); ac->addAction(QStringLiteral("view_word_wrap_marker"), a); a->setWhatsThis(i18n( "Show/hide the Word Wrap Marker, a vertical line drawn at the word " "wrap column as defined in the editing properties")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleWWMarker())); a = m_toggleNPSpaces = new KToggleAction(i18n("Show Non-Printable Spaces"), this); ac->addAction(QStringLiteral("view_non_printable_spaces"), a); a->setWhatsThis(i18n("Show/hide bounding box around non-printable spaces")); connect(a, SIGNAL(triggered(bool)), SLOT(toggleNPSpaces())); a = m_toggleWordCount = new KToggleAction(i18n("Show Word Count"), this); a->setChecked(false); ac->addAction(QStringLiteral("view_word_count"), a); a->setWhatsThis(i18n("Show/hide word count in status bar")); connect(a, &QAction::triggered, this, &ViewPrivate::toggleWordCount); a = m_switchCmdLine = ac->addAction(QStringLiteral("switch_to_cmd_line")); a->setText(i18n("Switch to Command Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_F7)); a->setWhatsThis(i18n("Show/hide the command line on the bottom of the view.")); connect(a, SIGNAL(triggered(bool)), SLOT(switchToCmdLine())); KActionMenu *am = new KActionMenu(i18n("Input Modes"), this); m_inputModeActions = new QActionGroup(am); ac->addAction(QStringLiteral("view_input_modes"), am); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { a = new QAction(mode->viewInputModeHuman(), m_inputModeActions); am->addAction(a); a->setWhatsThis(i18n("Activate/deactivate %1", mode->viewInputModeHuman())); const InputMode im = mode->viewInputMode(); a->setData(static_cast(im)); a->setCheckable(true); if (im == m_config->inputMode()) a->setChecked(true); connect(a, SIGNAL(triggered()), SLOT(toggleInputMode())); } a = m_setEndOfLine = new KSelectAction(i18n("&End of Line"), this); ac->addAction(QStringLiteral("set_eol"), a); a->setWhatsThis(i18n("Choose which line endings should be used, when you save the document")); - QStringList list; - list.append(i18nc("@item:inmenu End of Line", "&UNIX")); - list.append(i18nc("@item:inmenu End of Line", "&Windows/DOS")); - list.append(i18nc("@item:inmenu End of Line", "&Macintosh")); + const QStringList list { + i18nc("@item:inmenu End of Line", "&UNIX") + , i18nc("@item:inmenu End of Line", "&Windows/DOS") + , i18nc("@item:inmenu End of Line", "&Macintosh") + }; m_setEndOfLine->setItems(list); m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); connect(m_setEndOfLine, SIGNAL(triggered(int)), this, SLOT(setEol(int))); a = m_addBom = new KToggleAction(i18n("Add &Byte Order Mark (BOM)"), this); m_addBom->setChecked(m_doc->config()->bom()); ac->addAction(QStringLiteral("add_bom"), a); a->setWhatsThis(i18n("Enable/disable adding of byte order marks for UTF-8/UTF-16 encoded files while saving")); connect(m_addBom, SIGNAL(triggered(bool)), this, SLOT(setAddBom(bool))); // encoding menu m_encodingAction = new KateViewEncodingAction(m_doc, this, i18n("E&ncoding"), this); ac->addAction(QStringLiteral("set_encoding"), m_encodingAction); a = ac->addAction(KStandardAction::Find, this, SLOT(find())); a->setWhatsThis(i18n("Look up the first occurrence of a piece of text or regular expression.")); addAction(a); a = ac->addAction(QStringLiteral("edit_find_selected")); a->setText(i18n("Find Selected")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_H)); a->setWhatsThis(i18n("Finds next occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedForwards())); a = ac->addAction(QStringLiteral("edit_find_selected_backwards")); a->setText(i18n("Find Selected Backwards")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_H)); a->setWhatsThis(i18n("Finds previous occurrence of selected text.")); connect(a, SIGNAL(triggered(bool)), SLOT(findSelectedBackwards())); a = ac->addAction(KStandardAction::FindNext, this, SLOT(findNext())); a->setWhatsThis(i18n("Look up the next occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::FindPrev, QStringLiteral("edit_find_prev"), this, SLOT(findPrevious())); a->setWhatsThis(i18n("Look up the previous occurrence of the search phrase.")); addAction(a); a = ac->addAction(KStandardAction::Replace, this, SLOT(replace())); a->setWhatsThis(i18n("Look up a piece of text or regular expression and replace the result with some given text.")); m_spell->createActions(ac); m_toggleOnTheFlySpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), this); m_toggleOnTheFlySpellCheck->setWhatsThis(i18n("Enable/disable automatic spell checking")); connect(m_toggleOnTheFlySpellCheck, SIGNAL(triggered(bool)), SLOT(toggleOnTheFlySpellCheck(bool))); ac->addAction(QStringLiteral("tools_toggle_automatic_spell_checking"), m_toggleOnTheFlySpellCheck); ac->setDefaultShortcut(m_toggleOnTheFlySpellCheck, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); a = ac->addAction(QStringLiteral("tools_change_dictionary")); a->setText(i18n("Change Dictionary...")); a->setWhatsThis(i18n("Change the dictionary that is used for spell checking.")); connect(a, SIGNAL(triggered()), SLOT(changeDictionary())); a = ac->addAction(QStringLiteral("tools_clear_dictionary_ranges")); a->setText(i18n("Clear Dictionary Ranges")); a->setVisible(false); a->setWhatsThis(i18n("Remove all the separate dictionary ranges that were set for spell checking.")); connect(a, SIGNAL(triggered()), m_doc, SLOT(clearDictionaryRanges())); connect(m_doc, SIGNAL(dictionaryRangesPresent(bool)), a, SLOT(setVisible(bool))); m_copyHtmlAction = ac->addAction(QStringLiteral("edit_copy_html"), this, SLOT(exportHtmlToClipboard())); m_copyHtmlAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); m_copyHtmlAction->setText(i18n("Copy as &HTML")); m_copyHtmlAction->setWhatsThis(i18n("Use this command to copy the currently selected text as HTML to the system clipboard.")); a = ac->addAction(QStringLiteral("file_export_html"), this, SLOT(exportHtmlToFile())); a->setText(i18n("E&xport as HTML...")); a->setWhatsThis(i18n("This command allows you to export the current document" " with all highlighting information into a HTML document.")); m_spellingMenu->createActions(ac); m_bookmarks->createActions(ac); slotSelectionChanged(); //Now setup the editing actions before adding the associated //widget and setting the shortcut context setupEditActions(); setupCodeFolding(); slotClipboardHistoryChanged(); ac->addAssociatedWidget(m_viewInternal); foreach (QAction *action, ac->actions()) { action->setShortcutContext(Qt::WidgetWithChildrenShortcut); } connect(this, SIGNAL(selectionChanged(KTextEditor::View*)), this, SLOT(slotSelectionChanged())); } void KTextEditor::ViewPrivate::slotConfigDialog() { // invoke config dialog, will auto-save configuration to katepartrc KTextEditor::EditorPrivate::self()->configDialog(this); } void KTextEditor::ViewPrivate::setupEditActions() { //If you add an editing action to this //function make sure to include the line //m_editActions << a after creating the action KActionCollection *ac = actionCollection(); QAction *a = ac->addAction(QStringLiteral("word_left")); a->setText(i18n("Move Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::backwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_left")); a->setText(i18n("Select Character Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_left")); a->setText(i18n("Select Word Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("word_right")); a->setText(i18n("Move Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::forwardWord()); connect(a, SIGNAL(triggered(bool)), SLOT(wordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_char_right")); a->setText(i18n("Select Character Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftCursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("select_word_right")); a->setText(i18n("Select Word Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_line")); a->setText(i18n("Move to Beginning of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::beginningOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(home())); m_editActions << a; a = ac->addAction(QStringLiteral("beginning_of_document")); a->setText(i18n("Move to Beginning of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::begin()); connect(a, SIGNAL(triggered(bool)), SLOT(top())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_line")); a->setText(i18n("Select to Beginning of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftHome())); m_editActions << a; a = ac->addAction(QStringLiteral("select_beginning_of_document")); a->setText(i18n("Select to Beginning of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_Home)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTop())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_line")); a->setText(i18n("Move to End of Line")); ac->setDefaultShortcuts(a, KStandardShortcut::endOfLine()); connect(a, SIGNAL(triggered(bool)), SLOT(end())); m_editActions << a; a = ac->addAction(QStringLiteral("end_of_document")); a->setText(i18n("Move to End of Document")); ac->setDefaultShortcuts(a, KStandardShortcut::end()); connect(a, SIGNAL(triggered(bool)), SLOT(bottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_line")); a->setText(i18n("Select to End of Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftEnd())); m_editActions << a; a = ac->addAction(QStringLiteral("select_end_of_document")); a->setText(i18n("Select to End of Document")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_End)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottom())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_up")); a->setText(i18n("Select to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftUp())); m_editActions << a; a = ac->addAction(QStringLiteral("freeze_secondary_cursors")); a->setText(i18n("Freeze secondary cursor positions")); a->setCheckable(true); a->setChecked(false); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::META + Qt::Key_F)); connect(a, SIGNAL(triggered(bool)), SLOT(setSecondaryCursorsFrozen(bool))); m_editActions << a; a = ac->addAction(QStringLiteral("add_virtual_cursor")); a->setText(i18n("Add secondary cursor at cursor position")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::META + Qt::Key_D)); connect(a, SIGNAL(triggered(bool)), SLOT(placeSecondaryCursor())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_up")); a->setText(i18n("Scroll Line Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_down")); a->setText(i18n("Move to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(down())); m_editActions << a; a = ac->addAction(QStringLiteral("move_line_up")); a->setText(i18n("Move to Previous Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Up)); connect(a, SIGNAL(triggered(bool)), SLOT(up())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cursor_right")); a->setText(i18n("Move Cursor Right")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Right)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorRight())); m_editActions << a; a = ac->addAction(QStringLiteral("move_cusor_left")); a->setText(i18n("Move Cursor Left")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Left)); connect(a, SIGNAL(triggered(bool)), SLOT(cursorLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("select_line_down")); a->setText(i18n("Select to Next Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_line_down")); a->setText(i18n("Scroll Line Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_Down)); connect(a, SIGNAL(triggered(bool)), SLOT(scrollDown())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_up")); a->setText(i18n("Scroll Page Up")); ac->setDefaultShortcuts(a, KStandardShortcut::prior()); connect(a, SIGNAL(triggered(bool)), SLOT(pageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_up")); a->setText(i18n("Select Page Up")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageUp())); m_editActions << a; a = ac->addAction(QStringLiteral("move_top_of_view")); a->setText(i18n("Move to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(topOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_top_of_view")); a->setText(i18n("Select to Top of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageUp)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftTopOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("scroll_page_down")); a->setText(i18n("Scroll Page Down")); ac->setDefaultShortcuts(a, KStandardShortcut::next()); connect(a, SIGNAL(triggered(bool)), SLOT(pageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("select_page_down")); a->setText(i18n("Select Page Down")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftPageDown())); m_editActions << a; a = ac->addAction(QStringLiteral("move_bottom_of_view")); a->setText(i18n("Move to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(bottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("select_bottom_of_view")); a->setText(i18n("Select to Bottom of View")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_PageDown)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftBottomOfView())); m_editActions << a; a = ac->addAction(QStringLiteral("to_matching_bracket")); a->setText(i18n("Move to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(toMatchingBracket())); //m_editActions << a; a = ac->addAction(QStringLiteral("select_matching_bracket")); a->setText(i18n("Select to Matching Bracket")); ac->setDefaultShortcut(a, QKeySequence(Qt::SHIFT + Qt::CTRL + Qt::Key_6)); connect(a, SIGNAL(triggered(bool)), SLOT(shiftToMatchingBracket())); //m_editActions << a; // anders: shortcuts doing any changes should not be created in read-only mode if (!m_doc->readOnly()) { a = ac->addAction(QStringLiteral("transpose_char")); a->setText(i18n("Transpose Characters")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_T)); connect(a, SIGNAL(triggered(bool)), SLOT(transpose())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_line")); a->setText(i18n("Delete Line")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_K)); connect(a, SIGNAL(triggered(bool)), SLOT(killLine())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_left")); a->setText(i18n("Delete Word Left")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordBack()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordLeft())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_word_right")); a->setText(i18n("Delete Word Right")); ac->setDefaultShortcuts(a, KStandardShortcut::deleteWordForward()); connect(a, SIGNAL(triggered(bool)), SLOT(deleteWordRight())); m_editActions << a; a = ac->addAction(QStringLiteral("delete_next_character")); a->setText(i18n("Delete Next Character")); ac->setDefaultShortcut(a, QKeySequence(Qt::Key_Delete)); connect(a, SIGNAL(triggered(bool)), SLOT(keyDelete())); m_editActions << a; a = ac->addAction(QStringLiteral("backspace")); a->setText(i18n("Backspace")); QList scuts; scuts << QKeySequence(Qt::Key_Backspace) << QKeySequence(Qt::SHIFT + Qt::Key_Backspace); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(backspace())); m_editActions << a; a = ac->addAction(QStringLiteral("insert_tabulator")); a->setText(i18n("Insert Tab")); connect(a, SIGNAL(triggered(bool)), SLOT(insertTab())); m_editActions << a; a = ac->addAction(QStringLiteral("smart_newline")); a->setText(i18n("Insert Smart Newline")); a->setWhatsThis(i18n("Insert newline including leading characters of the current line which are not letters or numbers.")); scuts.clear(); scuts << QKeySequence(Qt::SHIFT + Qt::Key_Return) << QKeySequence(Qt::SHIFT + Qt::Key_Enter); ac->setDefaultShortcuts(a, scuts); connect(a, SIGNAL(triggered(bool)), SLOT(smartNewline())); m_editActions << a; a = ac->addAction(QStringLiteral("tools_indent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-more"))); a->setText(i18n("&Indent")); a->setWhatsThis(i18n("Use this to indent a selected block of text.

" "You can configure whether tabs should be honored and used or replaced with spaces, in the configuration dialog.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(indent())); a = ac->addAction(QStringLiteral("tools_unindent")); a->setIcon(QIcon::fromTheme(QStringLiteral("format-indent-less"))); a->setText(i18n("&Unindent")); a->setWhatsThis(i18n("Use this to unindent a selected block of text.")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); connect(a, SIGNAL(triggered(bool)), SLOT(unIndent())); } if (hasFocus()) { slotGotFocus(); } else { slotLostFocus(); } } void KTextEditor::ViewPrivate::setSecondaryCursorsFrozen(bool freeze) { cursors()->setSecondaryFrozen(freeze); auto a = actionCollection()->action(QStringLiteral("freeze_secondary_cursors")); Q_ASSERT(a); if ( !a ) return; if ( a->isChecked() != freeze ) { a->blockSignals(true); a->setChecked(freeze); a->blockSignals(false); } } void KTextEditor::ViewPrivate::placeSecondaryCursor() { cursors()->toggleSecondaryCursorAt(cursors()->primaryCursor()); setSecondaryCursorsFrozen(true); } void KTextEditor::ViewPrivate::setupCodeFolding() { KActionCollection *ac = this->actionCollection(); QAction *a; a = ac->addAction(QStringLiteral("folding_toplevel")); a->setText(i18n("Fold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotFoldToplevelNodes())); a = ac->addAction(QLatin1String("folding_expandtoplevel")); a->setText(i18n("Unfold Toplevel Nodes")); ac->setDefaultShortcut(a, QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandToplevelNodes())); /*a = ac->addAction(QLatin1String("folding_expandall")); a->setText(i18n("Unfold All Nodes")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(expandAll())); a = ac->addAction(QLatin1String("folding_collapse_dsComment")); a->setText(i18n("Fold Multiline Comments")); connect(a, SIGNAL(triggered(bool)), m_doc->foldingTree(), SLOT(collapseAll_dsComments())); */ a = ac->addAction(QStringLiteral("folding_collapselocal")); a->setText(i18n("Fold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotCollapseLocal())); a = ac->addAction(QStringLiteral("folding_expandlocal")); a->setText(i18n("Unfold Current Node")); connect(a, SIGNAL(triggered(bool)), SLOT(slotExpandLocal())); } void KTextEditor::ViewPrivate::slotFoldToplevelNodes() { for (int line = 0; line < doc()->lines(); ++line) { if (textFolding().isLineVisible(line)) { foldLine(line); } } } void KTextEditor::ViewPrivate::slotExpandToplevelNodes() { const auto topLevelRanges(textFolding().foldingRangesForParentRange()); for (const auto &range : topLevelRanges) { textFolding().unfoldRange(range.first); } } void KTextEditor::ViewPrivate::slotCollapseLocal() { foldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::slotExpandLocal() { unfoldLine(cursorPosition().line()); } void KTextEditor::ViewPrivate::foldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to fold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().foldRange(startingRanges[i].first); } // try if the highlighting can help us and create a fold textFolding().newFoldingRange(doc()->buffer().computeFoldingRangeForStartLine(startLine), Kate::TextFolding::Folded); } void KTextEditor::ViewPrivate::unfoldLine(int startLine) { // only for valid lines if (startLine < 0 || startLine >= doc()->buffer().lines()) { return; } // try to unfold all known ranges QVector > startingRanges = textFolding().foldingRangesStartingOnLine(startLine); for (int i = 0; i < startingRanges.size(); ++i) { textFolding().unfoldRange(startingRanges[i].first); } } KTextEditor::View::ViewMode KTextEditor::ViewPrivate::viewMode() const { return currentInputMode()->viewMode(); } QString KTextEditor::ViewPrivate::viewModeHuman() const { QString currentMode = currentInputMode()->viewModeHuman(); /** * append read-only if needed */ if (!m_doc->isReadWrite()) { currentMode = i18n("(R/O) %1", currentMode); } /** * return full mode */ return currentMode; } KTextEditor::View::InputMode KTextEditor::ViewPrivate::viewInputMode() const { return currentInputMode()->viewInputMode(); } QString KTextEditor::ViewPrivate::viewInputModeHuman() const { return currentInputMode()->viewInputModeHuman(); } void KTextEditor::ViewPrivate::setInputMode(KTextEditor::View::InputMode mode) { if (currentInputMode()->viewInputMode() == mode) { return; } if (!m_viewInternal->m_inputModes.contains(mode)) { return; } m_viewInternal->m_currentInputMode->deactivate(); m_viewInternal->m_currentInputMode = m_viewInternal->m_inputModes[mode]; m_viewInternal->m_currentInputMode->activate(); config()->setInputMode(mode); // TODO: this could be called from read config procedure, so it's not a good idea to set a specific view mode here /* small duplication, but need to do this if not toggled by action */ Q_FOREACH(QAction *action, m_inputModeActions->actions()) { if (static_cast(action->data().toInt()) == mode) { action->setChecked(true); break; } } /* inform the rest of the system about the change */ emit viewInputModeChanged(this, mode); emit viewModeChanged(this, viewMode()); } void KTextEditor::ViewPrivate::slotGotFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotGotFocus"; currentInputMode()->gotFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusIn(this); } void KTextEditor::ViewPrivate::slotLostFocus() { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::slotLostFocus"; currentInputMode()->lostFocus(); /** * update current view and scrollbars * it is needed for styles that implement different frame and scrollbar * rendering when focused */ update(); if (m_viewInternal->m_lineScroll->isVisible()) { m_viewInternal->m_lineScroll->update(); } if (m_viewInternal->m_columnScroll->isVisible()) { m_viewInternal->m_columnScroll->update(); } emit focusOut(this); } void KTextEditor::ViewPrivate::setDynWrapIndicators(int mode) { config()->setDynWordWrapIndicators(mode); } bool KTextEditor::ViewPrivate::isOverwriteMode() const { return m_doc->config()->ovr(); } void KTextEditor::ViewPrivate::reloadFile() { // bookmarks and cursor positions are temporarily saved by the document m_doc->documentReload(); } void KTextEditor::ViewPrivate::slotReadWriteChanged() { if (m_toggleWriteLock) { m_toggleWriteLock->setChecked(! m_doc->isReadWrite()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_paste->setEnabled(m_doc->isReadWrite()); m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); m_setEndOfLine->setEnabled(m_doc->isReadWrite()); - QStringList l; - - l << QStringLiteral("edit_replace") - << QStringLiteral("tools_spelling") - << QStringLiteral("tools_indent") - << QStringLiteral("tools_unindent") - << QStringLiteral("tools_cleanIndent") - << QStringLiteral("tools_align") - << QStringLiteral("tools_comment") - << QStringLiteral("tools_uncomment") - << QStringLiteral("tools_toggle_comment") - << QStringLiteral("tools_uppercase") - << QStringLiteral("tools_lowercase") - << QStringLiteral("tools_capitalize") - << QStringLiteral("tools_join_lines") - << QStringLiteral("tools_apply_wordwrap") - << QStringLiteral("tools_spelling_from_cursor") - << QStringLiteral("tools_spelling_selection"); + static const QStringList l { + QStringLiteral("edit_replace") + , QStringLiteral("tools_spelling") + , QStringLiteral("tools_indent") + , QStringLiteral("tools_unindent") + , QStringLiteral("tools_cleanIndent") + , QStringLiteral("tools_align") + , QStringLiteral("tools_comment") + , QStringLiteral("tools_uncomment") + , QStringLiteral("tools_toggle_comment") + , QStringLiteral("tools_uppercase") + , QStringLiteral("tools_lowercase") + , QStringLiteral("tools_capitalize") + , QStringLiteral("tools_join_lines") + , QStringLiteral("tools_apply_wordwrap") + , QStringLiteral("tools_spelling_from_cursor") + , QStringLiteral("tools_spelling_selection") + }; foreach (const QString &action, l) { QAction *a = actionCollection()->action(action); if (a) { a->setEnabled(m_doc->isReadWrite()); } } slotUpdateUndo(); currentInputMode()->readWriteChanged(m_doc->isReadWrite()); // => view mode changed emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotClipboardHistoryChanged() { m_pasteMenu->setEnabled(m_doc->isReadWrite() && !KTextEditor::EditorPrivate::self()->clipboardHistory().isEmpty()); } void KTextEditor::ViewPrivate::slotUpdateUndo() { if (m_doc->readOnly()) { return; } m_editUndo->setEnabled(m_doc->isReadWrite() && m_doc->undoCount() > 0); m_editRedo->setEnabled(m_doc->isReadWrite() && m_doc->redoCount() > 0); } bool KTextEditor::ViewPrivate::setCursorPositionInternal(const KTextEditor::Cursor &position, uint tabwidth, bool calledExternally) { Kate::TextLine l = m_doc->kateTextLine(position.line()); if (!l) { return false; } QString line_str = m_doc->line(position.line()); int x = 0; int z = 0; for (; z < line_str.length() && z < position.column(); z++) { if (line_str[z] == QLatin1Char('\t')) { x += tabwidth - (x % tabwidth); } else { x++; } } if (blockSelection()) { if (z < position.column()) { x += position.column() - z; } } auto cur = KTextEditor::Cursor(position.line(), x); m_viewInternal->cursors()->setPrimaryCursor(cur, false); m_viewInternal->notifyPrimaryCursorChanged(cur, false, true, calledExternally); return true; } void KTextEditor::ViewPrivate::toggleInsert() { m_doc->config()->setOvr(!m_doc->config()->ovr()); m_toggleInsert->setChecked(isOverwriteMode()); emit viewModeChanged(this, viewMode()); emit viewInputModeChanged(this, viewInputMode()); } void KTextEditor::ViewPrivate::slotSaveCanceled(const QString &error) { if (!error.isEmpty()) { // happens when canceling a job KMessageBox::error(this, error); } } void KTextEditor::ViewPrivate::gotoLine() { gotoBar()->updateData(); bottomViewBar()->showBarWidget(gotoBar()); } void KTextEditor::ViewPrivate::changeDictionary() { dictionaryBar()->updateData(); bottomViewBar()->showBarWidget(dictionaryBar()); } void KTextEditor::ViewPrivate::joinLines() { int first = selectionRange().start().line(); int last = selectionRange().end().line(); //int left = m_doc->line( last ).length() - m_doc->selEndCol(); if (first == last) { first = cursorPosition().line(); last = first + 1; } m_doc->joinLines(first, last); } void KTextEditor::ViewPrivate::readSessionConfig(const KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position setCursorPositionInternal(KTextEditor::Cursor(config.readEntry("CursorLine", 0), config.readEntry("CursorColumn", 0))); // restore dyn word wrap if set for this view if (config.hasKey("Dynamic Word Wrap")) { m_config->setDynWordWrap(config.readEntry("Dynamic Word Wrap", false)); } // restore text folding m_savedFoldingState = QJsonDocument::fromJson(config.readEntry("TextFolding", QByteArray())); applyFoldingState(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->readSessionConfig(config); } } void KTextEditor::ViewPrivate::writeSessionConfig(KConfigGroup &config, const QSet &flags) { Q_UNUSED(flags) // cursor position #warning TODO multicursor config.writeEntry("CursorLine", m_viewInternal->primaryCursor().line()); config.writeEntry("CursorColumn", m_viewInternal->primaryCursor().column()); // save dyn word wrap if set for this view if (m_config->dynWordWrapSet()) { config.writeEntry("Dynamic Word Wrap", m_config->dynWordWrap()); } // save text folding state saveFoldingState(); config.writeEntry("TextFolding", m_savedFoldingState.toJson(QJsonDocument::Compact)); m_savedFoldingState = QJsonDocument(); Q_FOREACH(KateAbstractInputMode *mode, m_viewInternal->m_inputModes) { mode->writeSessionConfig(config); } } int KTextEditor::ViewPrivate::getEol() const { return m_doc->config()->eol(); } void KTextEditor::ViewPrivate::setEol(int eol) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } if (eol != m_doc->config()->eol()) { m_doc->setModified(true); // mark modified (bug #143120) m_doc->config()->setEol(eol); } } void KTextEditor::ViewPrivate::setAddBom(bool enabled) { if (!doc()->isReadWrite()) { return; } if (m_updatingDocumentConfig) { return; } m_doc->config()->setBom(enabled); m_doc->bomSetByUser(); } void KTextEditor::ViewPrivate::setIconBorder(bool enable) { config()->setIconBar(enable); } void KTextEditor::ViewPrivate::toggleIconBorder() { config()->setIconBar(!config()->iconBar()); } void KTextEditor::ViewPrivate::setLineNumbersOn(bool enable) { config()->setLineNumbers(enable); } void KTextEditor::ViewPrivate::toggleLineNumbersOn() { config()->setLineNumbers(!config()->lineNumbers()); } void KTextEditor::ViewPrivate::setScrollBarMarks(bool enable) { config()->setScrollBarMarks(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMarks() { config()->setScrollBarMarks(!config()->scrollBarMarks()); } void KTextEditor::ViewPrivate::setScrollBarMiniMap(bool enable) { config()->setScrollBarMiniMap(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMap() { config()->setScrollBarMiniMap(!config()->scrollBarMiniMap()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapAll(bool enable) { config()->setScrollBarMiniMapAll(enable); } void KTextEditor::ViewPrivate::toggleScrollBarMiniMapAll() { config()->setScrollBarMiniMapAll(!config()->scrollBarMiniMapAll()); } void KTextEditor::ViewPrivate::setScrollBarMiniMapWidth(int width) { config()->setScrollBarMiniMapWidth(width); } void KTextEditor::ViewPrivate::toggleDynWordWrap() { config()->setDynWordWrap(!config()->dynWordWrap()); } void KTextEditor::ViewPrivate::toggleWWMarker() { m_renderer->config()->setWordWrapMarker(!m_renderer->config()->wordWrapMarker()); } void KTextEditor::ViewPrivate::toggleNPSpaces() { m_renderer->setShowNonPrintableSpaces(!m_renderer->showNonPrintableSpaces()); m_viewInternal->update(); // force redraw } void KTextEditor::ViewPrivate::toggleWordCount(bool on) { config()->setShowWordCount(on); } void KTextEditor::ViewPrivate::setFoldingMarkersOn(bool enable) { config()->setFoldingBar(enable); } void KTextEditor::ViewPrivate::toggleFoldingMarkers() { config()->setFoldingBar(!config()->foldingBar()); } bool KTextEditor::ViewPrivate::iconBorder() { return m_viewInternal->m_leftBorder->iconBorderOn(); } bool KTextEditor::ViewPrivate::lineNumbersOn() { return m_viewInternal->m_leftBorder->lineNumbersOn(); } bool KTextEditor::ViewPrivate::scrollBarMarks() { return m_viewInternal->m_lineScroll->showMarks(); } bool KTextEditor::ViewPrivate::scrollBarMiniMap() { return m_viewInternal->m_lineScroll->showMiniMap(); } int KTextEditor::ViewPrivate::dynWrapIndicators() { return m_viewInternal->m_leftBorder->dynWrapIndicators(); } bool KTextEditor::ViewPrivate::foldingMarkersOn() { return m_viewInternal->m_leftBorder->foldingMarkersOn(); } void KTextEditor::ViewPrivate::toggleWriteLock() { m_doc->setReadWrite(! m_doc->isReadWrite()); } void KTextEditor::ViewPrivate::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->registerTextHintProvider(provider); } void KTextEditor::ViewPrivate::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { m_viewInternal->unregisterTextHintProvider(provider); } void KTextEditor::ViewPrivate::setTextHintDelay(int delay) { m_viewInternal->setTextHintDelay(delay); } int KTextEditor::ViewPrivate::textHintDelay() const { return m_viewInternal->textHintDelay(); } void KTextEditor::ViewPrivate::find() { currentInputMode()->find(); } void KTextEditor::ViewPrivate::findSelectedForwards() { currentInputMode()->findSelectedForwards(); } void KTextEditor::ViewPrivate::findSelectedBackwards() { currentInputMode()->findSelectedBackwards(); } void KTextEditor::ViewPrivate::replace() { currentInputMode()->findReplace(); } void KTextEditor::ViewPrivate::findNext() { currentInputMode()->findNext(); } void KTextEditor::ViewPrivate::findPrevious() { currentInputMode()->findPrevious(); } void KTextEditor::ViewPrivate::slotSelectionChanged() { m_copy->setEnabled(selection() || m_config->smartCopyCut()); m_deSelect->setEnabled(selection()); m_copyHtmlAction->setEnabled (selection()); // update highlighting of current selected word selectionChangedForHighlights (); if (m_doc->readOnly()) { return; } m_cut->setEnabled(selection() || m_config->smartCopyCut()); m_spell->updateActions(); } void KTextEditor::ViewPrivate::switchToCmdLine() { currentInputMode()->activateCommandLine(); } KateRenderer *KTextEditor::ViewPrivate::renderer() { return m_renderer; } void KTextEditor::ViewPrivate::updateConfig() { if (m_startingUp) { return; } // dyn. word wrap & markers if (m_hasWrap != config()->dynWordWrap()) { m_viewInternal->prepareForDynWrapChange(); m_hasWrap = config()->dynWordWrap(); m_viewInternal->dynWrapChanged(); m_setDynWrapIndicators->setEnabled(config()->dynWordWrap()); m_toggleDynWrap->setChecked(config()->dynWordWrap()); } m_viewInternal->m_leftBorder->setDynWrapIndicators(config()->dynWordWrapIndicators()); m_setDynWrapIndicators->setCurrentItem(config()->dynWordWrapIndicators()); // line numbers m_viewInternal->m_leftBorder->setLineNumbersOn(config()->lineNumbers()); m_toggleLineNumbers->setChecked(config()->lineNumbers()); // icon bar m_viewInternal->m_leftBorder->setIconBorderOn(config()->iconBar()); m_toggleIconBar->setChecked(config()->iconBar()); // scrollbar marks m_viewInternal->m_lineScroll->setShowMarks(config()->scrollBarMarks()); m_toggleScrollBarMarks->setChecked(config()->scrollBarMarks()); // scrollbar mini-map m_viewInternal->m_lineScroll->setShowMiniMap(config()->scrollBarMiniMap()); m_toggleScrollBarMiniMap->setChecked(config()->scrollBarMiniMap()); // scrollbar mini-map - (whole document) m_viewInternal->m_lineScroll->setMiniMapAll(config()->scrollBarMiniMapAll()); //m_toggleScrollBarMiniMapAll->setChecked( config()->scrollBarMiniMapAll() ); // scrollbar mini-map.width m_viewInternal->m_lineScroll->setMiniMapWidth(config()->scrollBarMiniMapWidth()); // misc edit m_toggleBlockSelection->setChecked(blockSelection()); m_toggleInsert->setChecked(isOverwriteMode()); updateFoldingConfig(); // bookmark m_bookmarks->setSorting((KateBookmarks::Sorting) config()->bookmarkSort()); m_viewInternal->setAutoCenterLines(config()->autoCenterLines()); Q_FOREACH(KateAbstractInputMode *input, m_viewInternal->m_inputModes) { input->updateConfig(); } setInputMode(config()->inputMode()); reflectOnTheFlySpellCheckStatus(m_doc->isOnTheFlySpellCheckingEnabled()); // register/unregister word completion... bool wc = config()->wordCompletion(); if (wc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->wordCompletionModel())) { if (wc) registerCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); else unregisterCompletionModel(KTextEditor::EditorPrivate::self()->wordCompletionModel()); } bool kc = config()->keywordCompletion(); if (kc != isCompletionModelRegistered(KTextEditor::EditorPrivate::self()->keywordCompletionModel())) { if (kc) registerCompletionModel(KTextEditor::EditorPrivate::self()->keywordCompletionModel()); else unregisterCompletionModel (KTextEditor::EditorPrivate::self()->keywordCompletionModel()); } m_cut->setEnabled(m_doc->isReadWrite() && (selection() || m_config->smartCopyCut())); m_copy->setEnabled(selection() || m_config->smartCopyCut()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); emit configChanged(); } void KTextEditor::ViewPrivate::updateDocumentConfig() { if (m_startingUp) { return; } m_updatingDocumentConfig = true; m_setEndOfLine->setCurrentItem(m_doc->config()->eol()); m_addBom->setChecked(m_doc->config()->bom()); m_updatingDocumentConfig = false; // maybe block selection or wrap-cursor mode changed ensureCursorColumnValid(); // first change this m_renderer->setTabWidth(m_doc->config()->tabWidth()); m_renderer->setIndentWidth(m_doc->config()->indentationWidth()); // now redraw... m_viewInternal->cache()->clear(); tagAll(); updateView(true); } void KTextEditor::ViewPrivate::updateRendererConfig() { if (m_startingUp) { return; } m_toggleWWMarker->setChecked(m_renderer->config()->wordWrapMarker()); m_viewInternal->updateBracketMarkAttributes(); m_viewInternal->updateBracketMarks(); // now redraw... m_viewInternal->cache()->clear(); tagAll(); m_viewInternal->updateView(true); // update the left border right, for example linenumbers m_viewInternal->m_leftBorder->updateFont(); m_viewInternal->m_leftBorder->repaint(); m_viewInternal->m_lineScroll->queuePixmapUpdate(); currentInputMode()->updateRendererConfig(); // @@ showIndentLines is not cached anymore. // m_renderer->setShowIndentLines (m_renderer->config()->showIndentationLines()); emit configChanged(); } void KTextEditor::ViewPrivate::updateFoldingConfig() { // folding bar m_viewInternal->m_leftBorder->setFoldingMarkersOn(config()->foldingBar()); m_toggleFoldingMarkers->setChecked(config()->foldingBar()); if (hasCommentInFirstLine(m_doc)) { if (config()->foldFirstLine() && !m_autoFoldedFirstLine) { foldLine(0); m_autoFoldedFirstLine = true; } else if (!config()->foldFirstLine() && m_autoFoldedFirstLine) { unfoldLine(0); m_autoFoldedFirstLine = false; } } else { m_autoFoldedFirstLine = false; } #if 0 // FIXME: FOLDING - QStringList l; - - l << "folding_toplevel" << "folding_expandtoplevel" - << "folding_collapselocal" << "folding_expandlocal"; + const QStringList l { + QStringLiteral("folding_toplevel") + , QStringLiteral("folding_expandtoplevel") + , QStringLiteral("folding_collapselocal") + , QStringLiteral("folding_expandlocal") + }; QAction *a = 0; for (int z = 0; z < l.size(); z++) if ((a = actionCollection()->action(l[z].toAscii().constData()))) { a->setEnabled(m_doc->highlight() && m_doc->highlight()->allowsFolding()); } #endif } void KTextEditor::ViewPrivate::ensureCursorColumnValid() { #warning TODO multicursor // KTextEditor::Cursor c = m_viewInternal->getCursor(); // // // make sure the cursor is valid: // // - in block selection mode or if wrap cursor is off, the column is arbitrary // // - otherwise: it's bounded by the line length // if (!blockSelection() && wrapCursor() // && (!c.isValid() || c.column() > m_doc->lineLength(c.line()))) { // c.setColumn(m_doc->kateTextLine(cursorPosition().line())->length()); // setCursorPosition(c); // } } //BEGIN EDIT STUFF void KTextEditor::ViewPrivate::editStart() { m_viewInternal->editStart(); } void KTextEditor::ViewPrivate::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { m_viewInternal->editEnd(editTagLineStart, editTagLineEnd, tagFrom); } void KTextEditor::ViewPrivate::editSetCursor(const KTextEditor::Cursor &cursor) { m_viewInternal->editSetCursor(cursor); } //END //BEGIN TAG & CLEAR bool KTextEditor::ViewPrivate::tagLine(const KTextEditor::Cursor &virtualCursor) { return m_viewInternal->tagLine(virtualCursor); } bool KTextEditor::ViewPrivate::tagRange(const KTextEditor::Range &range, bool realLines) { return m_viewInternal->tagRange(range, realLines); } bool KTextEditor::ViewPrivate::tagLines(int start, int end, bool realLines) { return m_viewInternal->tagLines(start, end, realLines); } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { return m_viewInternal->tagLines(start, end, realCursors); } void KTextEditor::ViewPrivate::tagAll() { m_viewInternal->tagAll(); } void KTextEditor::ViewPrivate::clear() { m_viewInternal->clear(); } void KTextEditor::ViewPrivate::repaintText(bool paintOnlyDirty) { if (paintOnlyDirty) { m_viewInternal->updateDirty(); } else { m_viewInternal->update(); } } void KTextEditor::ViewPrivate::updateView(bool changed) { //qCDebug(LOG_KTE) << "KTextEditor::ViewPrivate::updateView"; m_viewInternal->updateView(changed); m_viewInternal->m_leftBorder->update(); } //END void KTextEditor::ViewPrivate::slotHlChanged() { KateHighlighting *hl = m_doc->highlight(); bool ok(!hl->getCommentStart(0).isEmpty() || !hl->getCommentSingleLineStart(0).isEmpty()); if (actionCollection()->action(QStringLiteral("tools_comment"))) { actionCollection()->action(QStringLiteral("tools_comment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_uncomment"))) { actionCollection()->action(QStringLiteral("tools_uncomment"))->setEnabled(ok); } if (actionCollection()->action(QStringLiteral("tools_toggle_comment"))) { actionCollection()->action(QStringLiteral("tools_toggle_comment"))->setEnabled(ok); } // show folding bar if "view defaults" says so, otherwise enable/disable only the menu entry updateFoldingConfig(); } int KTextEditor::ViewPrivate::virtualCursorColumn() const { return m_doc->toVirtualColumn(m_viewInternal->primaryCursor()); } void KTextEditor::ViewPrivate::notifyMousePositionChanged(const KTextEditor::Cursor &newPosition) { emit mousePositionChanged(this, newPosition); } //BEGIN KTextEditor::SelectionInterface stuff bool KTextEditor::ViewPrivate::setSelection(const KTextEditor::Range &selection) { qDebug() << "called" << selection; /** * anything to do? */ if ( !selections()->hasMultipleSelections() && selection == primarySelection()) { return false; } /** * set new range; repainting is done by the selection manager * this also emits the selectionChanged() signal */ selections()->setSelection(selection); /** * be done */ return true; } bool KTextEditor::ViewPrivate::clearSelection() { return clearSelection(true); } bool KTextEditor::ViewPrivate::clearSelection(bool redraw, bool finishedChangingSelection) { /** * no selection, nothing to do... */ if (!selection()) { return false; } /** * do clear; this also emits the selectionChanged signal */ selections()->clearSelection(); /** * be done */ return true; } bool KTextEditor::ViewPrivate::selection() const { return selections()->hasSelections(); } QString KTextEditor::ViewPrivate::selectionText() const { return m_doc->text(primarySelection()); } bool KTextEditor::ViewPrivate::removeSelectedText() { if (!selection()) { return false; } m_doc->editStart(); auto sels = selections()->selections(); std::sort(sels.begin(), sels.end(), [](const Range& a, const Range& b) { return a > b; }); Q_FOREACH ( const auto& range, sels ) { m_doc->removeText(range); } // don't redraw the cleared selection - that's done in editEnd(). clearSelection(false); m_doc->editEnd(); return true; } bool KTextEditor::ViewPrivate::selectAll() { setBlockSelection(false); top(); shiftBottom(); return true; } bool KTextEditor::ViewPrivate::cursorSelected(const KTextEditor::Cursor &cursor) { return selections()->positionSelected(cursor); } bool KTextEditor::ViewPrivate::lineSelected(int line) { return selections()->lineSelected(line); } bool KTextEditor::ViewPrivate::lineEndSelected(const KTextEditor::Cursor &lineEndPos) { return selections()->lineEndSelected(lineEndPos); } bool KTextEditor::ViewPrivate::lineHasSelected(int line) { return selections()->lineHasSelection(line); } bool KTextEditor::ViewPrivate::lineIsSelection(int line) { #warning TODO fix this return ( line == primarySelection().start().line() && line == primarySelection().end().line()); } void KTextEditor::ViewPrivate::selectWord(const KTextEditor::Cursor &cursor) { setSelection(m_doc->wordRangeAt(cursor)); } void KTextEditor::ViewPrivate::selectLine(const KTextEditor::Cursor &cursor) { int line = cursor.line(); if (line + 1 >= m_doc->lines()) { setSelection(KTextEditor::Range(line, 0, line, m_doc->lineLength(line))); } else { setSelection(KTextEditor::Range(line, 0, line + 1, 0)); } } void KTextEditor::ViewPrivate::cut() { if (!selection() && !m_config->smartCopyCut()) { return; } copy(); #warning fixme: smart copy cut // if (!selection()) { // selectLine(m_viewInternal->primaryCursor()); // } removeSelectedText(); } void KTextEditor::ViewPrivate::copy() const { #warning fixme: smart copy cut // if (!selection()) { // if (!m_config->smartCopyCut()) { // return; // } // text = m_doc->line(m_viewInternal->primaryCursor().line()) + QLatin1Char('\n'); // m_viewInternal->cursors()->moveCursorsStartOfLine(); // } m_clipboard.copyToClipboard(); } void KTextEditor::ViewPrivate::applyWordWrap() { if (selection()) { m_doc->wrapText(selectionRange().start().line(), selectionRange().end().line()); } else { m_doc->wrapText(0, m_doc->lastLine()); } } //END //BEGIN KTextEditor::BlockSelectionInterface stuff bool KTextEditor::ViewPrivate::blockSelection() const { return false; } bool KTextEditor::ViewPrivate::setBlockSelection(bool /*on*/) { return toAlignedBlock(false); } bool KTextEditor::ViewPrivate::toggleBlockSelection() { auto blockSelect = selections()->hasMultipleSelections(); m_toggleBlockSelection->setChecked(!blockSelect); return setBlockSelection(!blockSelect); } bool KTextEditor::ViewPrivate::toAlignedBlock(bool fill) { auto blockSelect = !selections()->hasMultipleSelections(); if (selections()->hasSelections()) { auto s = selections()->selections(); auto blockStart = std::min_element(s.begin(), s.end(), [](const KTextEditor::Range& r1, const KTextEditor::Range& r2) { return r1.start() < r2.start(); } )->start(); auto blockEnd = std::max_element(s.begin(), s.end(), [](const KTextEditor::Range& r1, const KTextEditor::Range& r2) { return r1.end() < r2.end(); } )->end(); if (blockSelect) { if (fill) { Document::EditingTransaction tr(doc()); auto cursorColumn = blockEnd.column(); for ( int i = blockStart.line(); i <= blockEnd.line(); i++ ) { auto missing = cursorColumn - doc()->lineLength(i); if (missing > 0) { doc()->insertText({i, doc()->lineLength(i)}, QStringLiteral(" ").repeated(missing)); } } } selections()->setSelectionBlock({blockStart, blockEnd}, KateMultiCursor::Right); } else { selections()->setSelection({blockStart, blockEnd}); } } return true; } bool KTextEditor::ViewPrivate::wrapCursor() const { return !blockSelection(); } //END void KTextEditor::ViewPrivate::slotTextInserted(KTextEditor::View *view, const KTextEditor::Cursor &position, const QString &text) { emit textInserted(view, position, text); } bool KTextEditor::ViewPrivate::insertTemplateInternal(const KTextEditor::Cursor& c, const QString& templateString, const QString& script) { /** * no empty templates */ if (templateString.isEmpty()) { return false; } /** * not for read-only docs */ if (!m_doc->isReadWrite()) { return false; } /** * only one handler maybe active at a time; store it in the document. * Clear it first to make sure at no time two handlers are active at once */ doc()->setActiveTemplateHandler(nullptr); doc()->setActiveTemplateHandler(new KateTemplateHandler(this, c, templateString, script, m_doc->undoManager())); return true; } bool KTextEditor::ViewPrivate::tagLines(KTextEditor::Range range, bool realRange) { return tagLines(range.start(), range.end(), realRange); } void KTextEditor::ViewPrivate::deactivateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(false); } } void KTextEditor::ViewPrivate::activateEditActions() { foreach (QAction *action, m_editActions) { action->setEnabled(true); } } bool KTextEditor::ViewPrivate::mouseTrackingEnabled() const { // FIXME support return true; } bool KTextEditor::ViewPrivate::setMouseTrackingEnabled(bool) { // FIXME support return true; } bool KTextEditor::ViewPrivate::isCompletionActive() const { return completionWidget()->isCompletionActive(); } KateCompletionWidget *KTextEditor::ViewPrivate::completionWidget() const { if (!m_completionWidget) { m_completionWidget = new KateCompletionWidget(const_cast(this)); } return m_completionWidget; } void KTextEditor::ViewPrivate::startCompletion(const KTextEditor::Range &word, KTextEditor::CodeCompletionModel *model) { completionWidget()->startCompletion(word, model); } void KTextEditor::ViewPrivate::abortCompletion() { completionWidget()->abortCompletion(); } void KTextEditor::ViewPrivate::forceCompletion() { completionWidget()->execute(); } void KTextEditor::ViewPrivate::registerCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->registerCompletionModel(model); } void KTextEditor::ViewPrivate::unregisterCompletionModel(KTextEditor::CodeCompletionModel *model) { completionWidget()->unregisterCompletionModel(model); } bool KTextEditor::ViewPrivate::isCompletionModelRegistered(KTextEditor::CodeCompletionModel *model) const { return completionWidget()->isCompletionModelRegistered(model); } bool KTextEditor::ViewPrivate::isAutomaticInvocationEnabled() const { return !m_temporaryAutomaticInvocationDisabled && m_config->automaticCompletionInvocation(); } void KTextEditor::ViewPrivate::setAutomaticInvocationEnabled(bool enabled) { config()->setAutomaticCompletionInvocation(enabled); } void KTextEditor::ViewPrivate::sendCompletionExecuted(const KTextEditor::Cursor &position, KTextEditor::CodeCompletionModel *model, const QModelIndex &index) { emit completionExecuted(this, position, model, index); } void KTextEditor::ViewPrivate::sendCompletionAborted() { emit completionAborted(this); } void KTextEditor::ViewPrivate::paste() { m_temporaryAutomaticInvocationDisabled = true; m_clipboard.pasteFromClipboard(QClipboard::Clipboard); m_temporaryAutomaticInvocationDisabled = false; } void KTextEditor::ViewPrivate::pasteInternal(const QVector& texts) { m_clipboard.pasteVector(texts); } bool KTextEditor::ViewPrivate::setCursorPosition(KTextEditor::Cursor position) { return setCursorPositionInternal(position, 1, true); } KateMultiSelection* KTextEditor::ViewPrivate::selections() { return m_viewInternal->selections(); } const KateMultiSelection* KTextEditor::ViewPrivate::selections() const { return m_viewInternal->selections(); } bool KTextEditor::ViewPrivate::setSelections(const QVector& newSelections, const QVector& newCursors) { if ( !newCursors.isEmpty() && (newSelections.size() != newCursors.size()) ) { Q_ASSERT(false); qWarning() << "mismatching cursor/selection size"; return false; } if ( std::any_of(newCursors.begin(), newCursors.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( newSelections.isEmpty() ) { bool ret = selections()->hasSelections(); selections()->clearSelection(); return ret; } selections()->setSelection(newSelections, newCursors); return true; } bool KTextEditor::ViewPrivate::setCursorPositions(const QVector& positions) { if ( std::any_of(positions.begin(), positions.end(), [](const KTextEditor::Cursor& c) { return !c.isValid(); }) ) { return false; } if ( positions.isEmpty() ) { return false; } auto s = QVector(); s.resize(positions.size()); selections()->setSelection(s, positions); return true; } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPosition() const { return m_viewInternal->primaryCursor(); } QVector KTextEditor::ViewPrivate::cursorPositions() const { return cursors()->cursors(); } const KateMultiCursor* KTextEditor::ViewPrivate::cursors() const { return m_viewInternal->cursors(); } KTextEditor::Cursor KTextEditor::ViewPrivate::cursorPositionVirtual() const { return KTextEditor::Cursor(m_viewInternal->primaryCursor().line(), virtualCursorColumn()); } QPoint KTextEditor::ViewPrivate::cursorToCoordinate(const KTextEditor::Cursor &cursor) const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorToCoordinate(cursor, true, false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } KTextEditor::Cursor KTextEditor::ViewPrivate::coordinatesToCursor(const QPoint &coords) const { // map from View to ViewInternal coordinates return m_viewInternal->coordinatesToCursor(m_viewInternal->mapFromParent(coords), false); } QPoint KTextEditor::ViewPrivate::cursorPositionCoordinates() const { // map from ViewInternal to View coordinates const QPoint pt = m_viewInternal->cursorCoordinates(false); return pt == QPoint(-1, -1) ? pt : m_viewInternal->mapToParent(pt); } void KTextEditor::ViewPrivate::setScrollPositionInternal(KTextEditor::Cursor &cursor) { m_viewInternal->scrollPos(cursor, false, true, false); } void KTextEditor::ViewPrivate::setHorizontalScrollPositionInternal(int x) { m_viewInternal->scrollColumns(x); } KTextEditor::Cursor KTextEditor::ViewPrivate::maxScrollPositionInternal() const { return m_viewInternal->maxStartPos(true); } int KTextEditor::ViewPrivate::firstDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->startLine()); } else { return m_viewInternal->startLine(); } } int KTextEditor::ViewPrivate::lastDisplayedLineInternal(LineType lineType) const { if (lineType == RealLine) { return m_textFolding.visibleLineToLine(m_viewInternal->endLine()); } else { return m_viewInternal->endLine(); } } QRect KTextEditor::ViewPrivate::textAreaRectInternal() const { const auto sourceRect = m_viewInternal->rect(); const auto topLeft = m_viewInternal->mapTo(this, sourceRect.topLeft()); const auto bottomRight = m_viewInternal->mapTo(this, sourceRect.bottomRight()); return {topLeft, bottomRight}; } bool KTextEditor::ViewPrivate::setCursorPositionVisual(const KTextEditor::Cursor &position) { return setCursorPositionInternal(position, m_doc->config()->tabWidth(), true); } QString KTextEditor::ViewPrivate::currentTextLine() { return m_doc->line(cursorPosition().line()); } QTextLayout * KTextEditor::ViewPrivate::textLayout(int line) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(line); return thisLine->isValid() ? thisLine->layout() : nullptr; } QTextLayout * KTextEditor::ViewPrivate::textLayout(const KTextEditor::Cursor &pos) const { KateLineLayoutPtr thisLine = m_viewInternal->cache()->line(pos); return thisLine->isValid() ? thisLine->layout() : nullptr; } void KTextEditor::ViewPrivate::indent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 1); } void KTextEditor::ViewPrivate::unIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, -1); } void KTextEditor::ViewPrivate::cleanIndent() { KTextEditor::Cursor c(cursorPosition().line(), 0); KTextEditor::Range r = selection() ? selectionRange() : KTextEditor::Range(c, c); m_doc->indent(r, 0); } void KTextEditor::ViewPrivate::align() { // no selection: align current line; selection: use selection range const int line = cursorPosition().line(); KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); if (selection()) { alignRange = selectionRange(); } m_doc->align(this, alignRange); } void KTextEditor::ViewPrivate::comment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 1); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uncomment() { m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), -1); } void KTextEditor::ViewPrivate::toggleComment() { #warning fixme // m_selection.setInsertBehaviors(Kate::TextRange::ExpandLeft | Kate::TextRange::ExpandRight); m_doc->comment(this, cursorPosition().line(), cursorPosition().column(), 0); // m_selection.setInsertBehaviors(Kate::TextRange::ExpandRight); } void KTextEditor::ViewPrivate::uppercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Uppercase); } void KTextEditor::ViewPrivate::killLine() { QSet lines; for ( const auto& cursor : cursors()->cursors() ) { auto selection = selections()->selectionForCursor(cursor)->toRange(); if ( selection.isValid() && !selection.isEmpty() ) { for ( int i = selection.start().line(); i <= selection.end().line(); i++ ) { lines.insert(i); } } else { lines.insert(cursor.line()); } } auto linesVector = lines.toList(); std::sort(linesVector.rbegin(), linesVector.rend()); m_doc->editStart(); for ( auto line : linesVector ) { m_doc->removeLine(line); } m_doc->editEnd(); } void KTextEditor::ViewPrivate::lowercase() { m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); } void KTextEditor::ViewPrivate::capitalize() { m_doc->editStart(); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Lowercase); m_doc->transform(this, m_viewInternal->primaryCursor(), KTextEditor::DocumentPrivate::Capitalize); m_doc->editEnd(); } void KTextEditor::ViewPrivate::keyReturn() { m_viewInternal->doReturn(); } void KTextEditor::ViewPrivate::smartNewline() { m_viewInternal->doSmartNewline(); } void KTextEditor::ViewPrivate::backspace() { m_viewInternal->doBackspace(); } void KTextEditor::ViewPrivate::insertTab() { m_viewInternal->doTabulator(); } void KTextEditor::ViewPrivate::deleteWordLeft() { m_viewInternal->doDeletePrevWord(); } void KTextEditor::ViewPrivate::keyDelete() { m_viewInternal->doDelete(); } void KTextEditor::ViewPrivate::deleteWordRight() { m_viewInternal->doDeleteNextWord(); } void KTextEditor::ViewPrivate::transpose() { m_viewInternal->doTranspose(); } void KTextEditor::ViewPrivate::cursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(); } else { m_viewInternal->cursorPrevChar(); } } void KTextEditor::ViewPrivate::shiftCursorLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorNextChar(true); } else { m_viewInternal->cursorPrevChar(true); } } void KTextEditor::ViewPrivate::cursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(); } else { m_viewInternal->cursorNextChar(); } } void KTextEditor::ViewPrivate::shiftCursorRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->cursorPrevChar(true); } else { m_viewInternal->cursorNextChar(true); } } void KTextEditor::ViewPrivate::wordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(); } else { m_viewInternal->wordPrev(); } } void KTextEditor::ViewPrivate::shiftWordLeft() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordNext(true); } else { m_viewInternal->wordPrev(true); } } void KTextEditor::ViewPrivate::wordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(); } else { m_viewInternal->wordNext(); } } void KTextEditor::ViewPrivate::shiftWordRight() { if (m_viewInternal->m_view->currentTextLine().isRightToLeft()) { m_viewInternal->wordPrev(true); } else { m_viewInternal->wordNext(true); } } void KTextEditor::ViewPrivate::home() { m_viewInternal->home(); } void KTextEditor::ViewPrivate::shiftHome() { m_viewInternal->home(true); } void KTextEditor::ViewPrivate::end() { m_viewInternal->end(); } void KTextEditor::ViewPrivate::shiftEnd() { m_viewInternal->end(true); } void KTextEditor::ViewPrivate::up() { m_viewInternal->cursorUp(); } void KTextEditor::ViewPrivate::shiftUp() { m_viewInternal->cursorUp(true); } void KTextEditor::ViewPrivate::down() { m_viewInternal->cursorDown(); } void KTextEditor::ViewPrivate::shiftDown() { m_viewInternal->cursorDown(true); } void KTextEditor::ViewPrivate::scrollUp() { m_viewInternal->scrollUp(); } void KTextEditor::ViewPrivate::scrollDown() { m_viewInternal->scrollDown(); } void KTextEditor::ViewPrivate::topOfView() { m_viewInternal->topOfView(); } void KTextEditor::ViewPrivate::shiftTopOfView() { m_viewInternal->topOfView(true); } void KTextEditor::ViewPrivate::bottomOfView() { m_viewInternal->bottomOfView(); } void KTextEditor::ViewPrivate::shiftBottomOfView() { m_viewInternal->bottomOfView(true); } void KTextEditor::ViewPrivate::pageUp() { m_viewInternal->pageUp(); } void KTextEditor::ViewPrivate::shiftPageUp() { m_viewInternal->pageUp(true); } void KTextEditor::ViewPrivate::pageDown() { m_viewInternal->pageDown(); } void KTextEditor::ViewPrivate::shiftPageDown() { m_viewInternal->pageDown(true); } void KTextEditor::ViewPrivate::top() { m_viewInternal->top_home(); } void KTextEditor::ViewPrivate::shiftTop() { m_viewInternal->top_home(true); } void KTextEditor::ViewPrivate::bottom() { m_viewInternal->bottom_end(); } void KTextEditor::ViewPrivate::shiftBottom() { m_viewInternal->bottom_end(true); } void KTextEditor::ViewPrivate::toMatchingBracket() { cursors()->clearSecondaryCursors(); m_viewInternal->cursorToMatchingBracket(); } void KTextEditor::ViewPrivate::shiftToMatchingBracket() { m_viewInternal->cursorToMatchingBracket(true); } void KTextEditor::ViewPrivate::toPrevModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() - 1; const int line = m_doc->findTouchedLine(startLine, false); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } void KTextEditor::ViewPrivate::toNextModifiedLine() { const int startLine = m_viewInternal->primaryCursor().line() + 1; const int line = m_doc->findTouchedLine(startLine, true); if (line >= 0) { KTextEditor::Cursor c(line, 0); m_viewInternal->updateSelection(c, false); m_viewInternal->cursors()->setPrimaryCursor(c); } } KTextEditor::Range KTextEditor::ViewPrivate::selectionRange() const { return primarySelection(); } QVector KTextEditor::ViewPrivate::selectionRanges() const { return selections()->selections(); } KTextEditor::Document *KTextEditor::ViewPrivate::document() const { return m_doc; } void KTextEditor::ViewPrivate::setContextMenu(QMenu *menu) { if (m_contextMenu) { disconnect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } m_contextMenu = menu; m_userContextMenuSet = true; if (m_contextMenu) { connect(m_contextMenu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(m_contextMenu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); } } QMenu *KTextEditor::ViewPrivate::contextMenu() const { if (m_userContextMenuSet) { return m_contextMenu; } else { KXMLGUIClient *client = const_cast(this); while (client->parentClient()) { client = client->parentClient(); } //qCDebug(LOG_KTE) << "looking up all menu containers"; if (client->factory()) { QList conts = client->factory()->containers(QStringLiteral("menu")); foreach (QWidget *w, conts) { if (w->objectName() == QLatin1String("ktexteditor_popup")) { //perhaps optimize this block QMenu *menu = (QMenu *)w; disconnect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); disconnect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); connect(menu, SIGNAL(aboutToShow()), this, SLOT(aboutToShowContextMenu())); connect(menu, SIGNAL(aboutToHide()), this, SLOT(aboutToHideContextMenu())); return menu; } } } } return nullptr; } QMenu *KTextEditor::ViewPrivate::defaultContextMenu(QMenu *menu) const { if (!menu) { menu = new QMenu(const_cast(this)); } menu->addAction(m_editUndo); menu->addAction(m_editRedo); menu->addSeparator(); menu->addAction(m_cut); menu->addAction(m_copy); menu->addAction(m_paste); menu->addSeparator(); menu->addAction(m_selectAll); menu->addAction(m_deSelect); if (QAction *spellingSuggestions = actionCollection()->action(QStringLiteral("spelling_suggestions"))) { menu->addSeparator(); menu->addAction(spellingSuggestions); } if (QAction *bookmark = actionCollection()->action(QStringLiteral("bookmarks"))) { menu->addSeparator(); menu->addAction(bookmark); } return menu; } void KTextEditor::ViewPrivate::aboutToShowContextMenu() { QMenu *menu = qobject_cast(sender()); if (menu) { emit contextMenuAboutToShow(this, menu); } } void KTextEditor::ViewPrivate::aboutToHideContextMenu() { m_spellingMenu->setUseMouseForMisspelledRange(false); } // BEGIN ConfigInterface stff QStringList KTextEditor::ViewPrivate::configKeys() const { static const QStringList keys = { QStringLiteral("icon-bar"), QStringLiteral("line-numbers"), QStringLiteral("dynamic-word-wrap"), QStringLiteral("background-color"), QStringLiteral("selection-color"), QStringLiteral("search-highlight-color"), QStringLiteral("replace-highlight-color"), QStringLiteral("default-mark-type"), QStringLiteral("allow-mark-menu"), QStringLiteral("folding-bar"), QStringLiteral("folding-preview"), QStringLiteral("icon-border-color"), QStringLiteral("folding-marker-color"), QStringLiteral("line-number-color"), QStringLiteral("current-line-number-color"), QStringLiteral("modification-markers"), QStringLiteral("keyword-completion"), QStringLiteral("word-count"), QStringLiteral("scrollbar-minimap"), QStringLiteral("scrollbar-preview"), QStringLiteral("font") }; return keys; } QVariant KTextEditor::ViewPrivate::configValue(const QString &key) { if (key == QLatin1String("icon-bar")) { return config()->iconBar(); } else if (key == QLatin1String("line-numbers")) { return config()->lineNumbers(); } else if (key == QLatin1String("dynamic-word-wrap")) { return config()->dynWordWrap(); } else if (key == QLatin1String("background-color")) { return renderer()->config()->backgroundColor(); } else if (key == QLatin1String("selection-color")) { return renderer()->config()->selectionColor(); } else if (key == QLatin1String("search-highlight-color")) { return renderer()->config()->searchHighlightColor(); } else if (key == QLatin1String("replace-highlight-color")) { return renderer()->config()->replaceHighlightColor(); } else if (key == QLatin1String("default-mark-type")) { return config()->defaultMarkType(); } else if (key == QLatin1String("allow-mark-menu")) { return config()->allowMarkMenu(); } else if (key == QLatin1String("folding-bar")) { return config()->foldingBar(); } else if (key == QLatin1String("folding-preview")) { return config()->foldingPreview(); } else if (key == QLatin1String("icon-border-color")) { return renderer()->config()->iconBarColor(); } else if (key == QLatin1String("folding-marker-color")) { return renderer()->config()->foldingColor(); } else if (key == QLatin1String("line-number-color")) { return renderer()->config()->lineNumberColor(); } else if (key == QLatin1String("current-line-number-color")) { return renderer()->config()->currentLineNumberColor(); } else if (key == QLatin1String("modification-markers")) { return config()->lineModification(); } else if (key == QLatin1String("keyword-completion")) { return config()->keywordCompletion(); } else if (key == QLatin1String("word-count")) { return config()->showWordCount(); } else if (key == QLatin1String("scrollbar-minimap")) { return config()->scrollBarMiniMap(); } else if (key == QLatin1String("scrollbar-preview")) { return config()->scrollBarPreview(); } else if (key == QLatin1String("font")) { return renderer()->config()->font(); } // return invalid variant return QVariant(); } void KTextEditor::ViewPrivate::setConfigValue(const QString &key, const QVariant &value) { if (value.canConvert(QVariant::Color)) { if (key == QLatin1String("background-color")) { renderer()->config()->setBackgroundColor(value.value()); } else if (key == QLatin1String("selection-color")) { renderer()->config()->setSelectionColor(value.value()); } else if (key == QLatin1String("search-highlight-color")) { renderer()->config()->setSearchHighlightColor(value.value()); } else if (key == QLatin1String("replace-highlight-color")) { renderer()->config()->setReplaceHighlightColor(value.value()); } else if (key == QLatin1String("icon-border-color")) { renderer()->config()->setIconBarColor(value.value()); } else if (key == QLatin1String("folding-marker-color")) { renderer()->config()->setFoldingColor(value.value()); } else if (key == QLatin1String("line-number-color")) { renderer()->config()->setLineNumberColor(value.value()); } else if (key == QLatin1String("current-line-number-color")) { renderer()->config()->setCurrentLineNumberColor(value.value()); } } else if (value.type() == QVariant::Bool) { // Note explicit type check above. If we used canConvert, then // values of type UInt will be trapped here. if (key == QLatin1String("icon-bar")) { config()->setIconBar(value.toBool()); } else if (key == QLatin1String("line-numbers")) { config()->setLineNumbers(value.toBool()); } else if (key == QLatin1String("dynamic-word-wrap")) { config()->setDynWordWrap(value.toBool()); } else if (key == QLatin1String("allow-mark-menu")) { config()->setAllowMarkMenu(value.toBool()); } else if (key == QLatin1String("folding-bar")) { config()->setFoldingBar(value.toBool()); } else if (key == QLatin1String("folding-preview")) { config()->setFoldingPreview(value.toBool()); } else if (key == QLatin1String("modification-markers")) { config()->setLineModification(value.toBool()); } else if (key == QLatin1String("keyword-completion")) { config()->setKeywordCompletion(value.toBool()); } else if (key == QLatin1String("word-count")) { config()->setShowWordCount(value.toBool()); } else if (key == QLatin1String("scrollbar-minimap")) { config()->setScrollBarMiniMap(value.toBool()); } else if (key == QLatin1String("scrollbar-preview")) { config()->setScrollBarPreview(value.toBool()); } } else if (value.canConvert(QVariant::UInt)) { if (key == QLatin1String("default-mark-type")) { config()->setDefaultMarkType(value.toUInt()); } } else if (value.canConvert(QVariant::Font)) { if (key == QLatin1String("font")) { renderer()->config()->setFont(value.value()); } } } // END ConfigInterface void KTextEditor::ViewPrivate::userInvokedCompletion() { completionWidget()->userInvokedCompletion(); } KateViewBar *KTextEditor::ViewPrivate::bottomViewBar() const { return m_bottomViewBar; } KateGotoBar *KTextEditor::ViewPrivate::gotoBar() { if (!m_gotoBar) { m_gotoBar = new KateGotoBar(this); bottomViewBar()->addBarWidget(m_gotoBar); } return m_gotoBar; } KateDictionaryBar *KTextEditor::ViewPrivate::dictionaryBar() { if (!m_dictionaryBar) { m_dictionaryBar = new KateDictionaryBar(this); bottomViewBar()->addBarWidget(m_dictionaryBar); } return m_dictionaryBar; } void KTextEditor::ViewPrivate::setAnnotationModel(KTextEditor::AnnotationModel *model) { KTextEditor::AnnotationModel *oldmodel = m_annotationModel; m_annotationModel = model; m_viewInternal->m_leftBorder->annotationModelChanged(oldmodel, m_annotationModel); } KTextEditor::AnnotationModel *KTextEditor::ViewPrivate::annotationModel() const { return m_annotationModel; } void KTextEditor::ViewPrivate::setAnnotationBorderVisible(bool visible) { m_viewInternal->m_leftBorder->setAnnotationBorderOn(visible); if ( !visible ) { // make sure the tooltip is hidden QToolTip::hideText(); } } bool KTextEditor::ViewPrivate::isAnnotationBorderVisible() const { return m_viewInternal->m_leftBorder->annotationBorderOn(); } KTextEditor::Range KTextEditor::ViewPrivate::visibleRange() { //ensure that the view is up-to-date, otherwise 'endPos()' might fail! m_viewInternal->updateView(); return KTextEditor::Range(m_viewInternal->toRealCursor(m_viewInternal->startPos()), m_viewInternal->toRealCursor(m_viewInternal->endPos())); } bool KTextEditor::ViewPrivate::event(QEvent *e) { switch (e->type()) { case QEvent::StyleChange: setupLayout(); return true; default: return KTextEditor::View::event(e); } } void KTextEditor::ViewPrivate::paintEvent(QPaintEvent *e) { //base class KTextEditor::View::paintEvent(e); const QRect contentsRect = m_topSpacer->geometry()| m_bottomSpacer->geometry()| m_leftSpacer->geometry()| m_rightSpacer->geometry(); if (contentsRect.isValid()) { QStyleOptionFrame opt; opt.initFrom(this); opt.frameShape = QFrame::StyledPanel; opt.state |= QStyle::State_Sunken; // clear mouseOver and focus state // update from relevant widgets opt.state &= ~(QStyle::State_HasFocus|QStyle::State_MouseOver); const QList widgets = QList() << m_viewInternal << m_viewInternal->m_leftBorder << m_viewInternal->m_lineScroll << m_viewInternal->m_columnScroll; foreach (const QWidget *w, widgets) { if (w->hasFocus()) opt.state |= QStyle::State_HasFocus; if (w->underMouse()) opt.state |= QStyle::State_MouseOver; } // update rect opt.rect=contentsRect; // render QPainter paint(this); paint.setClipRegion(e->region()); paint.setRenderHints(QPainter::Antialiasing); style()->drawControl(QStyle::CE_ShapedFrame, &opt, &paint, this); } } void KTextEditor::ViewPrivate::toggleOnTheFlySpellCheck(bool b) { m_doc->onTheFlySpellCheckingEnabled(b); } void KTextEditor::ViewPrivate::reflectOnTheFlySpellCheckStatus(bool enabled) { m_spellingMenu->setVisible(enabled); m_toggleOnTheFlySpellCheck->setChecked(enabled); } KateSpellingMenu *KTextEditor::ViewPrivate::spellingMenu() { return m_spellingMenu; } void KTextEditor::ViewPrivate::notifyAboutRangeChange(int startLine, int endLine, bool rangeWithAttribute) { #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "trigger attribute changed from" << startLine << "to" << endLine << "rangeWithAttribute" << rangeWithAttribute; #endif // first call: if (!m_delayedUpdateTriggered) { m_delayedUpdateTriggered = true; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; // only set initial line range, if range with attribute! if (rangeWithAttribute) { m_lineToUpdateMin = startLine; m_lineToUpdateMax = endLine; } // emit queued signal and be done emit delayedUpdateOfView(); return; } // ignore lines if no attribute if (!rangeWithAttribute) { return; } // update line range if (startLine != -1 && (m_lineToUpdateMin == -1 || startLine < m_lineToUpdateMin)) { m_lineToUpdateMin = startLine; } if (endLine != -1 && endLine > m_lineToUpdateMax) { m_lineToUpdateMax = endLine; } } void KTextEditor::ViewPrivate::slotDelayedUpdateOfView() { if (!m_delayedUpdateTriggered) { return; } #ifdef VIEW_RANGE_DEBUG // output args qCDebug(LOG_KTE) << "delayed attribute changed from" << m_lineToUpdateMin << "to" << m_lineToUpdateMax; #endif // update ranges in updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); // update view, if valid line range, else only feedback update wanted anyway if (m_lineToUpdateMin != -1 && m_lineToUpdateMax != -1) { tagLines(m_lineToUpdateMin, m_lineToUpdateMax, true); updateView(true); } // reset flags m_delayedUpdateTriggered = false; m_lineToUpdateMin = -1; m_lineToUpdateMax = -1; } void KTextEditor::ViewPrivate::updateRangesIn(KTextEditor::Attribute::ActivationType activationType) { // new ranges with cursor in, default none QSet newRangesIn; // on which range set we work? QSet &oldSet = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_rangesMouseIn : m_rangesCaretIn; // which cursor position to honor? KTextEditor::Cursor currentCursor = (activationType == KTextEditor::Attribute::ActivateMouseIn) ? m_viewInternal->getMouse() : m_viewInternal->primaryCursor(); // first: validate the remembered ranges QSet validRanges; foreach (Kate::TextRange *range, oldSet) if (m_doc->buffer().rangePointerValid(range)) { validRanges.insert(range); } // cursor valid? else no new ranges can be found if (currentCursor.isValid() && currentCursor.line() < m_doc->buffer().lines()) { // now: get current ranges for the line of cursor with an attribute QList rangesForCurrentCursor = m_doc->buffer().rangesForLine(currentCursor.line(), this, false); // match which ranges really fit the given cursor foreach (Kate::TextRange *range, rangesForCurrentCursor) { // range has no dynamic attribute of right type and no feedback object if ((!range->attribute() || !range->attribute()->dynamicAttribute(activationType)) && !range->feedback()) { continue; } // range doesn't contain cursor, not interesting if ((range->start().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (currentCursor < range->start().toCursor()) : (currentCursor <= range->start().toCursor())) { continue; } if ((range->end().insertBehavior() == KTextEditor::MovingCursor::StayOnInsert) ? (range->end().toCursor() <= currentCursor) : (range->end().toCursor() < currentCursor)) { continue; } // range contains cursor, was it already in old set? if (validRanges.contains(range)) { // insert in new, remove from old, be done with it newRangesIn.insert(range); validRanges.remove(range); continue; } // oh, new range, trigger update and insert into new set newRangesIn.insert(range); if (range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseEnteredRange(range, this); } else { range->feedback()->caretEnteredRange(range, this); } } #ifdef VIEW_RANGE_DEBUG // found new range for activation qCDebug(LOG_KTE) << "activated new range" << range << "by" << activationType; #endif } } // now: notify for left ranges! foreach (Kate::TextRange *range, validRanges) { // range valid + right dynamic attribute, trigger update if (range->toRange().isValid() && range->attribute() && range->attribute()->dynamicAttribute(activationType)) { notifyAboutRangeChange(range->start().line(), range->end().line(), true); } // feedback if (range->feedback()) { if (activationType == KTextEditor::Attribute::ActivateMouseIn) { range->feedback()->mouseExitedRange(range, this); } else { range->feedback()->caretExitedRange(range, this); } } } // set new ranges oldSet = newRangesIn; } void KTextEditor::ViewPrivate::postMessage(KTextEditor::Message *message, QList > actions) { // just forward to KateMessageWidget :-) if (message->position() == KTextEditor::Message::AboveView) { m_topMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::BelowView) { m_bottomMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::TopInView) { if (!m_floatTopMessageWidget) { m_floatTopMessageWidget = new KateMessageWidget(m_viewInternal, true); m_notificationLayout->insertWidget(0, m_floatTopMessageWidget, 0, Qt::Alignment(Qt::AlignTop | Qt::AlignRight)); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatTopMessageWidget, SLOT(startAutoHideTimer())); } m_floatTopMessageWidget->postMessage(message, actions); } else if (message->position() == KTextEditor::Message::BottomInView) { if (!m_floatBottomMessageWidget) { m_floatBottomMessageWidget = new KateMessageWidget(m_viewInternal, true); m_notificationLayout->addWidget(m_floatBottomMessageWidget, 0, Qt::Alignment(Qt::AlignBottom | Qt::AlignRight)); connect(this, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); connect(this, SIGNAL(cursorPositionChanged(KTextEditor::View*,KTextEditor::Cursor)), m_floatBottomMessageWidget, SLOT(startAutoHideTimer())); } m_floatBottomMessageWidget->postMessage(message, actions); } } void KTextEditor::ViewPrivate::saveFoldingState() { m_savedFoldingState = m_textFolding.exportFoldingRanges(); } void KTextEditor::ViewPrivate::applyFoldingState() { m_textFolding.importFoldingRanges(m_savedFoldingState); m_savedFoldingState = QJsonDocument(); } void KTextEditor::ViewPrivate::exportHtmlToFile(const QString &file) { KateExporter(this).exportToFile(file); } void KTextEditor::ViewPrivate::exportHtmlToClipboard () { KateExporter(this).exportToClipboard(); } void KTextEditor::ViewPrivate::exportHtmlToFile () { const QString file = QFileDialog::getSaveFileName(this, i18n("Export File as HTML"), m_doc->documentName()); if (!file.isEmpty()) { KateExporter(this).exportToFile(file); } } void KTextEditor::ViewPrivate::clearHighlights() { qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); m_currentTextForHighlights.clear(); } void KTextEditor::ViewPrivate::selectionChangedForHighlights() { QString text; // if text of selection is still the same, abort if (selection() && selectionRange().onSingleLine()) { text = selectionText(); if (text == m_currentTextForHighlights) return; } // text changed: remove all highlights + create new ones // (do not call clearHighlights(), since this also resets the m_currentTextForHighlights qDeleteAll(m_rangesForHighlights); m_rangesForHighlights.clear(); // do not highlight strings with leading and trailing spaces if (!text.isEmpty() && (text.at(0).isSpace() || text.at(text.length()-1).isSpace())) return; // trigger creation of ranges for current view range m_currentTextForHighlights = text; createHighlights(); } void KTextEditor::ViewPrivate::createHighlights() { // do nothing if no text to highlight if (m_currentTextForHighlights.isEmpty()) { return; } KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); attr->setBackground(Qt::yellow); // set correct highlight color from Kate's color schema QColor fgColor = defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); QColor bgColor = renderer()->config()->searchHighlightColor(); attr->setForeground(fgColor); attr->setBackground(bgColor); KTextEditor::Cursor start(visibleRange().start()); KTextEditor::Range searchRange; /** * only add word boundary if we can find the text then * fixes $lala hl */ QString regex = QRegExp::escape (m_currentTextForHighlights); if (QRegExp (QStringLiteral("\\b%1").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("\\b%1").arg(regex); if (QRegExp (QStringLiteral("%1\\b").arg(regex)).indexIn (QStringLiteral(" %1 ").arg(m_currentTextForHighlights)) != -1) regex = QStringLiteral("%1\\b").arg(regex); QVector matches; do { searchRange.setRange(start, visibleRange().end()); matches = m_doc->searchText(searchRange, regex, KTextEditor::Regex); if (matches.first().isValid()) { KTextEditor::MovingRange* mr = m_doc->newMovingRange(matches.first()); mr->setAttribute(attr); mr->setView(this); mr->setZDepth(-90000.0); // Set the z-depth to slightly worse than the selection mr->setAttributeOnlyForViews(true); m_rangesForHighlights.append(mr); start = matches.first().end(); } } while (matches.first().isValid()); } KateAbstractInputMode *KTextEditor::ViewPrivate::currentInputMode() const { return m_viewInternal->m_currentInputMode; } void KTextEditor::ViewPrivate::toggleInputMode() { if (QAction *a = dynamic_cast(sender())) { setInputMode(static_cast(a->data().toInt())); } } void KTextEditor::ViewPrivate::cycleInputMode() { InputMode current = currentInputMode()->viewInputMode(); InputMode to = (current == KTextEditor::View::NormalInputMode) ? KTextEditor::View::ViInputMode : KTextEditor::View::NormalInputMode; setInputMode(to); } //BEGIN KTextEditor::PrintInterface stuff bool KTextEditor::ViewPrivate::print() { return KatePrinter::print(this); } void KTextEditor::ViewPrivate::printPreview() { KatePrinter::printPreview(this); } //END // void KTextEditor::ViewPrivate::moveSecondaryCursors(int chars) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // if ( blockSelection() ) { // c->setPosition(c->line(), qMax(0, c->column() + chars)); // } // else { // c->move(chars, KTextEditor::MovingCursor::Wrap); // } // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::moveSecondaryCursorsVertically(int lines) // { // Q_FOREACH ( auto c, m_secondaryCursors ) { // const auto oldColumn = c->column(); // c->setLine(qMin(qMax(0, c->line() + lines), doc()->lines())); // auto newColumn = blockSelection() ? oldColumn : qMin(c->document()->lineLength(c->line()), oldColumn); // c->setColumn(newColumn); // Q_ASSERT(c->toCursor().isValid()); // } // } // // void KTextEditor::ViewPrivate::updateSecondaryCursorsPositions(std::function update) // { // Q_FOREACH ( const auto& c, m_secondaryCursors ) { // c->setPosition(update(c->toCursor())); // Q_ASSERT(c->toCursor().isValid()); // } // } KTextEditor::Attribute::Ptr KTextEditor::ViewPrivate::defaultStyleAttribute(KTextEditor::DefaultStyle defaultStyle) const { KateRendererConfig * renderConfig = const_cast(this)->renderer()->config(); KTextEditor::Attribute::Ptr style = m_doc->highlight()->attributes(renderConfig->schema()).at(defaultStyle); if (!style->hasProperty(QTextFormat::BackgroundBrush)) { // make sure the returned style has the default background color set style = new KTextEditor::Attribute(*style); style->setBackground(QBrush(renderConfig->backgroundColor())); } return style; } QList KTextEditor::ViewPrivate::lineAttributes(int line) { QList attribs; if (line < 0 || line >= m_doc->lines()) return attribs; Kate::TextLine kateLine = m_doc->kateTextLine(line); if (!kateLine) { return attribs; } const QVector &intAttrs = kateLine->attributesList(); for (int i = 0; i < intAttrs.size(); ++i) { if (intAttrs[i].length > 0 && intAttrs[i].attributeValue > 0) { attribs << KTextEditor::AttributeBlock( intAttrs.at(i).offset, intAttrs.at(i).length, renderer()->attribute(intAttrs.at(i).attributeValue) ); } } return attribs; } KateMultiCursor * KTextEditor::ViewPrivate::cursors() { return m_viewInternal->cursors(); } Cursors KTextEditor::ViewPrivate::allCursors() { return cursors()->cursors(); } diff --git a/src/view/kateviewhelpers.cpp b/src/view/kateviewhelpers.cpp index 3fff7733..694ecb9d 100644 --- a/src/view/kateviewhelpers.cpp +++ b/src/view/kateviewhelpers.cpp @@ -1,2814 +1,2813 @@ /* This file is part of the KDE libraries Copyright (C) 2008, 2009 Matthew Woehlke Copyright (C) 2007 Mirko Stocker Copyright (C) 2002 John Firebaugh Copyright (C) 2001 Anders Lund Copyright (C) 2001 Christoph Cullmann Copyright (C) 2011 Svyatoslav Kuzmich Copyright (C) 2012 Kåre Särs (Minimap) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewhelpers.h" #include "katecmd.h" #include #include #include #include "kateconfig.h" #include "katedocument.h" #include #include "katerenderer.h" #include "kateview.h" #include "kateviewinternal.h" #include "katelayoutcache.h" #include "katetextlayout.h" #include "kateglobal.h" #include "katepartdebug.h" #include "katecommandrangeexpressionparser.h" #include "kateabstractinputmode.h" #include "katetextpreview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN KateScrollBar static const int s_lineWidth = 100; static const int s_pixelMargin = 8; static const int s_linePixelIncLimit = 6; const unsigned char KateScrollBar::characterOpacity[256] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 15 0, 0, 0, 0, 0, 0, 0, 0, 255, 0, 255, 0, 0, 0, 0, 0, // <- 31 0, 125, 41, 221, 138, 195, 218, 21, 142, 142, 137, 137, 97, 87, 87, 140, // <- 47 223, 164, 183, 190, 191, 193, 214, 158, 227, 216, 103, 113, 146, 140, 146, 149, // <- 63 248, 204, 240, 174, 217, 197, 178, 205, 209, 176, 168, 211, 160, 246, 238, 218, // <- 79 195, 229, 227, 196, 167, 212, 188, 238, 197, 169, 189, 158, 21, 151, 115, 90, // <- 95 15, 192, 209, 153, 208, 187, 162, 221, 183, 149, 161, 191, 146, 203, 167, 182, // <- 111 208, 203, 139, 166, 158, 167, 157, 189, 164, 179, 156, 167, 145, 166, 109, 0, // <- 127 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 143 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // <- 159 0, 125, 184, 187, 146, 201, 127, 203, 89, 194, 156, 141, 117, 87, 202, 88, // <- 175 115, 165, 118, 121, 85, 190, 236, 87, 88, 111, 151, 140, 194, 191, 203, 148, // <- 191 215, 215, 222, 224, 223, 234, 230, 192, 208, 208, 216, 217, 187, 187, 194, 195, // <- 207 228, 255, 228, 228, 235, 239, 237, 150, 255, 222, 222, 229, 232, 180, 197, 225, // <- 223 208, 208, 216, 217, 212, 230, 218, 170, 202, 202, 211, 204, 156, 156, 165, 159, // <- 239 214, 194, 197, 197, 206, 206, 201, 132, 214, 183, 183, 192, 187, 195, 227, 198 }; KateScrollBar::KateScrollBar(Qt::Orientation orientation, KateViewInternal *parent) : QScrollBar(orientation, parent->m_view) , m_middleMouseDown(false) , m_leftMouseDown(false) , m_view(parent->m_view) , m_doc(parent->doc()) , m_viewInternal(parent) , m_textPreview(nullptr) , m_showMarks(false) , m_showMiniMap(false) , m_miniMapAll(true) , m_miniMapWidth(40) , m_grooveHeight(height()) , m_linesModified(0) { connect(this, SIGNAL(valueChanged(int)), this, SLOT(sliderMaybeMoved(int))); connect(m_doc, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(marksChanged())); m_updateTimer.setInterval(300); m_updateTimer.setSingleShot(true); QTimer::singleShot(10, this, SLOT(updatePixmap())); // track mouse for text preview widget setMouseTracking(orientation == Qt::Vertical); // setup text preview timer m_delayTextPreviewTimer.setSingleShot(true); m_delayTextPreviewTimer.setInterval(250); connect(&m_delayTextPreviewTimer, SIGNAL(timeout()), this, SLOT(showTextPreview())); } KateScrollBar::~KateScrollBar() { delete m_textPreview; } void KateScrollBar::setShowMiniMap(bool b) { if (b && !m_showMiniMap) { connect(m_view, SIGNAL(selectionChanged(KTextEditor::View*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_doc, SIGNAL(textChanged(KTextEditor::Document*)), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(m_view, SIGNAL(delayedUpdateOfView()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updatePixmap()), Qt::UniqueConnection); connect(&(m_view->textFolding()), SIGNAL(foldingRangesChanged()), &m_updateTimer, SLOT(start()), Qt::UniqueConnection); } else if (!b) { disconnect(&m_updateTimer); } m_showMiniMap = b; updateGeometry(); update(); } QSize KateScrollBar::sizeHint() const { if (m_showMiniMap) { return QSize(m_miniMapWidth, QScrollBar::sizeHint().height()); } return QScrollBar::sizeHint(); } int KateScrollBar::minimapYToStdY(int y) { // Check if the minimap fills the whole scrollbar if (m_stdGroveRect.height() == m_mapGroveRect.height()) { return y; } // check if y is on the step up/down if ((y < m_stdGroveRect.top()) || (y > m_stdGroveRect.bottom())) { return y; } if (y < m_mapGroveRect.top()) { return m_stdGroveRect.top() + 1; } if (y > m_mapGroveRect.bottom()) { return m_stdGroveRect.bottom() - 1; } // check for div/0 if (m_mapGroveRect.height() == 0) { return y; } int newY = (y - m_mapGroveRect.top()) * m_stdGroveRect.height() / m_mapGroveRect.height(); newY += m_stdGroveRect.top(); return newY; } void KateScrollBar::mousePressEvent(QMouseEvent *e) { // delete text preview hideTextPreview(); if (e->button() == Qt::MidButton) { m_middleMouseDown = true; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = true; } if (m_showMiniMap) { if (m_leftMouseDown) { // if we show the minimap left-click jumps directly to the selected position int newVal = (e->pos().y()-m_mapGroveRect.top()) / (double)m_mapGroveRect.height() * (double)(maximum()+pageStep()) - pageStep()/2; newVal = qBound(0, newVal, maximum()); setSliderPosition(newVal); } QMouseEvent eMod(QEvent::MouseButtonPress, QPoint(6, minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mousePressEvent(&eMod); } else { QScrollBar::mousePressEvent(e); } m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); redrawMarks(); } void KateScrollBar::mouseReleaseEvent(QMouseEvent *e) { if (e->button() == Qt::MidButton) { m_middleMouseDown = false; } else if (e->button() == Qt::LeftButton) { m_leftMouseDown = false; } redrawMarks(); if (m_leftMouseDown || m_middleMouseDown) { QToolTip::hideText(); } if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseButtonRelease, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseReleaseEvent(&eMod); } else { QScrollBar::mouseReleaseEvent(e); } } void KateScrollBar::mouseMoveEvent(QMouseEvent *e) { if (m_showMiniMap) { QMouseEvent eMod(QEvent::MouseMove, QPoint(e->pos().x(), minimapYToStdY(e->pos().y())), e->button(), e->buttons(), e->modifiers()); QScrollBar::mouseMoveEvent(&eMod); } else { QScrollBar::mouseMoveEvent(e); } if (e->buttons() & (Qt::LeftButton | Qt::MidButton)) { redrawMarks(); // current line tool tip m_toolTipPos = e->globalPos() - QPoint(e->pos().x(), 0); const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } showTextPreviewDelayed(); } void KateScrollBar::leaveEvent(QEvent *event) { hideTextPreview(); QAbstractSlider::leaveEvent(event); } bool KateScrollBar::eventFilter(QObject *object, QEvent *event) { Q_UNUSED(object) if (m_textPreview && event->type() == QEvent::WindowDeactivate) { // We need hide the scrollbar TextPreview widget hideTextPreview(); } return false; } void KateScrollBar::paintEvent(QPaintEvent *e) { if (m_doc->marks().size() != m_lines.size()) { recomputeMarksPositions(); } if (m_showMiniMap) { miniMapPaintEvent(e); } else { normalPaintEvent(e); } } void KateScrollBar::showTextPreviewDelayed() { if (!m_textPreview) { if (!m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.start(); } } else { showTextPreview(); } } void KateScrollBar::showTextPreview() { if (orientation() != Qt::Vertical || isSliderDown() || (minimum() == maximum()) || !m_view->config()->scrollBarPreview()) { return; } QRect grooveRect; if (m_showMiniMap) { // If mini-map is shown, the height of the map might not be the whole height grooveRect = m_mapGroveRect; } else { QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); } if (m_view->config()->scrollPastEnd()) { // Adjust the grove size to accommodate the added pageStep at the bottom int adjust = pageStep()*grooveRect.height() / (maximum() + pageStep() - minimum()); grooveRect.adjust(0,0,0, -adjust); } const QPoint cursorPos = mapFromGlobal(QCursor::pos()); if (grooveRect.contains(cursorPos)) { if (!m_textPreview) { m_textPreview = new KateTextPreview(m_view); m_textPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_textPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window qApp->installEventFilter(this); } const qreal posInPercent = static_cast(cursorPos.y() - grooveRect.top()) / grooveRect.height(); const qreal startLine = posInPercent * m_view->textFolding().visibleLines(); m_textPreview->resize(m_view->width() / 2, m_view->height() / 5); const int xGlobal = mapToGlobal(QPoint(0, 0)).x(); const int yGlobal = qMin(mapToGlobal(QPoint(0, height())).y() - m_textPreview->height(), qMax(mapToGlobal(QPoint(0, 0)).y(), mapToGlobal(cursorPos).y() - m_textPreview->height() / 2)); m_textPreview->move(xGlobal - m_textPreview->width(), yGlobal); m_textPreview->setLine(startLine); m_textPreview->setCenterView(true); m_textPreview->setScaleFactor(0.8); m_textPreview->raise(); m_textPreview->show(); } else { hideTextPreview(); } } void KateScrollBar::hideTextPreview() { if (m_delayTextPreviewTimer.isActive()) { m_delayTextPreviewTimer.stop(); } qApp->removeEventFilter(this); delete m_textPreview; } // This function is optimized for bing called in sequence. const QColor KateScrollBar::charColor(const QVector &attributes, int &attributeIndex, const QList &decorations, const QColor &defaultColor, int x, QChar ch) { QColor color = defaultColor; bool styleFound = false; // Query the decorations, that is, things like search highlighting, or the // KDevelop DUChain highlighting, for a color to use foreach (const QTextLayout::FormatRange &range, decorations) { if (range.start <= x && range.start + range.length > x) { // If there's a different background color set (search markers, ...) // use that, otherwise use the foreground color. if (range.format.hasProperty(QTextFormat::BackgroundBrush)) { color = range.format.background().color(); } else { color = range.format.foreground().color(); } styleFound = true; break; } } // If there's no decoration set for the current character (this will mostly be the case for // plain Kate), query the styles, that is, the default kate syntax highlighting. if (!styleFound) { // go to the block containing x while ((attributeIndex < attributes.size()) && ((attributes[attributeIndex].offset + attributes[attributeIndex].length) < x)) { ++attributeIndex; } if ((attributeIndex < attributes.size()) && (x < attributes[attributeIndex].offset + attributes[attributeIndex].length)) { color = m_view->renderer()->attribute(attributes[attributeIndex].attributeValue)->foreground().color(); } } // Query how much "blackness" the character has. // This causes for example a dot or a dash to appear less intense // than an A or similar. // This gives the pixels created a bit of structure, which makes it look more // like real text. color.setAlpha((ch.unicode() < 256) ? characterOpacity[ch.unicode()] : 222); return color; } void KateScrollBar::updatePixmap() { //QTime time; //time.start(); if (!m_showMiniMap) { // make sure no time is wasted if the option is disabled return; } // For performance reason, only every n-th line will be drawn if the widget is // sufficiently small compared to the amount of lines in the document. int docLineCount = m_view->textFolding().visibleLines(); int pixmapLineCount = docLineCount; if (m_view->config()->scrollPastEnd()) { pixmapLineCount += pageStep(); } int pixmapLinesUnscaled = pixmapLineCount; if (m_grooveHeight < 5) { m_grooveHeight = 5; } int lineDivisor = pixmapLinesUnscaled / m_grooveHeight; if (lineDivisor < 1) { lineDivisor = 1; } int charIncrement = 1; int lineIncrement = 1; if ((m_grooveHeight > 10) && (pixmapLineCount >= m_grooveHeight * 2)) { charIncrement = pixmapLineCount / m_grooveHeight; while (charIncrement > s_linePixelIncLimit) { lineIncrement++; pixmapLineCount = pixmapLinesUnscaled / lineIncrement; charIncrement = pixmapLineCount / m_grooveHeight; } pixmapLineCount /= charIncrement; } int pixmapLineWidth = s_pixelMargin + s_lineWidth / charIncrement; //qCDebug(LOG_KTE) << "l" << lineIncrement << "c" << charIncrement << "d" << lineDivisor; //qCDebug(LOG_KTE) << "pixmap" << pixmapLineCount << pixmapLineWidth << "docLines" << m_view->textFolding().visibleLines() << "height" << m_grooveHeight; const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor defaultTextColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor selectionBgColor = m_view->renderer()->config()->selectionColor(); QColor modifiedLineColor = m_view->renderer()->config()->modifiedLineColor(); QColor savedLineColor = m_view->renderer()->config()->savedLineColor(); // move the modified line color away from the background color modifiedLineColor.setHsv(modifiedLineColor.hue(), 255, 255 - backgroundColor.value() / 3); savedLineColor.setHsv(savedLineColor.hue(), 100, 255 - backgroundColor.value() / 3); // increase dimensions by ratio m_pixmap = QPixmap(pixmapLineWidth * m_view->devicePixelRatioF(), pixmapLineCount * m_view->devicePixelRatioF()); m_pixmap.fill(QColor("transparent")); // The text currently selected in the document, to be drawn later. const KTextEditor::Range &selection = m_view->selectionRange(); QPainter painter; if (painter.begin(&m_pixmap)) { // init pen once, afterwards, only change it if color changes to avoid a lot of allocation for setPen painter.setPen(selectionBgColor); // Do not force updates of the highlighting if the document is very large bool simpleMode = m_doc->lines() > 7500; int pixelY = 0; int drawnLines = 0; // Iterate over all visible lines, drawing them. for (int virtualLine = 0; virtualLine < docLineCount; virtualLine += lineIncrement) { int realLineNumber = m_view->textFolding().visibleLineToLine(virtualLine); QString lineText = m_doc->line(realLineNumber); if (!simpleMode) { m_doc->buffer().ensureHighlighted(realLineNumber); } const Kate::TextLine &kateline = m_doc->plainKateTextLine(realLineNumber); const QVector &attributes = kateline->attributesList(); QList< QTextLayout::FormatRange > decorations = m_view->renderer()->decorationsForLine(kateline, realLineNumber); int attributeIndex = 0; // Draw selection if it is on an empty line if (selection.contains(KTextEditor::Cursor(realLineNumber, 0)) && lineText.size() == 0) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(s_pixelMargin, pixelY, s_pixelMargin + s_lineWidth - 1, pixelY); } // Iterate over the line to draw the background int selStartX = -1; int selEndX = -1; int pixelX = s_pixelMargin; // use this to control the offset of the text from the left for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // Query the selection and draw it behind the character if (selection.contains(KTextEditor::Cursor(realLineNumber, x))) { if (selStartX == -1) selStartX = pixelX; selEndX = pixelX; if (lineText.size() - 1 == x) { selEndX = s_lineWidth + s_pixelMargin-1; } } if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { pixelX++; } } if (selStartX != -1) { if (selectionBgColor != painter.pen().color()) { painter.setPen(selectionBgColor); } painter.drawLine(selStartX, pixelY, selEndX, pixelY); } // Iterate over all the characters in the current line pixelX = s_pixelMargin; for (int x = 0; (x < lineText.size() && x < s_lineWidth); x += charIncrement) { if (pixelX >= s_lineWidth + s_pixelMargin) { break; } // draw the pixels if (lineText[x] == QLatin1Char(' ')) { pixelX++; } else if (lineText[x] == QLatin1Char('\t')) { pixelX += qMax(4 / charIncrement, 1); // FIXME: tab width... } else { const QColor newPenColor(charColor(attributes, attributeIndex, decorations, defaultTextColor, x, lineText[x])); if (newPenColor != painter.pen().color()) { painter.setPen(newPenColor); } // Actually draw the pixel with the color queried from the renderer. painter.drawPoint(pixelX, pixelY); pixelX++; } } drawnLines++; if (((drawnLines) % charIncrement) == 0) { pixelY++; } } //qCDebug(LOG_KTE) << drawnLines; // Draw line modification marker map. // Disable this if the document is really huge, // since it requires querying every line. if (m_doc->lines() < 50000) { for (int lineno = 0; lineno < docLineCount; lineno++) { int realLineNo = m_view->textFolding().visibleLineToLine(lineno); const Kate::TextLine &line = m_doc->plainKateTextLine(realLineNo); const QColor & col = line->markedAsModified() ? modifiedLineColor : savedLineColor; if (line->markedAsModified() || line->markedAsSavedOnDisk()) { painter.fillRect(2, lineno / lineDivisor, 3, 1, col); } } } // end painting painter.end(); } // set right ratio m_pixmap.setDevicePixelRatio(m_view->devicePixelRatioF()); //qCDebug(LOG_KTE) << time.elapsed(); // Redraw the scrollbar widget with the updated pixmap. update(); } void KateScrollBar::miniMapPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); m_stdGroveRect = grooveRect; if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSubLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.moveTop(alignMargin); grooveRect.setHeight(grooveRect.height() - alignMargin); } if (style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarAddLine, this).height() == 0) { int alignMargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &opt, this); grooveRect.setHeight(grooveRect.height() - alignMargin); } m_grooveHeight = grooveRect.height(); const int docXMargin = 1; QRect sliderRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); sliderRect.adjust(docXMargin, 0, 0, 0); //style()->drawControl(QStyle::CE_ScrollBarAddLine, &opt, &painter, this); //style()->drawControl(QStyle::CE_ScrollBarSubLine, &opt, &painter, this); // calculate the document size and position const int docHeight = qMin(grooveRect.height(), int(m_pixmap.height() / m_pixmap.devicePixelRatio() * 2)) - 2 * docXMargin; const int yoffset = 1; // top-aligned in stead of center-aligned (grooveRect.height() - docHeight) / 2; const QRect docRect(QPoint(grooveRect.left() + docXMargin, yoffset + grooveRect.top()), QSize(grooveRect.width() - docXMargin, docHeight)); m_mapGroveRect = docRect; // calculate the visible area int max = qMax(maximum() + 1, 1); int visibleStart = value() * docHeight / (max + pageStep()) + docRect.top() + 0.5; int visibleEnd = (value() + pageStep()) * docHeight / (max + pageStep()) + docRect.top(); QRect visibleRect = docRect; visibleRect.moveTop(visibleStart); visibleRect.setHeight(visibleEnd - visibleStart); // calculate colors const QColor backgroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->background().color(); const QColor foregroundColor = m_view->defaultStyleAttribute(KTextEditor::dsNormal)->foreground().color(); const QColor highlightColor = palette().link().color(); const int backgroundLightness = backgroundColor.lightness(); const int foregroundLightness = foregroundColor.lightness(); const int lighnessDiff = (foregroundLightness - backgroundLightness); // get a color suited for the color theme QColor darkShieldColor = palette().color(QPalette::Mid); int hue, sat, light; darkShieldColor.getHsl(&hue, &sat, &light); // apply suitable lightness darkShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.35); // gradient for nicer results QLinearGradient gradient(0, 0, width(), 0); gradient.setColorAt(0, darkShieldColor); gradient.setColorAt(0.3, darkShieldColor.lighter(115)); gradient.setColorAt(1, darkShieldColor); QColor lightShieldColor; lightShieldColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.15); QColor outlineColor; outlineColor.setHsl(hue, sat, backgroundLightness + lighnessDiff * 0.5); // draw the grove background in case the document is small painter.setPen(Qt::NoPen); painter.setBrush(backgroundColor); painter.drawRect(grooveRect); // adjust the rectangles if ((docHeight + 2 * docXMargin >= grooveRect.height()) && (sliderRect.height() > visibleRect.height() + 2)) { visibleRect.adjust(2, 0, -3, 0); } else { visibleRect.adjust(1, 0, -1, 2); sliderRect.setTop(visibleRect.top() - 1); sliderRect.setBottom(visibleRect.bottom() + 1); } // Smooth transform only when squeezing if (grooveRect.height() < m_pixmap.height() / m_pixmap.devicePixelRatio()) { painter.setRenderHint(QPainter::SmoothPixmapTransform); } // draw the modified lines margin QRect pixmapMarginRect(QPoint(0, 0), QSize(s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapMarginRect(QPoint(0, docRect.top()), QSize(s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapMarginRect, m_pixmap, pixmapMarginRect); // calculate the stretch and draw the stretched lines (scrollbar marks) QRect pixmapRect(QPoint(s_pixelMargin, 0), QSize(m_pixmap.width() / m_pixmap.devicePixelRatio() - s_pixelMargin, m_pixmap.height() / m_pixmap.devicePixelRatio())); QRect docPixmapRect(QPoint(s_pixelMargin, docRect.top()), QSize(docRect.width() - s_pixelMargin, docRect.height())); painter.drawPixmap(docPixmapRect, m_pixmap, pixmapRect); // delimit the end of the document const int y = docPixmapRect.height() + grooveRect.y(); if (y+2 < grooveRect.y() + grooveRect.height()) { QColor fg(foregroundColor); fg.setAlpha(30); painter.setBrush(Qt::NoBrush); painter.setPen(QPen(fg, 1)); painter.drawLine(grooveRect.x()+1,y+2,width()-1,y+2); } // fade the invisible sections const QRect top( grooveRect.x(), grooveRect.y(), grooveRect.width(), visibleRect.y()-grooveRect.y() //Pen width ); const QRect bottom( grooveRect.x(), grooveRect.y()+visibleRect.y()+visibleRect.height()-grooveRect.y(), //Pen width grooveRect.width(), grooveRect.height() - (visibleRect.y()-grooveRect.y())-visibleRect.height() ); QColor faded(backgroundColor); faded.setAlpha(110); painter.fillRect(top, faded); painter.fillRect(bottom, faded); // add a thin line to limit the scrollbar QColor c(foregroundColor); c.setAlpha(10); painter.setPen(QPen(c,1)); painter.drawLine(0, 0, 0, height()); if (m_showMarks) { QHashIterator it = m_lines; QPen penBg; penBg.setWidth(4); lightShieldColor.setAlpha(180); penBg.setColor(lightShieldColor); painter.setPen(penBg); while (it.hasNext()) { it.next(); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top();; painter.drawLine(6, y, width() - 6, y); } it = m_lines; QPen pen; pen.setWidth(2); while (it.hasNext()) { it.next(); pen.setColor(it.value()); painter.setPen(pen); int y = (it.key() - grooveRect.top()) * docHeight / grooveRect.height() + docRect.top(); painter.drawLine(6, y, width() - 6, y); } } // slider outline QColor sliderColor(highlightColor); sliderColor.setAlpha(50); painter.fillRect(sliderRect, sliderColor); painter.setPen(QPen(highlightColor, 0)); // rounded rect looks ugly for some reason, so we draw 4 lines. painter.drawLine(sliderRect.left(), sliderRect.top() + 1, sliderRect.left(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.right(), sliderRect.top() + 1, sliderRect.right(), sliderRect.bottom() - 1); painter.drawLine(sliderRect.left() + 1, sliderRect.top(), sliderRect.right() - 1, sliderRect.top()); painter.drawLine(sliderRect.left() + 1, sliderRect.bottom(), sliderRect.right() - 1, sliderRect.bottom()); } void KateScrollBar::normalPaintEvent(QPaintEvent *e) { QScrollBar::paintEvent(e); if (!m_showMarks) { return; } QPainter painter(this); QStyleOptionSlider opt; opt.init(this); opt.subControls = QStyle::SC_None; opt.activeSubControls = QStyle::SC_None; opt.orientation = orientation(); opt.minimum = minimum(); opt.maximum = maximum(); opt.sliderPosition = sliderPosition(); opt.sliderValue = value(); opt.singleStep = singleStep(); opt.pageStep = pageStep(); QRect rect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarSlider, this); int sideMargin = width() - rect.width(); if (sideMargin < 4) { sideMargin = 4; } sideMargin /= 2; QHashIterator it = m_lines; while (it.hasNext()) { it.next(); painter.setPen(it.value()); if (it.key() < rect.top() || it.key() > rect.bottom()) { painter.drawLine(0, it.key(), width(), it.key()); } else { painter.drawLine(0, it.key(), sideMargin, it.key()); painter.drawLine(width() - sideMargin, it.key(), width(), it.key()); } } } void KateScrollBar::resizeEvent(QResizeEvent *e) { QScrollBar::resizeEvent(e); m_updateTimer.start(); m_lines.clear(); update(); } void KateScrollBar::sliderChange(SliderChange change) { // call parents implementation QScrollBar::sliderChange(change); if (change == QAbstractSlider::SliderValueChange) { redrawMarks(); } else if (change == QAbstractSlider::SliderRangeChange) { marksChanged(); } if (m_leftMouseDown || m_middleMouseDown) { const int fromLine = m_viewInternal->toRealCursor(m_viewInternal->startPos()).line() + 1; const int lastLine = m_viewInternal->toRealCursor(m_viewInternal->endPos()).line() + 1; QToolTip::showText(m_toolTipPos, i18nc("from line - to line", "
%1

%2
", fromLine, lastLine), this); } } void KateScrollBar::marksChanged() { m_lines.clear(); update(); } void KateScrollBar::redrawMarks() { if (!m_showMarks) { return; } update(); } void KateScrollBar::recomputeMarksPositions() { // get the style options to compute the scrollbar pixels QStyleOptionSlider opt; initStyleOption(&opt); QRect grooveRect = style()->subControlRect(QStyle::CC_ScrollBar, &opt, QStyle::SC_ScrollBarGroove, this); // cache top margin and groove height const int top = grooveRect.top(); const int h = grooveRect.height() - 1; // make sure we have a sane height if (h <= 0) { return; } // get total visible (=without folded) lines in the document int visibleLines = m_view->textFolding().visibleLines() - 1; if (m_view->config()->scrollPastEnd()) { visibleLines += m_viewInternal->linesDisplayed() - 1; visibleLines -= m_view->config()->autoCenterLines(); } // now repopulate the scrollbar lines list m_lines.clear(); const QHash &marks = m_doc->marks(); for (QHash::const_iterator i = marks.constBegin(); i != marks.constEnd(); ++i) { KTextEditor::Mark *mark = i.value(); const int line = m_view->textFolding().lineToVisibleLine(mark->line); const double ratio = static_cast(line) / visibleLines; m_lines.insert(top + (int)(h * ratio), KateRendererConfig::global()->lineMarkerColor((KTextEditor::MarkInterface::MarkTypes)mark->type)); } } void KateScrollBar::sliderMaybeMoved(int value) { if (m_middleMouseDown) { // we only need to emit this signal once, as for the following slider // movements the signal sliderMoved() is already emitted. // Thus, set m_middleMouseDown to false right away. m_middleMouseDown = false; emit sliderMMBMoved(value); } } //END //BEGIN KateCmdLineEditFlagCompletion /** * This class provide completion of flags. It shows a short description of * each flag, and flags are appended. */ class KateCmdLineEditFlagCompletion : public KCompletion { public: KateCmdLineEditFlagCompletion() { ; } QString makeCompletion(const QString & /*s*/) Q_DECL_OVERRIDE { return QString(); } }; //END KateCmdLineEditFlagCompletion //BEGIN KateCmdLineEdit KateCommandLineBar::KateCommandLineBar(KTextEditor::ViewPrivate *view, QWidget *parent) : KateViewBarWidget(true, parent) { QHBoxLayout *topLayout = new QHBoxLayout(); centralWidget()->setLayout(topLayout); topLayout->setMargin(0); m_lineEdit = new KateCmdLineEdit(this, view); connect(m_lineEdit, SIGNAL(hideRequested()), SIGNAL(hideMe())); topLayout->addWidget(m_lineEdit); QToolButton *helpButton = new QToolButton(this); helpButton->setAutoRaise(true); helpButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual"))); topLayout->addWidget(helpButton); connect(helpButton, SIGNAL(clicked()), this, SLOT(showHelpPage())); setFocusProxy(m_lineEdit); } void KateCommandLineBar::showHelpPage() { KHelpClient::invokeHelp(QStringLiteral("advanced-editing-tools-commandline"), QStringLiteral("kate")); } KateCommandLineBar::~KateCommandLineBar() { } // inserts the given string in the command line edit and (if selected = true) selects it so the user // can type over it if they want to void KateCommandLineBar::setText(const QString &text, bool selected) { m_lineEdit->setText(text); if (selected) { m_lineEdit->selectAll(); } } void KateCommandLineBar::execute(const QString &text) { m_lineEdit->slotReturnPressed(text); } KateCmdLineEdit::KateCmdLineEdit(KateCommandLineBar *bar, KTextEditor::ViewPrivate *view) : KLineEdit() , m_view(view) , m_bar(bar) , m_msgMode(false) , m_histpos(0) , m_cmdend(0) , m_command(nullptr) { connect(this, SIGNAL(returnPressed(QString)), this, SLOT(slotReturnPressed(QString))); setCompletionObject(KateCmd::self()->commandCompletionObject()); setAutoDeleteCompletionObject(false); m_hideTimer = new QTimer(this); m_hideTimer->setSingleShot(true); connect(m_hideTimer, SIGNAL(timeout()), this, SLOT(hideLineEdit())); // make sure the timer is stopped when the user switches views. if not, focus will be given to the // wrong view when KateViewBar::hideCurrentBarWidget() is called after 4 seconds. (the timer is // used for showing things like "Success" for four seconds after the user has used the kate // command line) connect(m_view, SIGNAL(focusOut(KTextEditor::View*)), m_hideTimer, SLOT(stop())); } void KateCmdLineEdit::hideEvent(QHideEvent *e) { Q_UNUSED(e); } QString KateCmdLineEdit::helptext(const QPoint &) const { QString beg = QStringLiteral("
Help: "); QString mid = QStringLiteral("
"); QString end = QStringLiteral("
"); QString t = text(); QRegExp re(QLatin1String("\\s*help\\s+(.*)")); if (re.indexIn(t) > -1) { QString s; // get help for command QString name = re.cap(1); if (name == QLatin1String("list")) { return beg + i18n("Available Commands") + mid + KateCmd::self()->commandList().join(QLatin1Char(' ')) + i18n("

For help on individual commands, do 'help <command>'

") + end; } else if (! name.isEmpty()) { KTextEditor::Command *cmd = KateCmd::self()->queryCommand(name); if (cmd) { if (cmd->help(m_view, name, s)) { return beg + name + mid + s + end; } else { return beg + name + mid + i18n("No help for '%1'", name) + end; } } else { return beg + mid + i18n("No such command %1", name) + end; } } } return beg + mid + i18n( "

This is the Katepart command line.
" "Syntax: command [ arguments ]
" "For a list of available commands, enter help list
" "For help for individual commands, enter help <command>

") + end; } bool KateCmdLineEdit::event(QEvent *e) { if (e->type() == QEvent::QueryWhatsThis) { setWhatsThis(helptext(QPoint())); e->accept(); return true; } return KLineEdit::event(e); } /** * Parse the text as a command. * * The following is a simple PEG grammar for the syntax of the command. * (A PEG grammar is like a BNF grammar, except that "/" stands for * ordered choice: only the first matching rule is used. In other words, * the parsing is short-circuited in the manner of the "or" operator in * programming languages, and so the grammar is unambiguous.) * * Text <- Range? Command * / Position * Range <- Position ("," Position)? * / "%" * Position <- Base Offset? * Base <- Line * / LastLine * / ThisLine * / Mark * Offset <- [+-] Base * Line <- [0-9]+ * LastLine <- "$" * ThisLine <- "." * Mark <- "'" [a-z] */ void KateCmdLineEdit::slotReturnPressed(const QString &text) { if (text.isEmpty()) { return; } // silently ignore leading space characters uint n = 0; const uint textlen = text.length(); while ((n < textlen) && (text[n].isSpace())) { n++; } if (n >= textlen) { return; } QString cmd = text.mid(n); // Parse any leading range expression, and strip it (and maybe do some other transforms on the command). QString leadingRangeExpression; KTextEditor::Range range = CommandRangeExpressionParser::parseRangeExpression(cmd, m_view, leadingRangeExpression, cmd); // Built in help: if the command starts with "help", [try to] show some help if (cmd.startsWith(QLatin1String("help"))) { QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), helptext(QPoint())); clear(); KateCmd::self()->appendHistory(cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); return; } if (cmd.length() > 0) { KTextEditor::Command *p = KateCmd::self()->queryCommand(cmd); m_oldText = leadingRangeExpression + cmd; m_msgMode = true; // the following commands changes the focus themselves, so bar should be hidden before execution. if (QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { emit hideRequested(); } if (!p) { setText(i18n("No such command: \"%1\"", cmd)); } else if (range.isValid() && !p->supportsRange(cmd)) { - // we got a range and a valid command, but the command does not inherit the RangeCommand - // extension. bail out. + // Raise message, when the command does not support ranges. setText(i18n("Error: No range allowed for command \"%1\".", cmd)); } else { QString msg; if (p->exec(m_view, cmd, msg, range)) { // append command along with range (will be empty if none given) to history KateCmd::self()->appendHistory(leadingRangeExpression + cmd); m_histpos = KateCmd::self()->historyLength(); m_oldText.clear(); if (msg.length() > 0) { setText(i18n("Success: ") + msg); } else if (isVisible()) { // always hide on success without message emit hideRequested(); } } else { if (msg.length() > 0) { if (msg.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(mapToGlobal(QPoint(0, 0)), msg); } else { setText(msg); } } else { setText(i18n("Command \"%1\" failed.", cmd)); } } } } // clean up if (completionObject() != KateCmd::self()->commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_command = nullptr; m_cmdend = 0; // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|bn|bnext|bf|bfirst|bl|blast|edit|e")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { m_view->setFocus(); } if (isVisible()) { m_hideTimer->start(4000); } } void KateCmdLineEdit::hideLineEdit() // unless i have focus ;) { if (! hasFocus()) { emit hideRequested(); } } void KateCmdLineEdit::focusInEvent(QFocusEvent *ev) { if (m_msgMode) { m_msgMode = false; setText(m_oldText); selectAll(); } KLineEdit::focusInEvent(ev); } void KateCmdLineEdit::keyPressEvent(QKeyEvent *ev) { if (ev->key() == Qt::Key_Escape || (ev->key() == Qt::Key_BracketLeft && ev->modifiers() == Qt::ControlModifier)) { m_view->setFocus(); hideLineEdit(); clear(); } else if (ev->key() == Qt::Key_Up) { fromHistory(true); } else if (ev->key() == Qt::Key_Down) { fromHistory(false); } uint cursorpos = cursorPosition(); KLineEdit::keyPressEvent(ev); // during typing, let us see if we have a valid command if (! m_cmdend || cursorpos <= m_cmdend) { QChar c; if (! ev->text().isEmpty()) { c = ev->text().at(0); } if (! m_cmdend && ! c.isNull()) { // we have no command, so lets see if we got one if (! c.isLetterOrNumber() && c != QLatin1Char('-') && c != QLatin1Char('_')) { m_command = KateCmd::self()->queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<queryCommand(text().trimmed()); if (m_command) { //qCDebug(LOG_KTE)<<"keypress in commandline: We have a command! "<commandCompletionObject()) { KCompletion *c = completionObject(); setCompletionObject(KateCmd::self()->commandCompletionObject()); delete c; } m_cmdend = 0; } } // if we got a command, check if it wants to do something. if (m_command) { KCompletion *cmpl = m_command->completionObject(m_view, text().left(m_cmdend).trimmed()); if (cmpl) { // We need to prepend the current command name + flag string // when completion is done //qCDebug(LOG_KTE)<<"keypress in commandline: Setting completion object!"; setCompletionObject(cmpl); } } } else if (m_command && !ev->text().isEmpty()) { // check if we should call the commands processText() if (m_command->wantsToProcessText(text().left(m_cmdend).trimmed())) { m_command->processText(m_view, text()); } } } void KateCmdLineEdit::fromHistory(bool up) { if (! KateCmd::self()->historyLength()) { return; } QString s; if (up) { if (m_histpos > 0) { m_histpos--; s = KateCmd::self()->fromHistory(m_histpos); } } else { if (m_histpos < (KateCmd::self()->historyLength() - 1)) { m_histpos++; s = KateCmd::self()->fromHistory(m_histpos); } else { m_histpos = KateCmd::self()->historyLength(); setText(m_oldText); } } if (! s.isEmpty()) { // Select the argument part of the command, so that it is easy to overwrite setText(s); static QRegExp reCmd = QRegExp(QLatin1String(".*[\\w\\-]+(?:[^a-zA-Z0-9_-]|:\\w+)(.*)")); if (reCmd.indexIn(text()) == 0) { setSelection(text().length() - reCmd.cap(1).length(), reCmd.cap(1).length()); } } } //END KateCmdLineEdit //BEGIN KateIconBorder using namespace KTextEditor; KateIconBorder::KateIconBorder(KateViewInternal *internalView, QWidget *parent) : QWidget(parent) , m_view(internalView->m_view) , m_doc(internalView->doc()) , m_viewInternal(internalView) , m_iconBorderOn(false) , m_lineNumbersOn(false) , m_relLineNumbersOn(false) , m_updateRelLineNumbers(false) , m_foldingMarkersOn(false) , m_dynWrapIndicatorsOn(false) , m_annotationBorderOn(false) , m_dynWrapIndicators(0) , m_lastClickedLine(-1) , m_cachedLNWidth(0) , m_maxCharWidth(0.0) , iconPaneWidth(16) , m_annotationBorderWidth(6) , m_foldingPreview(nullptr) , m_foldingRange(nullptr) , m_nextHighlightBlock(-2) , m_currentBlockLine(-1) { setAttribute(Qt::WA_StaticContents); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); setMouseTracking(true); m_doc->setMarkDescription(MarkInterface::markType01, i18n("Bookmark")); m_doc->setMarkPixmap(MarkInterface::markType01, QIcon::fromTheme(QStringLiteral("bookmarks")).pixmap(16, 16)); updateFont(); m_delayFoldingHlTimer.setSingleShot(true); m_delayFoldingHlTimer.setInterval(150); connect(&m_delayFoldingHlTimer, SIGNAL(timeout()), this, SLOT(showBlock())); // user interaction (scrolling) hides e.g. preview connect(m_view, SIGNAL(displayRangeChanged(KTextEditor::ViewPrivate*)), this, SLOT(displayRangeChanged())); } KateIconBorder::~KateIconBorder() { delete m_foldingPreview; delete m_foldingRange; } void KateIconBorder::setIconBorderOn(bool enable) { if (enable == m_iconBorderOn) { return; } m_iconBorderOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setAnnotationBorderOn(bool enable) { if (enable == m_annotationBorderOn) { return; } m_annotationBorderOn = enable; emit m_view->annotationBorderVisibilityChanged(m_view, enable); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::removeAnnotationHovering() { // remove hovering if it's still there if (m_annotationBorderOn && !m_hoveredAnnotationGroupIdentifier.isEmpty()) { m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::setLineNumbersOn(bool enable) { if (enable == m_lineNumbersOn) { return; } m_lineNumbersOn = enable; m_dynWrapIndicatorsOn = (m_dynWrapIndicators == 1) ? enable : m_dynWrapIndicators; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setRelLineNumbersOn(bool enable) { if (enable == m_relLineNumbersOn) { return; } m_relLineNumbersOn = enable; /* * We don't have to touch the m_dynWrapIndicatorsOn because * we already got it right from the m_lineNumbersOn */ updateGeometry(); QTimer::singleShot( 0, this, SLOT(update()) ); } void KateIconBorder::updateForCursorLineChange() { if (m_relLineNumbersOn) { m_updateRelLineNumbers = true; } // always do normal update, e.g. for different current line color! update(); } void KateIconBorder::setDynWrapIndicators(int state) { if (state == m_dynWrapIndicators) { return; } m_dynWrapIndicators = state; m_dynWrapIndicatorsOn = (state == 1) ? m_lineNumbersOn : state; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::setFoldingMarkersOn(bool enable) { if (enable == m_foldingMarkersOn) { return; } m_foldingMarkersOn = enable; updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } QSize KateIconBorder::sizeHint() const { int w = 0; if (m_iconBorderOn) { w += iconPaneWidth + 2; } if (m_annotationBorderOn) { w += m_annotationBorderWidth + 2; } if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { w += lineNumberWidth() + 2; } if (m_foldingMarkersOn) { w += iconPaneWidth; } // space for the line modification system border if (m_view->config()->lineModification()) { w += 3; } // two pixel space w += 2; return QSize(w, 0); } // This function (re)calculates the maximum width of any of the digit characters (0 -> 9) // for graceful handling of variable-width fonts as the linenumber font. void KateIconBorder::updateFont() { const QFontMetricsF &fm = m_view->renderer()->config()->fontMetrics(); m_maxCharWidth = 0.0; // Loop to determine the widest numeric character in the current font. // 48 is ascii '0' for (int i = 48; i < 58; i++) { const qreal charWidth = ceil(fm.width(QChar(i))); m_maxCharWidth = qMax(m_maxCharWidth, charWidth); } // the icon pane scales with the font... iconPaneWidth = fm.height(); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } int KateIconBorder::lineNumberWidth() const { // width = (number of digits + 1) * char width const int digits = (int) ceil(log10((double)(m_view->doc()->lines() + 1))); int width = m_lineNumbersOn ? (int)ceil((digits + 1) * m_maxCharWidth) : 0; if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { // HACK: 16 == style().scrollBarExtent().width() width = qMax(16 + 4, width); if (m_cachedLNWidth != width || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { int w = 16;// HACK: 16 == style().scrollBarExtent().width() style().scrollBarExtent().width(); int h = m_view->renderer()->lineHeight(); QSize newSize(w * devicePixelRatio(), h * devicePixelRatio()); if ((m_arrow.size() != newSize || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) && !newSize.isEmpty()) { m_arrow = QPixmap(newSize); m_arrow.setDevicePixelRatio(devicePixelRatio()); QPainter p(&m_arrow); p.fillRect(0, 0, w, h, m_view->renderer()->config()->iconBarColor()); h = m_view->renderer()->config()->fontMetrics().ascent(); p.setPen(m_view->renderer()->config()->lineNumberColor()); QPainterPath path; path.moveTo(w / 2, h / 2); path.lineTo(w / 2, 0); path.lineTo(w / 4, h / 4); path.lineTo(0, 0); path.lineTo(0, h / 2); path.lineTo(w / 2, h - 1); path.lineTo(w * 3 / 4, h - 1); path.lineTo(w - 1, h * 3 / 4); path.lineTo(w * 3 / 4, h / 2); path.lineTo(0, h / 2); p.drawPath(path); } } } return width; } void KateIconBorder::paintEvent(QPaintEvent *e) { paintBorder(e->rect().x(), e->rect().y(), e->rect().width(), e->rect().height()); } static void paintTriangle(QPainter &painter, QColor c, int xOffset, int yOffset, int width, int height, bool open) { painter.setRenderHint(QPainter::Antialiasing); qreal size = qMin(width, height); if (KColorUtils::luma(c) > 0.25) { c = KColorUtils::darken(c); } else { c = KColorUtils::shade(c, 0.1); } QPen pen; pen.setJoinStyle(Qt::RoundJoin); pen.setColor(c); pen.setWidthF(1.5); painter.setPen(pen); painter.setBrush(c); // let some border, if possible size *= 0.6; qreal halfSize = size / 2; qreal halfSizeP = halfSize * 0.6; QPointF middle(xOffset + (qreal)width / 2, yOffset + (qreal)height / 2); if (open) { QPointF points[3] = { middle + QPointF(-halfSize, -halfSizeP), middle + QPointF(halfSize, -halfSizeP), middle + QPointF(0, halfSizeP) }; painter.drawConvexPolygon(points, 3); } else { QPointF points[3] = { middle + QPointF(-halfSizeP, -halfSize), middle + QPointF(-halfSizeP, halfSize), middle + QPointF(halfSizeP, 0) }; painter.drawConvexPolygon(points, 3); } painter.setRenderHint(QPainter::Antialiasing, false); } void KateIconBorder::paintBorder(int /*x*/, int y, int /*width*/, int height) { uint h = m_view->renderer()->lineHeight(); uint startz = (y / h); uint endz = startz + 1 + (height / h); uint lineRangesSize = m_viewInternal->cache()->viewCacheLineCount(); uint currentLine = m_view->cursorPosition().line(); // center the folding boxes int m_px = (h - 11) / 2; if (m_px < 0) { m_px = 0; } int lnWidth(0); if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { // avoid calculating unless needed ;-) lnWidth = lineNumberWidth(); if (lnWidth != m_cachedLNWidth || m_oldBackgroundColor != m_view->renderer()->config()->iconBarColor()) { // we went from n0 ->n9 lines or vice verca // this causes an extra updateGeometry() first time the line numbers // are displayed, but sizeHint() is supposed to be const so we can't set // the cached value there. m_cachedLNWidth = lnWidth; m_oldBackgroundColor = m_view->renderer()->config()->iconBarColor(); updateGeometry(); update(); return; } } int w(this->width()); // sane value/calc only once QPainter p(this); p.setRenderHints(QPainter::TextAntialiasing); p.setFont(m_view->renderer()->config()->font()); // for line numbers KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); for (uint z = startz; z <= endz; z++) { int y = h * z; int realLine = -1; if (z < lineRangesSize) { realLine = m_viewInternal->cache()->viewLine(z).line(); } int lnX = 0; p.fillRect(0, y, w - 5, h, m_view->renderer()->config()->iconBarColor()); p.fillRect(w - 5, y, 5, h, m_view->renderer()->config()->backgroundColor()); // icon pane if (m_iconBorderOn) { p.setPen(m_view->renderer()->config()->separatorColor()); p.setBrush(m_view->renderer()->config()->separatorColor()); p.drawLine(lnX + iconPaneWidth + 1, y, lnX + iconPaneWidth + 1, y + h); if ((realLine > -1) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { uint mrk(m_doc->mark(realLine)); // call only once if (mrk) { for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (mrk & markType) { QPixmap px_mark(m_doc->markPixmap(markType)); if (!px_mark.isNull() && h > 0 && iconPaneWidth > 0) { if (iconPaneWidth < px_mark.width() || h < (uint)px_mark.height()) { px_mark = px_mark.scaled(iconPaneWidth, h, Qt::KeepAspectRatio); } // center the mark pixmap int x_px = (iconPaneWidth - px_mark.width()) / 2; if (x_px < 0) { x_px = 0; } int y_px = (h - px_mark.height()) / 2; if (y_px < 0) { y_px = 0; } p.drawPixmap(lnX + x_px, y + y_px, px_mark); } } } } } lnX += iconPaneWidth + 2; } // annotation information if (m_annotationBorderOn) { // Draw a border line between annotations and the line numbers p.setPen(m_view->renderer()->config()->lineNumberColor()); p.setBrush(m_view->renderer()->config()->lineNumberColor()); int borderWidth = m_annotationBorderWidth; p.drawLine(lnX + borderWidth + 1, y, lnX + borderWidth + 1, y + h); if ((realLine > -1) && model) { // Fetch data from the model QVariant text = model->data(realLine, Qt::DisplayRole); QVariant foreground = model->data(realLine, Qt::ForegroundRole); QVariant background = model->data(realLine, Qt::BackgroundRole); // Fill the background if (background.isValid()) { p.fillRect(lnX, y, borderWidth + 1, h, background.value()); } // Set the pen for drawing the foreground if (foreground.isValid()) { p.setPen(foreground.value()); } // Draw a border around all adjacent entries that have the same text as the currently hovered one const QVariant identifier = model->data( realLine, (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ); if( m_hoveredAnnotationGroupIdentifier == identifier.toString() ) { p.drawLine(lnX, y, lnX, y + h); p.drawLine(lnX + borderWidth, y, lnX + borderWidth, y + h); QVariant beforeText = model->data(realLine - 1, Qt::DisplayRole); QVariant afterText = model->data(realLine + 1, Qt::DisplayRole); if (((beforeText.isValid() && beforeText.canConvert() && text.isValid() && text.canConvert() && beforeText.toString() != text.toString()) || realLine == 0) && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawLine(lnX + 1, y, lnX + borderWidth, y); } if (((afterText.isValid() && afterText.canConvert() && text.isValid() && text.canConvert() && afterText.toString() != text.toString()) || realLine == m_view->doc()->lines() - 1) && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawLine(lnX + 1, y + h - 1, lnX + borderWidth, y + h - 1); } } if (foreground.isValid()) { QPen pen = p.pen(); pen.setWidth(1); p.setPen(pen); } // Now draw the normal text if (text.isValid() && text.canConvert() && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { p.drawText(lnX + 3, y, borderWidth - 3, h, Qt::AlignLeft | Qt::AlignVCenter, text.toString()); } } // adjust current X position and reset the pen and brush lnX += borderWidth + 2; } // line number if (m_lineNumbersOn || (m_view->dynWordWrap() && m_dynWrapIndicatorsOn)) { if (realLine > -1) { int distanceToCurrent = abs(realLine - static_cast(currentLine)); QColor color; if (distanceToCurrent == 0) { color = m_view->renderer()->config()->currentLineNumberColor(); } else { color = m_view->renderer()->config()->lineNumberColor(); } p.setPen(color); p.setBrush(color); if (m_viewInternal->cache()->viewLine(z).startCol() == 0) { if (m_relLineNumbersOn) { if (distanceToCurrent == 0) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignLeft|Qt::AlignVCenter, QString::number(realLine + 1)); } else { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip|Qt::AlignRight|Qt::AlignVCenter, QString::number(distanceToCurrent)); } if (m_updateRelLineNumbers) { m_updateRelLineNumbers = false; update(); } } else if (m_lineNumbersOn) { p.drawText(lnX + m_maxCharWidth / 2, y, lnWidth - m_maxCharWidth, h, Qt::TextDontClip | Qt::AlignRight | Qt::AlignVCenter, QString::number(realLine + 1)); } } else if (m_view->dynWordWrap() && m_dynWrapIndicatorsOn) { p.drawPixmap(lnX + lnWidth - (m_arrow.width() / m_arrow.devicePixelRatio()) - 2, y, m_arrow); } } lnX += lnWidth + 2; } // folding markers if (m_foldingMarkersOn) { // first icon border background p.fillRect(lnX, y, iconPaneWidth, h, m_view->renderer()->config()->iconBarColor()); // possible additional folding highlighting if ((realLine >= 0) && m_foldingRange && m_foldingRange->overlapsLine(realLine)) { p.save(); // use linear gradient as brush QLinearGradient g(lnX, y, lnX + iconPaneWidth, y); const QColor foldingColor(m_view->renderer()->config()->foldingColor()); g.setColorAt(0, foldingColor); g.setColorAt(0.3, foldingColor.lighter(110)); g.setColorAt(1, foldingColor); p.setBrush(g); p.setPen(foldingColor); p.setClipRect(lnX, y, iconPaneWidth, h); p.setRenderHint(QPainter::Antialiasing); const qreal r = 4.0; if (m_foldingRange->start().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == 0) { p.drawRect(lnX, y, iconPaneWidth, h + r); } else if (m_foldingRange->end().line() == realLine && m_viewInternal->cache()->viewLine(z).viewLine() == m_viewInternal->cache()->viewLineCount(realLine) - 1) { p.drawRect(lnX, y - r, iconPaneWidth, h + r); } else { p.drawRect(lnX, y - r, iconPaneWidth, h + 2 * r); } p.restore(); } if ((realLine >= 0) && (m_viewInternal->cache()->viewLine(z).startCol() == 0)) { QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(realLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } Kate::TextLine tl = m_doc->kateTextLine(realLine); if (!startingRanges.isEmpty() || tl->markedAsFoldingStart()) { if (anyFolded) { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, false); } else { paintTriangle(p, m_view->renderer()->config()->foldingColor(), lnX, y, iconPaneWidth, h, true); } } } lnX += iconPaneWidth; } // modified line system if (m_view->config()->lineModification() && realLine > -1 && !m_doc->url().isEmpty()) { // one pixel space ++lnX; Kate::TextLine tl = m_doc->plainKateTextLine(realLine); if (tl->markedAsModified()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->modifiedLineColor()); } if (tl->markedAsSavedOnDisk()) { p.fillRect(lnX, y, 3, h, m_view->renderer()->config()->savedLineColor()); } } } } KateIconBorder::BorderArea KateIconBorder::positionToArea(const QPoint &p) const { // see KateIconBorder::sizeHint() for pixel spacings int x = 0; if (m_iconBorderOn) { x += iconPaneWidth; if (p.x() <= x) { return IconBorder; } x += 2; } if (this->m_annotationBorderOn) { x += m_annotationBorderWidth; if (p.x() <= x) { return AnnotationBorder; } x += 2; } if (m_lineNumbersOn || m_dynWrapIndicators) { x += lineNumberWidth(); if (p.x() <= x) { return LineNumbers; } x += 2; } if (m_foldingMarkersOn) { x += iconPaneWidth; if (p.x() <= x) { return FoldingMarkers; } } if (m_view->config()->lineModification()) { x += 3 + 2; if (p.x() <= x) { return ModificationBorder; } } return None; } void KateIconBorder::mousePressEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { m_lastClickedLine = t.line(); if (positionToArea(e->pos()) != IconBorder && positionToArea(e->pos()) != AnnotationBorder) { QMouseEvent forward(QEvent::MouseButtonPress, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mousePressEvent(&forward); } return e->accept(); } QWidget::mousePressEvent(e); } void KateIconBorder::showDelayedBlock(int line) { // save the line over which the mouse hovers // either we start the timer for delay, or we show the block immediately // if the moving range already exists m_nextHighlightBlock = line; if (!m_foldingRange) { if (!m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.start(); } } else { showBlock(); } } void KateIconBorder::showBlock() { if (m_nextHighlightBlock == m_currentBlockLine) { return; } m_currentBlockLine = m_nextHighlightBlock; if (m_currentBlockLine >= m_doc->buffer().lines()) { return; } /** * compute to which folding range we belong * FIXME: optimize + perhaps have some better threshold or use timers! */ KTextEditor::Range newRange = KTextEditor::Range::invalid(); for (int line = m_currentBlockLine; line >= qMax(0, m_currentBlockLine - 1024); --line) { /** * try if we have folding range from that line, should be fast per call */ KTextEditor::Range foldingRange = m_doc->buffer().computeFoldingRangeForStartLine(line); if (!foldingRange.isValid()) { continue; } /** * does the range reach us? */ if (foldingRange.overlapsLine(m_currentBlockLine)) { newRange = foldingRange; break; } } if (newRange.isValid() && m_foldingRange && *m_foldingRange == newRange) { // new range equals the old one, nothing to do. return; } else { // the ranges differ, delete the old, if it exists delete m_foldingRange; m_foldingRange = nullptr; } if (newRange.isValid()) { //qCDebug(LOG_KTE) << "new folding hl-range:" << newRange; m_foldingRange = m_doc->newMovingRange(newRange, KTextEditor::MovingRange::ExpandRight); KTextEditor::Attribute::Ptr attr(new KTextEditor::Attribute()); /** * create highlighting color with alpha for the range! */ QColor result = m_view->renderer()->config()->foldingColor(); result.setAlphaF(0.5); attr->setBackground(QBrush(result)); m_foldingRange->setView(m_view); // use z depth defined in moving ranges interface m_foldingRange->setZDepth(-100.0); m_foldingRange->setAttribute(attr); } // show text preview, if a folded region starts here bool foldUnderMouse = false; if (m_foldingRange && m_view->config()->foldingPreview()) { const QPoint globalPos = QCursor::pos(); const QPoint pos = mapFromGlobal(globalPos); const KateTextLayout &t = m_view->m_viewInternal->yToKateTextLayout(pos.y()); if (t.isValid() && positionToArea(pos) == FoldingMarkers) { const int realLine = t.line(); foldUnderMouse = !m_view->textFolding().isLineVisible(realLine + 1); if (foldUnderMouse) { if (!m_foldingPreview) { m_foldingPreview = new KateTextPreview(m_view); m_foldingPreview->setAttribute(Qt::WA_ShowWithoutActivating); m_foldingPreview->setFrameStyle(QFrame::StyledPanel); // event filter to catch application WindowDeactivate event, to hide the preview window // qApp->installEventFilter(this); } // TODO: use KateViewInternal::maxLen() somehow to compute proper width for amount of lines to display // try using the end line of the range for proper popup height const int lineCount = qMin(m_foldingRange->numberOfLines() + 1, (height() - pos.y()) / m_view->renderer()->lineHeight()); m_foldingPreview->resize(m_view->width() / 2, lineCount * m_view->renderer()->lineHeight() + 2 * m_foldingPreview->frameWidth()); const int xGlobal = mapToGlobal(QPoint(width(), 0)).x(); const int yGlobal = m_view->mapToGlobal(m_view->cursorToCoordinate(KTextEditor::Cursor(realLine, 0))).y(); m_foldingPreview->move(QPoint(xGlobal, yGlobal) - m_foldingPreview->contentsRect().topLeft()); m_foldingPreview->setLine(realLine); m_foldingPreview->setCenterView(false); m_foldingPreview->setShowFoldedLines(true); m_foldingPreview->raise(); m_foldingPreview->show(); } } } if (!foldUnderMouse) { delete m_foldingPreview; } /** * repaint */ repaint(); } void KateIconBorder::hideBlock() { if (m_delayFoldingHlTimer.isActive()) { m_delayFoldingHlTimer.stop(); } m_nextHighlightBlock = -2; m_currentBlockLine = -1; delete m_foldingRange; m_foldingRange = nullptr; delete m_foldingPreview; } void KateIconBorder::leaveEvent(QEvent *event) { hideBlock(); removeAnnotationHovering(); QWidget::leaveEvent(event); } void KateIconBorder::mouseMoveEvent(QMouseEvent *e) { const KateTextLayout &t = m_viewInternal->yToKateTextLayout(e->y()); if (t.isValid()) { if (positionToArea(e->pos()) == FoldingMarkers) { showDelayedBlock(t.line()); } else { hideBlock(); } if (positionToArea(e->pos()) == AnnotationBorder) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { m_hoveredAnnotationGroupIdentifier = model->data( t.line(), (Qt::ItemDataRole) KTextEditor::AnnotationModel::GroupIdentifierRole ).toString(); showAnnotationTooltip(t.line(), e->globalPos()); QTimer::singleShot(0, this, SLOT(update())); } } else { if (positionToArea(e->pos()) == IconBorder) { m_doc->requestMarkTooltip(t.line(), e->globalPos()); } m_hoveredAnnotationGroupIdentifier.clear(); hideAnnotationTooltip(); QTimer::singleShot(0, this, SLOT(update())); } if (positionToArea(e->pos()) != IconBorder) { QPoint p = m_viewInternal->mapFromGlobal(e->globalPos()); QMouseEvent forward(QEvent::MouseMove, p, e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseMoveEvent(&forward); } } else { // remove hovering if it's still there removeAnnotationHovering(); } QWidget::mouseMoveEvent(e); } void KateIconBorder::mouseReleaseEvent(QMouseEvent *e) { const int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine >= 0 && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); if (area == IconBorder) { if (e->button() == Qt::LeftButton) { if (!m_doc->handleMarkClick(cursorOnLine)) { KateViewConfig *config = m_view->config(); const uint editBits = m_doc->editableMarks(); // is the default or the only editable mark const uint singleMark = qPopulationCount(editBits) > 1 ? editBits & config->defaultMarkType() : editBits; if (singleMark) { if (m_doc->mark(cursorOnLine) & singleMark) { m_doc->removeMark(cursorOnLine, singleMark); } else { m_doc->addMark(cursorOnLine, singleMark); } } else if (config->allowMarkMenu()) { showMarkMenu(cursorOnLine, QCursor::pos()); } } } else if (e->button() == Qt::RightButton) { showMarkMenu(cursorOnLine, QCursor::pos()); } } if (area == FoldingMarkers) { // ask the folding info for this line, if any folds are around! QVector > startingRanges = m_view->textFolding().foldingRangesStartingOnLine(cursorOnLine); bool anyFolded = false; for (int i = 0; i < startingRanges.size(); ++i) if (startingRanges[i].second & Kate::TextFolding::Folded) { anyFolded = true; } // fold or unfold all ranges, remember if any action happened! bool actionDone = false; for (int i = 0; i < startingRanges.size(); ++i) { actionDone = (anyFolded ? m_view->textFolding().unfoldRange(startingRanges[i].first) : m_view->textFolding().foldRange(startingRanges[i].first)) || actionDone; } // if no action done, try to fold it, create non-persistent folded range, if possible! if (!actionDone) { // either use the fold for this line or the range that is highlighted ATM if any! KTextEditor::Range foldingRange = m_view->doc()->buffer().computeFoldingRangeForStartLine(cursorOnLine); if (!foldingRange.isValid() && m_foldingRange) { foldingRange = m_foldingRange->toRange(); } m_view->textFolding().newFoldingRange(foldingRange, Kate::TextFolding::Folded); } delete m_foldingPreview; } if (area == AnnotationBorder) { const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (e->button() == Qt::LeftButton && singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } else if (e->button() == Qt::RightButton) { showAnnotationMenu(cursorOnLine, e->globalPos()); } } } QMouseEvent forward(QEvent::MouseButtonRelease, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseReleaseEvent(&forward); } void KateIconBorder::mouseDoubleClickEvent(QMouseEvent *e) { int cursorOnLine = m_viewInternal->yToKateTextLayout(e->y()).line(); if (cursorOnLine == m_lastClickedLine && cursorOnLine <= m_doc->lastLine()) { BorderArea area = positionToArea(e->pos()); const bool singleClick = style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, nullptr, this); if (area == AnnotationBorder && !singleClick) { emit m_view->annotationActivated(m_view, cursorOnLine); } } QMouseEvent forward(QEvent::MouseButtonDblClick, QPoint(0, e->y()), e->button(), e->buttons(), e->modifiers()); m_viewInternal->mouseDoubleClickEvent(&forward); } void KateIconBorder::wheelEvent(QWheelEvent *e) { QCoreApplication::sendEvent(m_viewInternal, e); } void KateIconBorder::showMarkMenu(uint line, const QPoint &pos) { if (m_doc->handleMarkContextMenu(line, pos)) { return; } if (!m_view->config()->allowMarkMenu()) { return; } QMenu markMenu; QMenu selectDefaultMark; auto selectDefaultMarkActionGroup = new QActionGroup(&selectDefaultMark); QVector vec(33); int i = 1; for (uint bit = 0; bit < 32; bit++) { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes)(1 << bit); if (!(m_doc->editableMarks() & markType)) { continue; } QAction *mA; QAction *dMA; const QPixmap icon = m_doc->markPixmap(markType); if (!m_doc->markDescription(markType).isEmpty()) { mA = markMenu.addAction(icon, m_doc->markDescription(markType)); dMA = selectDefaultMark.addAction(icon, m_doc->markDescription(markType)); } else { mA = markMenu.addAction(icon, i18n("Mark Type %1", bit + 1)); dMA = selectDefaultMark.addAction(icon, i18n("Mark Type %1", bit + 1)); } selectDefaultMarkActionGroup->addAction(dMA); mA->setData(i); mA->setCheckable(true); dMA->setData(i + 100); dMA->setCheckable(true); if (m_doc->mark(line) & markType) { mA->setChecked(true); } if (markType & KateViewConfig::global()->defaultMarkType()) { dMA->setChecked(true); } vec[i++] = markType; } if (markMenu.actions().count() == 0) { return; } if (markMenu.actions().count() > 1) { markMenu.addAction(i18n("Set Default Mark Type"))->setMenu(&selectDefaultMark); } QAction *rA = markMenu.exec(pos); if (!rA) { return; } int result = rA->data().toInt(); if (result > 100) { KateViewConfig::global()->setDefaultMarkType(vec[result - 100]); } else { MarkInterface::MarkTypes markType = (MarkInterface::MarkTypes) vec[result]; if (m_doc->mark(line) & markType) { m_doc->removeMark(line, markType); } else { m_doc->addMark(line, markType); } } } void KateIconBorder::showAnnotationTooltip(int line, const QPoint &pos) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::ToolTipRole); QString tip = data.toString(); if (!tip.isEmpty()) { QToolTip::showText(pos, data.toString(), this); } } } int KateIconBorder::annotationLineWidth(int line) { KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { QVariant data = model->data(line, Qt::DisplayRole); return data.toString().length() * m_maxCharWidth + 8; } return 8; } void KateIconBorder::updateAnnotationLine(int line) { if (annotationLineWidth(line) > m_annotationBorderWidth) { m_annotationBorderWidth = annotationLineWidth(line); updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } } void KateIconBorder::showAnnotationMenu(int line, const QPoint &pos) { QMenu menu; QAction a(i18n("Disable Annotation Bar"), &menu); a.setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); menu.addAction(&a); emit m_view->annotationContextMenuAboutToShow(m_view, &menu, line); if (menu.exec(pos) == &a) { m_view->setAnnotationBorderVisible(false); } } void KateIconBorder::hideAnnotationTooltip() { QToolTip::hideText(); } void KateIconBorder::updateAnnotationBorderWidth() { m_annotationBorderWidth = 6; KTextEditor::AnnotationModel *model = m_view->annotationModel() ? m_view->annotationModel() : m_doc->annotationModel(); if (model) { for (int i = 0; i < m_view->doc()->lines(); i++) { int curwidth = annotationLineWidth(i); if (curwidth > m_annotationBorderWidth) { m_annotationBorderWidth = curwidth; } } } updateGeometry(); QTimer::singleShot(0, this, SLOT(update())); } void KateIconBorder::annotationModelChanged(KTextEditor::AnnotationModel *oldmodel, KTextEditor::AnnotationModel *newmodel) { if (oldmodel) { oldmodel->disconnect(this); } if (newmodel) { connect(newmodel, SIGNAL(reset()), this, SLOT(updateAnnotationBorderWidth())); connect(newmodel, SIGNAL(lineChanged(int)), this, SLOT(updateAnnotationLine(int))); } updateAnnotationBorderWidth(); } void KateIconBorder::displayRangeChanged() { hideBlock(); removeAnnotationHovering(); } //END KateIconBorder //BEGIN KateViewEncodingAction // Acording to http://www.iana.org/assignments/ianacharset-mib // the default/unknown mib value is 2. #define MIB_DEFAULT 2 bool lessThanAction(KSelectAction *a, KSelectAction *b) { return a->text() < b->text(); } void KateViewEncodingAction::Private::init() { QList actions; q->setToolBarMode(MenuMode); int i; foreach (const QStringList &encodingsForScript, KCharsets::charsets()->encodingsByScript()) { KSelectAction *tmp = new KSelectAction(encodingsForScript.at(0), q); for (i = 1; i < encodingsForScript.size(); ++i) { tmp->addAction(encodingsForScript.at(i)); } q->connect(tmp, SIGNAL(triggered(QAction*)), q, SLOT(_k_subActionTriggered(QAction*))); //tmp->setCheckable(true); actions << tmp; } qSort(actions.begin(), actions.end(), lessThanAction); foreach (KSelectAction *action, actions) { q->addAction(action); } } void KateViewEncodingAction::Private::_k_subActionTriggered(QAction *action) { if (currentSubAction == action) { return; } currentSubAction = action; bool ok = false; int mib = q->mibForName(action->text(), &ok); if (ok) { emit q->KSelectAction::triggered(action->text()); emit q->triggered(q->codecForMib(mib)); } } KateViewEncodingAction::KateViewEncodingAction(KTextEditor::DocumentPrivate *_doc, KTextEditor::ViewPrivate *_view, const QString &text, QObject *parent, bool saveAsMode) : KSelectAction(text, parent), doc(_doc), view(_view), d(new Private(this)) , m_saveAsMode(saveAsMode) { d->init(); connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); } KateViewEncodingAction::~KateViewEncodingAction() { delete d; } void KateViewEncodingAction::slotAboutToShow() { setCurrentCodec(doc->config()->encoding()); } void KateViewEncodingAction::setEncoding(const QString &e) { /** * in save as mode => trigger saveAs */ if (m_saveAsMode) { doc->documentSaveAsWithEncoding(e); return; } /** * else switch encoding */ doc->userSetEncodingForNextReload(); doc->setEncoding(e); view->reloadFile(); } int KateViewEncodingAction::mibForName(const QString &codecName, bool *ok) const { // FIXME logic is good but code is ugly bool success = false; int mib = MIB_DEFAULT; KCharsets *charsets = KCharsets::charsets(); QTextCodec *codec = charsets->codecForName(codecName, success); if (!success) { // Maybe we got a description name instead codec = charsets->codecForName(charsets->encodingForName(codecName), success); } if (codec) { mib = codec->mibEnum(); } if (ok) { *ok = success; } if (success) { return mib; } qCWarning(LOG_KTE) << "Invalid codec name: " << codecName; return MIB_DEFAULT; } QTextCodec *KateViewEncodingAction::codecForMib(int mib) const { if (mib == MIB_DEFAULT) { // FIXME offer to change the default codec return QTextCodec::codecForLocale(); } else { return QTextCodec::codecForMib(mib); } } QTextCodec *KateViewEncodingAction::currentCodec() const { return codecForMib(currentCodecMib()); } bool KateViewEncodingAction::setCurrentCodec(QTextCodec *codec) { disconnect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); int i, j; for (i = 0; i < actions().size(); ++i) { if (actions().at(i)->menu()) { for (j = 0; j < actions().at(i)->menu()->actions().size(); ++j) { if (!j && !actions().at(i)->menu()->actions().at(j)->data().isNull()) { continue; } if (actions().at(i)->menu()->actions().at(j)->isSeparator()) { continue; } if (codec == KCharsets::charsets()->codecForName(actions().at(i)->menu()->actions().at(j)->text())) { d->currentSubAction = actions().at(i)->menu()->actions().at(j); d->currentSubAction->setChecked(true); } else { actions().at(i)->menu()->actions().at(j)->setChecked(false); } } } } connect(this, SIGNAL(triggered(QString)), this, SLOT(setEncoding(QString))); return true; } QString KateViewEncodingAction::currentCodecName() const { return d->currentSubAction->text(); } bool KateViewEncodingAction::setCurrentCodec(const QString &codecName) { return setCurrentCodec(KCharsets::charsets()->codecForName(codecName)); } int KateViewEncodingAction::currentCodecMib() const { return mibForName(currentCodecName()); } bool KateViewEncodingAction::setCurrentCodec(int mib) { return setCurrentCodec(codecForMib(mib)); } //END KateViewEncodingAction //BEGIN KateViewBar related classes KateViewBarWidget::KateViewBarWidget(bool addCloseButton, QWidget *parent) : QWidget(parent) , m_viewBar(nullptr) { QHBoxLayout *layout = new QHBoxLayout(this); // NOTE: Here be cosmetics. layout->setMargin(0); // hide button if (addCloseButton) { QToolButton *hideButton = new QToolButton(this); hideButton->setAutoRaise(true); hideButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close"))); connect(hideButton, SIGNAL(clicked()), SIGNAL(hideMe())); layout->addWidget(hideButton); layout->setAlignment(hideButton, Qt::AlignLeft | Qt::AlignTop); } // widget to be used as parent for the real content m_centralWidget = new QWidget(this); layout->addWidget(m_centralWidget); setLayout(layout); setFocusProxy(m_centralWidget); } KateViewBar::KateViewBar(bool external, QWidget *parent, KTextEditor::ViewPrivate *view) : QWidget(parent), m_external(external), m_view(view), m_permanentBarWidget(nullptr) { m_layout = new QVBoxLayout(this); m_stack = new QStackedWidget(this); m_layout->addWidget(m_stack); m_layout->setMargin(0); m_stack->hide(); hide(); } void KateViewBar::addBarWidget(KateViewBarWidget *newBarWidget) { // just ignore additional adds for already existing widgets if (hasBarWidget(newBarWidget)) { return; } // add new widget, invisible... newBarWidget->hide(); m_stack->addWidget(newBarWidget); newBarWidget->setAssociatedViewBar(this); connect(newBarWidget, SIGNAL(hideMe()), SLOT(hideCurrentBarWidget())); } void KateViewBar::removeBarWidget(KateViewBarWidget *barWidget) { // remove only if there if (!hasBarWidget(barWidget)) { return; } m_stack->removeWidget(barWidget); barWidget->setAssociatedViewBar(nullptr); barWidget->hide(); disconnect(barWidget, nullptr, this, nullptr); } void KateViewBar::addPermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget); Q_ASSERT(!m_permanentBarWidget); m_stack->addWidget(barWidget); m_stack->setCurrentWidget(barWidget); m_stack->show(); m_permanentBarWidget = barWidget; m_permanentBarWidget->show(); setViewBarVisible(true); } void KateViewBar::removePermanentBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(m_permanentBarWidget == barWidget); Q_UNUSED(barWidget); const bool hideBar = m_stack->currentWidget() == m_permanentBarWidget; m_permanentBarWidget->hide(); m_stack->removeWidget(m_permanentBarWidget); m_permanentBarWidget = nullptr; if (hideBar) { m_stack->hide(); setViewBarVisible(false); } } bool KateViewBar::hasPermanentWidget(KateViewBarWidget *barWidget) const { return (m_permanentBarWidget == barWidget); } void KateViewBar::showBarWidget(KateViewBarWidget *barWidget) { Q_ASSERT(barWidget != nullptr); if (barWidget != qobject_cast(m_stack->currentWidget())) { hideCurrentBarWidget(); } // raise correct widget m_stack->setCurrentWidget(barWidget); barWidget->show(); barWidget->setFocus(Qt::ShortcutFocusReason); m_stack->show(); setViewBarVisible(true); } bool KateViewBar::hasBarWidget(KateViewBarWidget *barWidget) const { return m_stack->indexOf(barWidget) != -1; } void KateViewBar::hideCurrentBarWidget() { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (current) { current->closed(); } // if we have any permanent widget, make it visible again if (m_permanentBarWidget) { m_stack->setCurrentWidget (m_permanentBarWidget); } else { // else: hide the bar m_stack->hide(); setViewBarVisible(false); } m_view->setFocus(); } void KateViewBar::setViewBarVisible(bool visible) { if (m_external) { if (visible) { m_view->mainWindow()->showViewBar(m_view); } else { m_view->mainWindow()->hideViewBar(m_view); } } else { setVisible(visible); } } bool KateViewBar::hiddenOrPermanent() const { KateViewBarWidget *current = qobject_cast(m_stack->currentWidget()); if (!isVisible() || (m_permanentBarWidget && m_permanentBarWidget == current)) { return true; } return false; } void KateViewBar::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Escape) { hideCurrentBarWidget(); return; } QWidget::keyPressEvent(event); } void KateViewBar::hideEvent(QHideEvent *event) { Q_UNUSED(event); // if (!event->spontaneous()) // m_view->setFocus(); } //END KateViewBar related classes KatePasteMenu::KatePasteMenu(const QString &text, KTextEditor::ViewPrivate *view) : KActionMenu(text, view) , m_view(view) { connect(menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShow())); } void KatePasteMenu::slotAboutToShow() { menu()->clear(); /** * insert complete paste history */ int i = 0; Q_FOREACH (const auto &texts, KTextEditor::EditorPrivate::self()->clipboardHistory()) { /** * get text for the menu ;) */ QString text; Q_FOREACH (const auto& t, texts) { if ( !text.isEmpty() ) { text.append(QLatin1String(" ")); } text.append(t); } if ( texts.size() > 1 ) { text.prepend(QLatin1String("[") + i18nc("%1 entries", "always plural", texts.size()) + QLatin1String("] ")); } QString leftPart = (text.size() > 48) ? (text.left(48) + QLatin1String("...")) : text; QAction *a = menu()->addAction(leftPart.replace(QLatin1String("\n"), QLatin1String(" ")), this, SLOT(paste())); a->setData(i++); } } void KatePasteMenu::paste() { if (!sender()) { return; } QAction *action = qobject_cast(sender()); if (!action) { return; } // get index int i = action->data().toInt(); if (i >= KTextEditor::EditorPrivate::self()->clipboardHistory().size()) { return; } // paste m_view->pasteInternal(KTextEditor::EditorPrivate::self()->clipboardHistory().at(i)); } diff --git a/src/view/kateviewinternal.cpp b/src/view/kateviewinternal.cpp index 20d651d3..e01dba4c 100644 --- a/src/view/kateviewinternal.cpp +++ b/src/view/kateviewinternal.cpp @@ -1,3265 +1,3263 @@ /* This file is part of the KDE libraries Copyright (C) 2002 John Firebaugh Copyright (C) 2002 Joseph Wenninger Copyright (C) 2002,2003 Christoph Cullmann Copyright (C) 2002-2007 Hamish Rodda Copyright (C) 2003 Anakim Border Copyright (C) 2007 Mirko Stocker Copyright (C) 2007 Matthew Woehlke Copyright (C) 2008 Erlend Hamberg Copyright (C) 2016 Sven Brauch Based on: KWriteView : Copyright (C) 1999 Jochen Wilhelmy This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kateviewinternal.h" #include "kateview.h" #include "kateviewhelpers.h" #include "katehighlight.h" #include "katebuffer.h" #include "katerenderer.h" #include "kateconfig.h" #include "katelayoutcache.h" #include "katecompletionwidget.h" #include "spellcheck/spellingmenu.h" #include "kateviewaccessible.h" #include "katetextanimation.h" #include "katemessagewidget.h" #include "kateglobal.h" #include "kateabstractinputmodefactory.h" #include "kateabstractinputmode.h" #include "katepartdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const bool debugPainting = false; class ZoomEventFilter { public: - ZoomEventFilter(KateViewInternal *vi) - { - } + ZoomEventFilter() = default; bool detectZoomingEvent(QWheelEvent *e, Qt::KeyboardModifiers modifier = Qt::ControlModifier) { Qt::KeyboardModifiers modState = e->modifiers(); if (modState == modifier) { if (m_lastWheelEvent.isValid()) { const qint64 deltaT = m_lastWheelEvent.elapsed(); // Pressing the specified modifier key within 200ms of the previous "unmodified" // wheelevent is not allowed to toggle on text zooming if (m_lastWheelEventUnmodified && deltaT < 200) { m_ignoreZoom = true; } else if (deltaT > 1000) { // the protection is kept active for 1s after the last wheel event // TODO: this value should be tuned, preferrably by someone using // Ctrl+Wheel zooming frequently. m_ignoreZoom = false; } } else { // we can't say anything and have to assume there's nothing // accidental to the modifier being pressed. m_ignoreZoom = false; } m_lastWheelEventUnmodified = false; if (m_ignoreZoom) { // unset the modifier so the view scrollbars can handle the scroll // event and produce normal, not accelerated scrolling modState &= ~modifier; e->setModifiers(modState); } } else { // state is reset after any wheel event without the zoom modifier m_lastWheelEventUnmodified = true; m_ignoreZoom = false; } m_lastWheelEvent.start(); // inform the caller whether this event is allowed to trigger text zooming. return !m_ignoreZoom && modState == modifier; } protected: QElapsedTimer m_lastWheelEvent; bool m_ignoreZoom = false; bool m_lastWheelEventUnmodified = false; }; KateViewInternal::KateViewInternal(KTextEditor::ViewPrivate *view) : QWidget(view) , editSessionNumber(0) , editIsRunning(false) , m_view(view) , m_cursors(this) , m_selections(this) , m_mouse() , m_possibleTripleClick(false) , m_completionItemExpanded(false) , m_bm(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmStart(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmEnd(doc()->newMovingRange(KTextEditor::Range::invalid(), KTextEditor::MovingRange::DoNotExpand)) , m_bmLastFlashPos(doc()->newMovingCursor(KTextEditor::Cursor::invalid())) , m_dummy(nullptr) // stay on cursor will avoid that the view scroll around on press return at beginning , m_startPos(doc()->buffer(), KTextEditor::Cursor(0, 0), Kate::TextCursor::StayOnInsert) , m_visibleLineCount(0) , m_madeVisible(false) , m_shiftKeyPressed(false) , m_autoCenterLines(0) , m_minLinesVisible(0) , m_selChangedByUser(false) , m_selectAnchor(-1, -1) , m_layoutCache(new KateLayoutCache(renderer(), this)) , m_cachedMaxStartPos(-1, -1) , m_dragScrollTimer(this) , m_scrollTimer(this) , m_cursorTimer(this) , m_textHintTimer(this) , m_textHintDelay(500) , m_textHintPos(-1, -1) , m_imPreeditRange(nullptr) { QList factories = KTextEditor::EditorPrivate::self()->inputModeFactories(); Q_FOREACH(KateAbstractInputModeFactory *factory, factories) { KateAbstractInputMode *m = factory->createInputMode(this); m_inputModes.insert(m->viewInputMode(), m); } m_currentInputMode = m_inputModes[KTextEditor::View::NormalInputMode]; // TODO: twisted, but needed setMinimumSize(0, 0); setAttribute(Qt::WA_OpaquePaintEvent); setAttribute(Qt::WA_InputMethodEnabled); // bracket markers are only for this view and should not be printed m_bm->setView(m_view); m_bmStart->setView(m_view); m_bmEnd->setView(m_view); m_bm->setAttributeOnlyForViews(true); m_bmStart->setAttributeOnlyForViews(true); m_bmEnd->setAttributeOnlyForViews(true); // use z depth defined in moving ranges interface m_bm->setZDepth(-1000.0); m_bmStart->setZDepth(-1000.0); m_bmEnd->setZDepth(-1000.0); // update mark attributes updateBracketMarkAttributes(); // // scrollbar for lines // m_lineScroll = new KateScrollBar(Qt::Vertical, this); m_lineScroll->show(); m_lineScroll->setTracking(true); m_lineScroll->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); // Hijack the line scroller's controls, so we can scroll nicely for word-wrap connect(m_lineScroll, SIGNAL(actionTriggered(int)), SLOT(scrollAction(int))); connect(m_lineScroll, SIGNAL(sliderMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(sliderMMBMoved(int)), SLOT(scrollLines(int))); connect(m_lineScroll, SIGNAL(valueChanged(int)), SLOT(scrollLines(int))); // // scrollbar for columns // m_columnScroll = new QScrollBar(Qt::Horizontal, m_view); if (m_view->dynWordWrap()) { m_columnScroll->hide(); } else { m_columnScroll->show(); } m_columnScroll->setTracking(true); m_startX = 0; connect(m_columnScroll, SIGNAL(valueChanged(int)), SLOT(scrollColumns(int))); // bottom corner box m_dummy = new QWidget(m_view); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_dummy->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); if (m_view->dynWordWrap()) { m_dummy->hide(); } else { m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); // // iconborder ;) // m_leftBorder = new KateIconBorder(this, m_view); m_leftBorder->show(); // update view if folding ranges change connect(&m_view->textFolding(), SIGNAL(foldingRangesChanged()), SLOT(slotRegionVisibilityChanged())); m_displayCursor.setPosition(0, 0); setAcceptDrops(true); - m_zoomEventFilter = new ZoomEventFilter(this); + m_zoomEventFilter = new ZoomEventFilter(); // event filter installEventFilter(this); // set initial cursor m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); // call mouseMoveEvent also if no mouse button is pressed setMouseTracking(true); m_dragInfo.state = diNone; // timers connect(&m_dragScrollTimer, SIGNAL(timeout()), this, SLOT(doDragScroll())); connect(&m_scrollTimer, SIGNAL(timeout()), this, SLOT(scrollTimeout())); connect(&m_cursorTimer, SIGNAL(timeout()), this, SLOT(cursorTimeout())); connect(&m_textHintTimer, SIGNAL(timeout()), this, SLOT(textHintTimeout())); #ifndef QT_NO_ACCESSIBILITY QAccessible::installFactory(accessibleInterfaceFactory); #endif connect(doc(), &KTextEditor::DocumentPrivate::textInserted, this, &KateViewInternal::documentTextInserted); connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &KateViewInternal::documentTextRemoved); // update is called in KTextEditor::ViewPrivate, after construction and layout is over // but before any other kateviewinternal call } KateViewInternal::~KateViewInternal() { // delete text animation object here, otherwise it updates the view in its destructor delete m_textAnimation; #ifndef QT_NO_ACCESSIBILITY QAccessible::removeFactory(accessibleInterfaceFactory); #endif // kill preedit ranges delete m_imPreeditRange; qDeleteAll(m_imPreeditRangeChildren); qDeleteAll(m_inputModes); // delete bracket markers delete m_bm; delete m_bmStart; delete m_bmEnd; delete m_zoomEventFilter; } void KateViewInternal::prepareForDynWrapChange() { // Which is the current view line? m_wrapChangeViewLine = cache()->displayViewLine(m_displayCursor, true); } void KateViewInternal::dynWrapChanged() { m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); if (m_view->dynWordWrap()) { m_columnScroll->hide(); m_dummy->hide(); } else { // column scrollbar + bottom corner box m_columnScroll->show(); m_dummy->show(); } cache()->setWrap(m_view->dynWordWrap()); updateView(); if (m_view->dynWordWrap()) { scrollColumns(0); } // Determine where the cursor should be to get the cursor on the same view line if (m_wrapChangeViewLine != -1) { KTextEditor::Cursor newStart = viewLineOffset(m_displayCursor, -m_wrapChangeViewLine); makeVisible(newStart, newStart.column(), true); } else { update(); } } KTextEditor::Cursor KateViewInternal::endPos() const { // Hrm, no lines laid out at all?? if (!cache()->viewCacheLineCount()) { return KTextEditor::Cursor(); } for (int i = qMin(linesDisplayed() - 1, cache()->viewCacheLineCount() - 1); i >= 0; i--) { const KateTextLayout &thisLine = cache()->viewLine(i); if (thisLine.line() == -1) { continue; } if (thisLine.virtualLine() >= m_view->textFolding().visibleLines()) { // Cache is too out of date return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } return KTextEditor::Cursor(thisLine.virtualLine(), thisLine.wrap() ? thisLine.endCol() - 1 : thisLine.endCol()); } // can happen, if view is still invisible return KTextEditor::Cursor(); } int KateViewInternal::endLine() const { return endPos().line(); } KateTextLayout KateViewInternal::yToKateTextLayout(int y) const { if (y < 0 || y > size().height()) { return KateTextLayout::invalid(); } int range = y / renderer()->lineHeight(); // lineRanges is always bigger than 0, after the initial updateView call if (range >= 0 && range < cache()->viewCacheLineCount()) { return cache()->viewLine(range); } return KateTextLayout::invalid(); } int KateViewInternal::lineToY(int viewLine) const { return (viewLine - startLine()) * renderer()->lineHeight(); } void KateViewInternal::slotIncFontSizes(qreal step) { renderer()->increaseFontSizes(step); } void KateViewInternal::slotDecFontSizes(qreal step) { renderer()->decreaseFontSizes(step); } /** * Line is the real line number to scroll to. */ void KateViewInternal::scrollLines(int line) { KTextEditor::Cursor newPos(line, 0); scrollPos(newPos); } // This can scroll less than one true line void KateViewInternal::scrollViewLines(int offset) { KTextEditor::Cursor c = viewLineOffset(startPos(), offset); scrollPos(c); bool blocked = m_lineScroll->blockSignals(true); m_lineScroll->setValue(startLine()); m_lineScroll->blockSignals(blocked); } void KateViewInternal::scrollAction(int action) { switch (action) { case QAbstractSlider::SliderSingleStepAdd: scrollNextLine(); break; case QAbstractSlider::SliderSingleStepSub: scrollPrevLine(); break; case QAbstractSlider::SliderPageStepAdd: scrollNextPage(); break; case QAbstractSlider::SliderPageStepSub: scrollPrevPage(); break; case QAbstractSlider::SliderToMinimum: top_home(); break; case QAbstractSlider::SliderToMaximum: bottom_end(); break; } } void KateViewInternal::scrollNextPage() { scrollViewLines(qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevPage() { scrollViewLines(-qMax(linesDisplayed() - 1, 0)); } void KateViewInternal::scrollPrevLine() { scrollViewLines(-1); } void KateViewInternal::scrollNextLine() { scrollViewLines(1); } KTextEditor::Cursor KateViewInternal::maxStartPos(bool changed) { cache()->setAcceptDirtyLayouts(true); if (m_cachedMaxStartPos.line() == -1 || changed) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); if (m_view->config()->scrollPastEnd()) { m_cachedMaxStartPos = viewLineOffset(end, -m_minLinesVisible); } else { m_cachedMaxStartPos = viewLineOffset(end, -(linesDisplayed() - 1)); } } cache()->setAcceptDirtyLayouts(false); return m_cachedMaxStartPos; } // c is a virtual cursor void KateViewInternal::scrollPos(KTextEditor::Cursor &c, bool force, bool calledExternally, bool emitSignals) { if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } if (c.line() < 0) { c.setLine(0); } KTextEditor::Cursor limit = maxStartPos(); if (c > limit) { c = limit; // Re-check we're not just scrolling to the same place if (!force && ((!m_view->dynWordWrap() && c.line() == startLine()) || c == startPos())) { return; } } int viewLinesScrolled = 0; // only calculate if this is really used and useful, could be wrong here, please recheck // for larger scrolls this makes 2-4 seconds difference on my xeon with dyn. word wrap on // try to get it really working ;) bool viewLinesScrolledUsable = !force && (c.line() >= startLine() - linesDisplayed() - 1) && (c.line() <= endLine() + linesDisplayed() + 1); if (viewLinesScrolledUsable) { viewLinesScrolled = cache()->displayViewLine(c); } m_startPos.setPosition(c); // set false here but reversed if we return to makeVisible m_madeVisible = false; if (viewLinesScrolledUsable) { int lines = linesDisplayed(); if (m_view->textFolding().visibleLines() < lines) { KTextEditor::Cursor end(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); lines = qMin(linesDisplayed(), cache()->displayViewLine(end) + 1); } Q_ASSERT(lines >= 0); if (!calledExternally && qAbs(viewLinesScrolled) < lines && // NOTE: on some machines we must update if the floating widget is visible // otherwise strange painting bugs may occur during scrolling... !((m_view->m_floatTopMessageWidget && m_view->m_floatTopMessageWidget->isVisible()) || (m_view->m_floatBottomMessageWidget && m_view->m_floatBottomMessageWidget->isVisible())) ) { updateView(false, viewLinesScrolled); int scrollHeight = -(viewLinesScrolled * (int)renderer()->lineHeight()); // scroll excluding child widgets (floating notifications) scroll(0, scrollHeight, rect()); m_leftBorder->scroll(0, scrollHeight); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } return; } } updateView(); update(); m_leftBorder->update(); if (emitSignals) { emit m_view->verticalScrollPositionChanged(m_view, c); emit m_view->displayRangeChanged(m_view); } } void KateViewInternal::scrollColumns(int x) { if (x < 0) { x = 0; } if (x > m_columnScroll->maximum()) { x = m_columnScroll->maximum(); } if (x == m_startX) { return; } int dx = m_startX - x; m_startX = x; if (qAbs(dx) < width()) { // scroll excluding child widgets (floating notifications) scroll(dx, 0, rect()); } else { update(); } emit m_view->horizontalScrollPositionChanged(m_view); emit m_view->displayRangeChanged(m_view); bool blocked = m_columnScroll->blockSignals(true); m_columnScroll->setValue(m_startX); m_columnScroll->blockSignals(blocked); } // If changed is true, the lines that have been set dirty have been updated. void KateViewInternal::updateView(bool changed, int viewLinesScrolled) { doUpdateView(changed, viewLinesScrolled); if (changed) { updateDirty(); } } void KateViewInternal::doUpdateView(bool changed, int viewLinesScrolled) { if (!isVisible() && !viewLinesScrolled && !changed) { return; //When this view is not visible, don't do anything } bool blocked = m_lineScroll->blockSignals(true); if (width() != cache()->viewWidth()) { cache()->setViewWidth(width()); changed = true; } /* It was observed that height() could be negative here -- when the main Kate view has 0 as size (during creation), and there frame arount KateViewInternal. In which case we'd set the view cache to 0 (or less!) lines, and start allocating huge chunks of data, later. */ int newSize = (qMax(0, height()) / renderer()->lineHeight()) + 1; cache()->updateViewCache(startPos(), newSize, viewLinesScrolled); m_visibleLineCount = newSize; KTextEditor::Cursor maxStart = maxStartPos(changed); int maxLineScrollRange = maxStart.line(); if (m_view->dynWordWrap() && maxStart.column() != 0) { maxLineScrollRange++; } m_lineScroll->setRange(0, maxLineScrollRange); m_lineScroll->setValue(startPos().line()); m_lineScroll->setSingleStep(1); m_lineScroll->setPageStep(qMax(0, height()) / renderer()->lineHeight()); m_lineScroll->blockSignals(blocked); KateViewConfig::ScrollbarMode show_scrollbars = static_cast(view()->config()->showScrollbars()); bool visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (maxLineScrollRange != 0))); bool visible_dummy = visible; m_lineScroll->setVisible(visible); if (!m_view->dynWordWrap()) { int max = maxLen(startLine()) - width(); if (max < 0) { max = 0; } // if we lose the ability to scroll horizontally, move view to the far-left if (max == 0) { scrollColumns(0); } blocked = m_columnScroll->blockSignals(true); // disable scrollbar m_columnScroll->setDisabled(max == 0); visible = ((show_scrollbars == KateViewConfig::AlwaysOn) || ((show_scrollbars == KateViewConfig::ShowWhenNeeded) && (max != 0))); visible_dummy &= visible; m_columnScroll->setVisible(visible); m_columnScroll->setRange(0, max + (renderer()->spaceWidth() / 2)); // Add some space for the caret at EOL m_columnScroll->setValue(m_startX); // Approximate linescroll m_columnScroll->setSingleStep(renderer()->config()->fontMetrics().width(QLatin1Char('a'))); m_columnScroll->setPageStep(width()); m_columnScroll->blockSignals(blocked); } else { visible_dummy = false; } m_dummy->setVisible(visible_dummy); } /** * this function ensures a certain location is visible on the screen. * if endCol is -1, ignore making the columns visible. */ void KateViewInternal::makeVisible(const KTextEditor::Cursor &c, int endCol, bool force, bool center, bool calledExternally) { //qCDebug(LOG_KTE) << "MakeVisible start " << startPos() << " end " << endPos() << " -> request: " << c;// , new start [" << scroll.line << "," << scroll.col << "] lines " << (linesDisplayed() - 1) << " height " << height(); // if the line is in a folded region, unfold all the way up //if ( doc()->foldingTree()->findNodeForLine( c.line )->visible ) // qCDebug(LOG_KTE)<<"line ("< endPos())) { KTextEditor::Cursor scroll = viewLineOffset(c, -int(linesDisplayed()) / 2); scrollPos(scroll, false, calledExternally); } else if (c > viewLineOffset(startPos(), linesDisplayed() - m_minLinesVisible - 1)) { KTextEditor::Cursor scroll = viewLineOffset(c, -(linesDisplayed() - m_minLinesVisible - 1)); scrollPos(scroll, false, calledExternally); } else if (c < viewLineOffset(startPos(), m_minLinesVisible)) { KTextEditor::Cursor scroll = viewLineOffset(c, -m_minLinesVisible); scrollPos(scroll, false, calledExternally); } else { // Check to see that we're not showing blank lines KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, max.column(), calledExternally); } } if (!m_view->dynWordWrap() && (endCol != -1 || m_view->wrapCursor())) { KTextEditor::Cursor rc = toRealCursor(c); int sX = renderer()->cursorToX(cache()->textLayout(rc), rc, !m_view->wrapCursor()); int sXborder = sX - 8; if (sXborder < 0) { sXborder = 0; } if (sX < m_startX) { scrollColumns(sXborder); } else if (sX > m_startX + width()) { scrollColumns(sX - width() + 8); } } m_madeVisible = !force; #ifndef QT_NO_ACCESSIBILITY // FIXME -- is this needed? // QAccessible::updateAccessibility(this, KateCursorAccessible::ChildId, QAccessible::Focus); #endif } void KateViewInternal::slotRegionVisibilityChanged() { qCDebug(LOG_KTE); cache()->clear(); m_cachedMaxStartPos.setLine(-1); KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max, false, false, false /* don't emit signals! */); } // if text was folded: make sure the cursor is on a visible line qint64 foldedRangeId = -1; if (!m_view->textFolding().isLineVisible(primaryCursor().line(), &foldedRangeId)) { KTextEditor::Range foldingRange = m_view->textFolding().foldingRange(foldedRangeId); Q_ASSERT(foldingRange.start().isValid()); // set cursor to start of folding region cursors()->setPrimaryCursor(foldingRange.start(), true); } else { // force an update of the cursor, since otherwise the m_displayCursor // line may be below the total amount of visible lines. cursors()->setPrimaryCursor(primaryCursor(), true); } updateView(); update(); m_leftBorder->update(); // emit signals here, scrollPos has this disabled, to ensure we do this after all stuff is updated! emit m_view->verticalScrollPositionChanged(m_view, max); emit m_view->displayRangeChanged(m_view); } void KateViewInternal::slotRegionBeginEndAddedRemoved(unsigned int) { qCDebug(LOG_KTE); // FIXME: performance problem m_leftBorder->update(); } void KateViewInternal::showEvent(QShowEvent *e) { updateView(); QWidget::showEvent(e); } int KateViewInternal::linesDisplayed() const { int h = height(); // catch zero heights, even if should not happen int fh = qMax(1, renderer()->lineHeight()); // default to 1, there is always one line around.... // too many places calc with linesDisplayed() - 1 return qMax(1, (h - (h % fh)) / fh); } QPoint KateViewInternal::cursorToCoordinate(const KTextEditor::Cursor &cursor, bool realCursor, bool includeBorder) const { if (cursor.line() >= doc()->lines()) { return QPoint(-1, -1); } int viewLine = cache()->displayViewLine(realCursor ? toVirtualCursor(cursor) : cursor, true); if (viewLine < 0 || viewLine >= cache()->viewCacheLineCount()) { return QPoint(-1, -1); } const int y = (int)viewLine * renderer()->lineHeight(); KateTextLayout layout = cache()->viewLine(viewLine); if (cursor.column() > doc()->lineLength(cursor.line())) { return QPoint(-1, -1); } int x = 0; // only set x value if we have a valid layout (bug #171027) if (layout.isValid()) { x = (int)layout.lineLayout().cursorToX(cursor.column()); } // else // qCDebug(LOG_KTE) << "Invalid Layout"; if (includeBorder) { x += m_leftBorder->width(); } x -= startX(); return QPoint(x, y); } QPoint KateViewInternal::cursorCoordinates(bool includeBorder) const { return cursorToCoordinate(m_displayCursor, false, includeBorder); } KTextEditor::Cursor KateViewInternal::findMatchingBracket() { KTextEditor::Cursor c; if (!m_bm->toRange().isValid()) { return KTextEditor::Cursor::invalid(); } Q_ASSERT(m_bmEnd->toRange().isValid()); Q_ASSERT(m_bmStart->toRange().isValid()); auto cursor = primaryCursor(); if (m_bmStart->toRange().contains(cursor) || m_bmStart->end() == cursor) { c = m_bmEnd->end(); } else if (m_bmEnd->toRange().contains(cursor) || m_bmEnd->end() == cursor) { c = m_bmStart->start(); } else { // should never happen: a range exists, but the cursor position is // neither at the start nor at the end... return KTextEditor::Cursor::invalid(); } return c; } void KateViewInternal::doReturn() { doc()->newLine(m_view); m_leftBorder->updateForCursorLineChange(); updateView(); } void KateViewInternal::doSmartNewline() { int ln = primaryCursor().line(); Kate::TextLine line = doc()->kateTextLine(ln); int col = qMin(primaryCursor().column(), line->firstChar()); if (col != -1) { while (line->length() > col && !(line->at(col).isLetterOrNumber() || line->at(col) == QLatin1Char('_')) && col < primaryCursor().column()) { ++col; } } else { col = line->length(); // stay indented } doc()->editStart(); doc()->editWrapLine(ln, primaryCursor().column()); doc()->insertText(KTextEditor::Cursor(ln + 1, 0), line->string(0, col)); doc()->editEnd(); updateView(); } void KateViewInternal::doDelete() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); bool hadSelection = view()->selection(); Q_FOREACH ( const auto& cursor, cursors ) { doc()->del(m_view, cursor); if (hadSelection) { // if we had a selection, only call del() once. break; } } } void KateViewInternal::doBackspace() { auto cursors = view()->allCursors(); KTextEditor::Document::EditingTransaction t(doc()); bool hadSelection = view()->selection(); Q_FOREACH ( const auto& cursor, cursors ) { doc()->backspace(m_view, cursor); if (hadSelection) { // if we had a selection, only call backspace() once. break; } } } void KateViewInternal::doTabulator() { doc()->insertTab(m_view, primaryCursor()); } void KateViewInternal::doTranspose() { doc()->transpose(primaryCursor()); } void KateViewInternal::doDeletePrevWord() { doc()->editStart(); wordPrev(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::doDeleteNextWord() { doc()->editStart(); wordNext(true); KTextEditor::Range selection = m_view->selectionRange(); m_view->removeSelectedText(); doc()->editEnd(); tagRange(selection, true); updateDirty(); } void KateViewInternal::clearSelectionUnless(bool sel) { if ( ! sel ) { selections()->clearSelectionIfNotPersistent(); } } void KateViewInternal::cursorPrevChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsLeft(sel); } void KateViewInternal::cursorNextChar(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsRight(sel); } void KateViewInternal::wordPrev(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordPrevious(sel); } void KateViewInternal::wordNext(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsWordNext(sel); } void KateViewInternal::home(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsStartOfLine(sel); } void KateViewInternal::end(bool sel) { clearSelectionUnless(sel); cursors()->moveCursorsEndOfLine(sel); } KateTextLayout KateViewInternal::currentLayout(const KTextEditor::Cursor& cursor) const { return cache()->textLayout(cursor); } KateTextLayout KateViewInternal::previousLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor); if (currentViewLine) { return cache()->textLayout(cursor.line(), currentViewLine - 1); } else { return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() - 1), -1); } } KateTextLayout KateViewInternal::nextLayout(const KTextEditor::Cursor& cursor) const { int currentViewLine = cache()->viewLine(cursor) + 1; if (currentViewLine >= cache()->line(cursor.line())->viewLineCount()) { currentViewLine = 0; return cache()->textLayout(m_view->textFolding().visibleLineToLine(toVirtualCursor(cursor).line() + 1), currentViewLine); } else { return cache()->textLayout(cursor.line(), currentViewLine); } } /* * This returns the cursor which is offset by (offset) view lines. * This is the main function which is called by code not specifically dealing with word-wrap. * The opposite conversion (cursor to offset) can be done with cache()->displayViewLine(). * * The cursors involved are virtual cursors (ie. equivalent to m_displayCursor) */ KTextEditor::Cursor KateViewInternal::viewLineOffset(const KTextEditor::Cursor &virtualCursor, int offset) { if (!m_view->dynWordWrap()) { KTextEditor::Cursor ret(qMin((int)m_view->textFolding().visibleLines() - 1, virtualCursor.line() + offset), 0); if (ret.line() < 0) { ret.setLine(0); } return ret; } KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_view->textFolding().visibleLineToLine(m_view->textFolding().lineToVisibleLine(virtualCursor.line()))); int cursorViewLine = cache()->viewLine(realCursor); int currentOffset = 0; int virtualLine = 0; bool forwards = (offset > 0) ? true : false; if (forwards) { currentOffset = cache()->lastViewLine(realCursor.line()) - cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine + offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() + 1; } else { offset = -offset; currentOffset = cursorViewLine; if (offset <= currentOffset) { // the answer is on the same line KateTextLayout thisLine = cache()->textLayout(realCursor.line(), cursorViewLine - offset); Q_ASSERT(thisLine.virtualLine() == (int) m_view->textFolding().lineToVisibleLine(virtualCursor.line())); return KTextEditor::Cursor(virtualCursor.line(), thisLine.startCol()); } virtualLine = virtualCursor.line() - 1; } currentOffset++; while (virtualLine >= 0 && virtualLine < (int)m_view->textFolding().visibleLines()) { int realLine = m_view->textFolding().visibleLineToLine(virtualLine); KateLineLayoutPtr thisLine = cache()->line(realLine, virtualLine); if (!thisLine) { break; } for (int i = 0; i < thisLine->viewLineCount(); ++i) { if (offset == currentOffset) { KateTextLayout thisViewLine = thisLine->viewLine(i); if (!forwards) { // We actually want it the other way around int requiredViewLine = cache()->lastViewLine(realLine) - thisViewLine.viewLine(); if (requiredViewLine != thisViewLine.viewLine()) { thisViewLine = thisLine->viewLine(requiredViewLine); } } KTextEditor::Cursor ret(virtualLine, thisViewLine.startCol()); return ret; } currentOffset++; } if (forwards) { virtualLine++; } else { virtualLine--; } } // Looks like we were asked for something a bit exotic. // Return the max/min valid position. if (forwards) { return KTextEditor::Cursor(m_view->textFolding().visibleLines() - 1, doc()->lineLength(m_view->textFolding().visibleLineToLine(m_view->textFolding().visibleLines() - 1))); } else { return KTextEditor::Cursor(0, 0); } } int KateViewInternal::lineMaxCursorX(const KateTextLayout &range) { if (!m_view->wrapCursor() && !range.wrap()) { return INT_MAX; } int maxX = range.endX(); if (maxX && range.wrap()) { QChar lastCharInLine = doc()->kateTextLine(range.line())->at(range.endCol() - 1); maxX -= renderer()->config()->fontMetrics().width(lastCharInLine); } return maxX; } int KateViewInternal::lineMaxCol(const KateTextLayout &range) { int maxCol = range.endCol(); if (maxCol && range.wrap()) { maxCol--; } return maxCol; } void KateViewInternal::cursorUp(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorUp(); return; } cursors()->moveCursorsUp(sel); } void KateViewInternal::cursorDown(bool sel) { if (!sel && m_view->completionWidget()->isCompletionActive()) { m_view->completionWidget()->cursorDown(); return; } cursors()->moveCursorsDown(sel); } void KateViewInternal::cursorToMatchingBracket(bool sel) { KTextEditor::Cursor c = findMatchingBracket(); if (c.isValid()) { updateSelection(c, sel); cursors()->setPrimaryCursor(c); } } void KateViewInternal::topOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(startPos(), m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } void KateViewInternal::bottomOfView(bool sel) { KTextEditor::Cursor c = viewLineOffset(endPos(), -m_minLinesVisible); updateSelection(toRealCursor(c), sel); cursors()->setPrimaryCursor(toRealCursor(c)); } // lines is the offset to scroll by void KateViewInternal::scrollLines(int lines, bool sel) { KTextEditor::Cursor c = viewLineOffset(m_displayCursor, lines); // Fix the virtual cursor -> real cursor c.setLine(m_view->textFolding().visibleLineToLine(c.line())); // how far do we move? auto moveLines = c.line() - primaryCursor().line(); cursors()->moveCursorsDown(sel, moveLines); // handles negative values } // This is a bit misleading... it's asking for the view to be scrolled, not the cursor void KateViewInternal::scrollUp() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), -1); scrollPos(newPos); } void KateViewInternal::scrollDown() { KTextEditor::Cursor newPos = viewLineOffset(startPos(), 1); scrollPos(newPos); } void KateViewInternal::setAutoCenterLines(int viewLines, bool updateView) { m_autoCenterLines = viewLines; m_minLinesVisible = qMin(int((linesDisplayed() - 1) / 2), m_autoCenterLines); if (updateView) { KateViewInternal::updateView(); } } void KateViewInternal::pageUp(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageUp(); return; } bool atTop = startPos().atStartOfDocument(); // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = -qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = -qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } qDebug() << "scroll by:" << linesToScroll; if (!doc()->pageUpDownMovesCursor() && !atTop) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll - 1); scrollPos(newStartPos); cursors()->moveCursorsDown(sel, linesToScroll - 1); } else { scrollLines(linesToScroll, sel); } } void KateViewInternal::pageDown(bool sel, bool half) { if (m_view->isCompletionActive()) { m_view->completionWidget()->pageDown(); return; } bool atEnd = startPos() >= m_cachedMaxStartPos; // Adjust for an auto-centering cursor int lineadj = m_minLinesVisible; int linesToScroll; if (! half) { linesToScroll = qMax((linesDisplayed() - 1) - lineadj, 0); } else { linesToScroll = qMax((linesDisplayed() / 2 - 1) - lineadj, 0); } qDebug() << "scroll by:" << linesToScroll; if (!doc()->pageUpDownMovesCursor() && !atEnd) { KTextEditor::Cursor newStartPos = viewLineOffset(startPos(), linesToScroll + 1); scrollPos(newStartPos); cursors()->moveCursorsDown(sel, linesToScroll + 1); } else { scrollLines(linesToScroll, sel); } } int KateViewInternal::maxLen(int startLine) { Q_ASSERT(!m_view->dynWordWrap()); int displayLines = (m_view->height() / renderer()->lineHeight()) + 1; int maxLen = 0; for (int z = 0; z < displayLines; z++) { int virtualLine = startLine + z; if (virtualLine < 0 || virtualLine >= (int)m_view->textFolding().visibleLines()) { break; } maxLen = qMax(maxLen, cache()->line(m_view->textFolding().visibleLineToLine(virtualLine))->width()); } return maxLen; } bool KateViewInternal::columnScrollingPossible() { return !m_view->dynWordWrap() && m_columnScroll->isEnabled() && (m_columnScroll->maximum() > 0); } bool KateViewInternal::lineScrollingPossible() { return m_lineScroll->minimum() != m_lineScroll->maximum(); } void KateViewInternal::top_home(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->top(); return; } cursors()->moveCursorsTopHome(sel); } void KateViewInternal::bottom_end(bool sel) { if (m_view->isCompletionActive()) { m_view->completionWidget()->bottom(); return; } cursors()->moveCursorsBottomEnd(sel); } void KateViewInternal::updateSelection(const KTextEditor::Cursor &_newCursor, bool keepSel) { /** KTextEditor::Cursor newCursor = _newCursor; if (keepSel) { if (!m_view->selection() || (m_selectAnchor.line() == -1) //don't kill the selection if we have a persistent selection and //the cursor is inside or at the boundaries of the selected area || (m_view->config()->persistentSelection() && !(m_view->selectionRange().contains(primaryCursor()) || m_view->selectionRange().boundaryAtCursor(primaryCursor())))) { m_selectAnchor = primaryCursor(); setSelection(KTextEditor::Range(primaryCursor(), newCursor)); } else { bool doSelect = true; switch (m_selectionMode) { case Word: { // Restore selStartCached if needed. It gets nuked by // viewSelectionChanged if we drag the selection into non-existence, // which can legitimately happen if a shift+DC selection is unable to // set a "proper" (i.e. non-empty) cached selection, e.g. because the // start was on something that isn't a word. Word select mode relies // on the cached selection being set properly, even if it is empty // (i.e. selStartCached == selEndCached). if (!m_selectionCached.isValid()) { m_selectionCached.setStart(m_selectionCached.end()); } int c; if (newCursor > m_selectionCached.start()) { m_selectAnchor = m_selectionCached.start(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && doc()->highlight()->isInWord(l->at(c - 1))) { for (; c < l->length(); c++) if (!doc()->highlight()->isInWord(l->at(c))) { break; } } newCursor.setColumn(c); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); Kate::TextLine l = doc()->kateTextLine(newCursor.line()); c = newCursor.column(); if (c > 0 && c < doc()->lineLength(newCursor.line()) && doc()->highlight()->isInWord(l->at(c)) && doc()->highlight()->isInWord(l->at(c - 1))) { for (c -= 2; c >= 0; c--) if (!doc()->highlight()->isInWord(l->at(c))) { break; } newCursor.setColumn(c + 1); } } else { doSelect = false; } } break; case Line: if (!m_selectionCached.isValid()) { m_selectionCached = KTextEditor::Range(endLine(), 0, endLine(), 0); } if (newCursor.line() > m_selectionCached.start().line()) { if (newCursor.line() + 1 >= doc()->lines()) { newCursor.setColumn(doc()->line(newCursor.line()).length()); } else { newCursor.setPosition(newCursor.line() + 1, 0); } // Grow to include the entire line m_selectAnchor = m_selectionCached.start(); m_selectAnchor.setColumn(0); } else if (newCursor.line() < m_selectionCached.start().line()) { newCursor.setColumn(0); // Grow to include entire line m_selectAnchor = m_selectionCached.end(); if (m_selectAnchor.column() > 0) { if (m_selectAnchor.line() + 1 >= doc()->lines()) { m_selectAnchor.setColumn(doc()->line(newCursor.line()).length()); } else { m_selectAnchor.setPosition(m_selectAnchor.line() + 1, 0); } } } else { // same line, ignore doSelect = false; } break; case Mouse: { if (!m_selectionCached.isValid()) { break; } if (newCursor > m_selectionCached.end()) { m_selectAnchor = m_selectionCached.start(); } else if (newCursor < m_selectionCached.start()) { m_selectAnchor = m_selectionCached.end(); } else { doSelect = false; } } break; default:; } if (doSelect) { setSelection(KTextEditor::Range(m_selectAnchor, newCursor)); } else if (m_selectionCached.isValid()) { // we have a cached selection, so we restore that setSelection(m_selectionCached); } } m_selChangedByUser = true; } else if (!m_view->config()->persistentSelection()) { m_view->clearSelection(); m_selectionCached = KTextEditor::Range::invalid(); m_selectAnchor = KTextEditor::Cursor::invalid(); } **/ #ifndef QT_NO_ACCESSIBILITY // FIXME KF5 // QAccessibleTextSelectionEvent ev(this, /* selection start, selection end*/); // QAccessible::updateAccessibility(&ev); #endif } void KateViewInternal::setSelection(const KTextEditor::Range &range) { m_view->setSelection(range); } void KateViewInternal::moveCursorToSelectionEdge() { if (!m_view->selection()) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if (m_view->selectionRange().start() < m_selectAnchor) { cursors()->setPrimaryCursorWithoutSelection(m_view->selectionRange().start()); } else { cursors()->setPrimaryCursorWithoutSelection(m_view->selectionRange().end()); } m_minLinesVisible = tmp; } void KateViewInternal::updateCursorFlashTimer() { if (m_cursorTimer.isActive()) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } renderer()->setDrawCaret(true); } } void KateViewInternal::notifyPrimaryCursorChanged(const KTextEditor::Cursor &newCursor, bool force, bool center, bool calledExternally) { if (!force && (m_lastUpdatedPrimary == newCursor)) { m_displayCursor = toVirtualCursor(newCursor); if (!m_madeVisible && m_view == doc()->activeView()) { // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } return; } if (m_lastUpdatedPrimary.line() != newCursor.line()) { m_leftBorder->updateForCursorLineChange(); } // unfold if required m_view->textFolding().ensureLineIsVisible(newCursor.line()); m_displayCursor = toVirtualCursor(newCursor); Q_ASSERT(m_displayCursor.isValid()); m_lastUpdatedPrimary = newCursor; if (m_view == doc()->activeView()) { makeVisible(m_displayCursor, m_displayCursor.column(), false, center, calledExternally); } updateBracketMarks(); updateMicroFocus(); updateCursorFlashTimer(); cursorMoved(); emit m_view->cursorPositionChanged(m_view, primaryCursor()); } void KateViewInternal::updateBracketMarkAttributes() { KTextEditor::Attribute::Ptr bracketFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); bracketFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); bracketFill->setBackgroundFillWhitespace(false); if (QFontInfo(renderer()->currentFont()).fixedPitch()) { // make font bold only for fixed fonts, otherwise text jumps around bracketFill->setFontBold(); } m_bmStart->setAttribute(bracketFill); m_bmEnd->setAttribute(bracketFill); if (m_view->m_renderer->config()->showWholeBracketExpression()) { KTextEditor::Attribute::Ptr expressionFill = KTextEditor::Attribute::Ptr(new KTextEditor::Attribute()); expressionFill->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); expressionFill->setBackgroundFillWhitespace(false); m_bm->setAttribute(expressionFill); } else { m_bm->setAttribute(KTextEditor::Attribute::Ptr(new KTextEditor::Attribute())); } } void KateViewInternal::updateBracketMarks() { // add some limit to this, this is really endless on big files without limit const int maxLines = 5000; const KTextEditor::Range newRange = doc()->findMatchingBracket(primaryCursor(), maxLines); // new range valid, then set ranges to it if (newRange.isValid()) { if (m_bm->toRange() == newRange) { return; } // modify full range m_bm->setRange(newRange); // modify start and end ranges m_bmStart->setRange(KTextEditor::Range(m_bm->start(), KTextEditor::Cursor(m_bm->start().line(), m_bm->start().column() + 1))); m_bmEnd->setRange(KTextEditor::Range(m_bm->end(), KTextEditor::Cursor(m_bm->end().line(), m_bm->end().column() + 1))); // flash matching bracket if (!renderer()->config()->animateBracketMatching()) { return; } const KTextEditor::Cursor flashPos = (primaryCursor() == m_bmStart->start() || primaryCursor() == m_bmStart->end()) ? m_bmEnd->start() : m_bm->start(); if (flashPos != m_bmLastFlashPos->toCursor()) { m_bmLastFlashPos->setPosition(flashPos); KTextEditor::Attribute::Ptr attribute = doc()->attributeAt(flashPos); attribute->setBackground(m_view->m_renderer->config()->highlightedBracketColor()); attribute->setFontBold(m_bmStart->attribute()->fontBold()); flashChar(flashPos, attribute); } return; } // new range was invalid m_bm->setRange(KTextEditor::Range::invalid()); m_bmStart->setRange(KTextEditor::Range::invalid()); m_bmEnd->setRange(KTextEditor::Range::invalid()); m_bmLastFlashPos->setPosition(KTextEditor::Cursor::invalid()); } bool KateViewInternal::tagLine(const KTextEditor::Cursor &virtualCursor) { // FIXME may be a more efficient way for this if ((int)m_view->textFolding().visibleLineToLine(virtualCursor.line()) > doc()->lastLine()) { return false; } // End FIXME int viewLine = cache()->displayViewLine(virtualCursor, true); if (viewLine >= 0 && viewLine < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine).setDirty(); // tag one line more because of overlapping things like _, bug 335079 if (viewLine+1 < cache()->viewCacheLineCount()) { cache()->viewLine(viewLine+1).setDirty(); } m_leftBorder->update(0, lineToY(viewLine), m_leftBorder->width(), renderer()->lineHeight()); return true; } return false; } bool KateViewInternal::tagLines(int start, int end, bool realLines) { return tagLines(KTextEditor::Cursor(start, 0), KTextEditor::Cursor(end, -1), realLines); } bool KateViewInternal::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end, bool realCursors) { if (realCursors) { cache()->relayoutLines(start.line(), end.line()); //qCDebug(LOG_KTE)<<"realLines is true"; start = toVirtualCursor(start); end = toVirtualCursor(end); } else { cache()->relayoutLines(toRealCursor(start).line(), toRealCursor(end).line()); } if (end.line() < startLine()) { //qCDebug(LOG_KTE)<<"end endLine(), but cache may not be valid when checking, so use a // less optimal but still adequate approximation (potential overestimation but minimal performance difference) if (start.line() > startLine() + cache()->viewCacheLineCount()) { //qCDebug(LOG_KTE)<<"start> endLine"<updateViewCache(startPos()); //qCDebug(LOG_KTE) << "tagLines( [" << start << "], [" << end << "] )"; bool ret = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1)))) { ret = true; break; //qCDebug(LOG_KTE) << "Tagged line " << line.line(); } } if (!m_view->dynWordWrap()) { int y = lineToY(start.line()); // FIXME is this enough for when multiple lines are deleted int h = (end.line() - start.line() + 2) * renderer()->lineHeight(); if (end.line() >= m_view->textFolding().visibleLines() - 1) { h = height(); } m_leftBorder->update(0, y, m_leftBorder->width(), h); } else { // FIXME Do we get enough good info in editRemoveText to optimize this more? //bool justTagged = false; for (int z = 0; z < cache()->viewCacheLineCount(); z++) { KateTextLayout &line = cache()->viewLine(z); if (!line.isValid() || ((line.virtualLine() > start.line() || (line.virtualLine() == start.line() && line.endCol() >= start.column() && start.column() != -1)) && (line.virtualLine() < end.line() || (line.virtualLine() == end.line() && (line.startCol() <= end.column() || end.column() == -1))))) { //justTagged = true; m_leftBorder->update(0, z * renderer()->lineHeight(), m_leftBorder->width(), m_leftBorder->height()); break; } /*else if (justTagged) { justTagged = false; leftBorder->update (0, z * doc()->viewFont.fontHeight, leftBorder->width(), doc()->viewFont.fontHeight); break; }*/ } } return ret; } bool KateViewInternal::tagRange(const KTextEditor::Range &range, bool realCursors) { return tagLines(range.start(), range.end(), realCursors); } void KateViewInternal::tagAll() { // clear the cache... cache()->clear(); m_leftBorder->updateFont(); m_leftBorder->update(); } void KateViewInternal::paintCursor() { Q_FOREACH ( const auto& secondary, view()->cursors()->cursors() ) { if (tagLine(secondary)) { updateDirty(); } } } KTextEditor::Cursor KateViewInternal::pointToCursor(const QPoint& p) const { KateTextLayout thisLine = yToKateTextLayout(p.y()); KTextEditor::Cursor c; if (!thisLine.isValid()) { // probably user clicked below the last line -> use the last line thisLine = cache()->textLayout(doc()->lines() - 1, -1); } c = renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor()); if (c.line() < 0 || c.line() >= doc()->lines()) { return {}; } return c; } // Point in content coordinates void KateViewInternal::placeCursor(const QPoint &p, bool keepSelection, bool updateSelection) { auto c = pointToCursor(p); if ( ! c.isValid() ) { return; } int tmp = m_minLinesVisible; m_minLinesVisible = 0; if ( keepSelection ) { cursors()->setPrimaryCursorWithoutSelection(c); } else { cursors()->setPrimaryCursor(c); } m_minLinesVisible = tmp; if (updateSelection && keepSelection) { moveCursorToSelectionEdge(); } } // Point in content coordinates bool KateViewInternal::isTargetSelected(const QPoint &p) { const KateTextLayout &thisLine = yToKateTextLayout(p.y()); if (!thisLine.isValid()) { return false; } return m_view->cursorSelected(renderer()->xToCursor(thisLine, startX() + p.x(), !m_view->wrapCursor())); } //BEGIN EVENT HANDLING STUFF bool KateViewInternal::eventFilter(QObject *obj, QEvent *e) { switch (e->type()) { case QEvent::ChildAdded: case QEvent::ChildRemoved: { QChildEvent *c = static_cast(e); if (c->added()) { c->child()->installEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->installEventFilter(this);*/ } else if (c->removed()) { c->child()->removeEventFilter(this); /*foreach (QWidget* child, c->child()->findChildren()) child->removeEventFilter(this);*/ } } break; case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); if (k->key() == Qt::Key_Escape && k->modifiers() == Qt::NoModifier) { if (m_view->isCompletionActive()) { m_view->abortCompletion(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "aborting completion"; return true; } else if (!m_view->bottomViewBar()->hiddenOrPermanent()) { m_view->bottomViewBar()->hideCurrentBarWidget(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "closing view bar"; return true; } else if (!m_view->config()->persistentSelection() && m_view->selection()) { m_currentInputMode->clearSelection(); k->accept(); //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "clearing selection"; return true; } else if (m_view->cursors()->hasSecondaryCursors()) { m_view->cursors()->clearSecondaryCursors(); k->accept(); return true; } } if (m_currentInputMode->stealKey(k)) { k->accept(); return true; } } break; case QEvent::KeyPress: { QKeyEvent *k = static_cast(e); // Override all other single key shortcuts which do not use a modifier other than Shift if (obj == this && (!k->modifiers() || k->modifiers() == Qt::ShiftModifier)) { keyPressEvent(k); if (k->isAccepted()) { //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "using keystroke"; return true; } } //qCDebug(LOG_KTE) << obj << "shortcut override" << k->key() << "ignoring"; } break; case QEvent::DragMove: { QPoint currentPoint = ((QDragMoveEvent *) e)->pos(); QRect doNotScrollRegion(s_scrollMargin, s_scrollMargin, width() - s_scrollMargin * 2, height() - s_scrollMargin * 2); if (!doNotScrollRegion.contains(currentPoint)) { startDragScroll(); // Keep sending move events ((QDragMoveEvent *)e)->accept(QRect(0, 0, 0, 0)); } dragMoveEvent((QDragMoveEvent *)e); } break; case QEvent::DragLeave: // happens only when pressing ESC while dragging stopDragScroll(); break; default: break; } return QWidget::eventFilter(obj, e); } void KateViewInternal::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Left && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateLeft(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Right && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateRight(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Up && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateUp(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Down && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateDown(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Return && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateAccept(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Backspace && e->modifiers() == Qt::AltModifier) { m_view->emitNavigateBack(); e->setAccepted(true); return; } if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive()) { m_completionItemExpanded = m_view->completionWidget()->toggleExpanded(true); m_view->completionWidget()->resetHadNavigation(); m_altDownTime.start(); } // Note: AND'ing with is a quick hack to fix Key_Enter const int key = e->key() | (e->modifiers() & Qt::ShiftModifier); if (m_currentInputMode->keyPress(e)) { return; } if (!doc()->isReadWrite()) { e->ignore(); return; } if ((key == Qt::Key_Return) || (key == Qt::Key_Enter) || (key == Qt::SHIFT + Qt::Key_Return) || (key == Qt::SHIFT + Qt::Key_Enter)) { doReturn(); e->accept(); return; } if (key == Qt::Key_Backspace || key == Qt::SHIFT + Qt::Key_Backspace) { //m_view->backspace(); e->accept(); return; } if (key == Qt::Key_Tab || key == Qt::SHIFT + Qt::Key_Backtab || key == Qt::Key_Backtab) { if (m_view->completionWidget()->isCompletionActive()) { e->accept(); m_view->completionWidget()->tab(key != Qt::Key_Tab); return; } if (key == Qt::Key_Tab) { uint tabHandling = doc()->config()->tabHandling(); // convert tabSmart into tabInsertsTab or tabIndents: if (tabHandling == KateDocumentConfig::tabSmart) { // multiple lines selected if (m_view->selection() && !m_view->selectionRange().onSingleLine()) { tabHandling = KateDocumentConfig::tabIndents; } // otherwise: take look at cursor position else { // if the cursor is at or before the first non-space character // or on an empty line, // Tab indents, otherwise it inserts a tab character. Kate::TextLine line = doc()->kateTextLine(primaryCursor().line()); int first = line->firstChar(); if (first < 0 || primaryCursor().column() <= first) { tabHandling = KateDocumentConfig::tabIndents; } else { tabHandling = KateDocumentConfig::tabInsertsTab; } } } if (tabHandling == KateDocumentConfig::tabInsertsTab) { doc()->typeChars(m_view, QStringLiteral("\t")); } else { Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), 1); } } e->accept(); return; } else if (doc()->config()->tabHandling() != KateDocumentConfig::tabInsertsTab) { // key == Qt::SHIFT+Qt::Key_Backtab || key == Qt::Key_Backtab Q_FOREACH ( const auto& cursor, m_view->allCursors() ) { doc()->indent(m_view->selection() ? m_view->selectionRange() : KTextEditor::Range(cursor.line(), 0, cursor.line(), 0), -1); } e->accept(); return; } } if (!(e->modifiers() & Qt::ControlModifier) && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } // allow composition of AltGr + (q|2|3) on windows static const int altGR = Qt::ControlModifier | Qt::AltModifier; if ((e->modifiers() & altGR) == altGR && !e->text().isEmpty() && doc()->typeChars(m_view, e->text())) { e->accept(); return; } e->ignore(); } void KateViewInternal::keyReleaseEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Alt && m_view->completionWidget()->isCompletionActive() && ((m_completionItemExpanded && (m_view->completionWidget()->hadNavigation() || m_altDownTime.elapsed() > 300)) || (!m_completionItemExpanded && !m_view->completionWidget()->hadNavigation()))) { m_view->completionWidget()->toggleExpanded(false, true); } if ((e->modifiers() & Qt::SHIFT) == Qt::SHIFT) { m_shiftKeyPressed = true; } else { if (m_shiftKeyPressed) { m_shiftKeyPressed = false; if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } } } e->ignore(); return; } void KateViewInternal::contextMenuEvent(QContextMenuEvent *e) { // try to show popup menu QPoint p = e->pos(); if (e->reason() == QContextMenuEvent::Keyboard) { makeVisible(m_displayCursor, 0); p = cursorCoordinates(false); p.rx() -= startX(); } else if (! m_view->selection() || m_view->config()->persistentSelection()) { placeCursor(e->pos()); } // popup is a qguardedptr now if (m_view->contextMenu()) { m_view->spellingMenu()->setUseMouseForMisspelledRange((e->reason() == QContextMenuEvent::Mouse)); m_view->contextMenu()->popup(mapToGlobal(p)); e->accept(); } } void KateViewInternal::mousePressEvent(QMouseEvent *e) { qDebug() << "called"; if ( e->button() == Qt::LeftButton ) { // request the software keyboard, if any if (qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) { QEvent event(QEvent::RequestSoftwareInputPanel); QApplication::sendEvent(this, &event); } } // handle cursor placement and selection auto newCursor = pointToCursor(e->pos()); if (e->modifiers() & Qt::ShiftModifier) { auto flags = (KateMultiSelection::SelectionFlags) (KateMultiSelection::UsePrimaryCursor | KateMultiSelection::KeepSelectionRange); selections()->beginNewSelection(newCursor, KateMultiSelection::Character, flags); cursors()->setPrimaryCursorWithoutSelection(newCursor); Q_EMIT m_view->selectionChanged(m_view); } else { KateMultiSelection::SelectionMode selectionMode = KateMultiSelection::Character; KateMultiSelection::SelectionFlags flags = KateMultiSelection::UsePrimaryCursor; if ( m_possibleTripleClick ) { selectionMode = KateMultiSelection::Line; } if ( !m_possibleTripleClick && isTargetSelected(e->pos())) { m_dragInfo.state = diPending; m_dragInfo.start = e->pos(); } else { if ( e->modifiers() == (Qt::ControlModifier | Qt::MetaModifier) ) { flags = KateMultiSelection::AddNewCursor; } else { view()->cursors()->clearSecondaryCursors(); } selections()->beginNewSelection(newCursor, selectionMode, flags); Q_EMIT m_view->selectionChanged(m_view); } m_possibleTripleClick = false; } updateCursorFlashTimer(); e->accept(); } else { e->ignore(); } } void KateViewInternal::mouseDoubleClickEvent(QMouseEvent *e) { auto secondary = (e->modifiers() == (Qt::MetaModifier | Qt::ControlModifier)); auto newCursor = pointToCursor(e->pos()); switch (e->button()) { case Qt::LeftButton: selections()->beginNewSelection(newCursor, KateMultiSelection::Word, secondary ? KateMultiSelection::AddNewCursor : KateMultiSelection::UsePrimaryCursor); Q_EMIT m_view->selectionChanged(m_view); #warning fixme: this weird "shift double click" feature #warning fixme: select to matching bracket on dclick #if 0 if (e->modifiers() & Qt::ShiftModifier) { // Now select the word under the select anchor int cs, ce; Kate::TextLine l = doc()->kateTextLine(m_selectAnchor.line()); ce = m_selectAnchor.column(); if (ce > 0 && doc()->highlight()->isInWord(l->at(ce))) { for (; ce < l->length(); ce++) if (!doc()->highlight()->isInWord(l->at(ce))) { break; } } cs = m_selectAnchor.column() - 1; if (cs < doc()->lineLength(m_selectAnchor.line()) && doc()->highlight()->isInWord(l->at(cs))) { for (cs--; cs >= 0; cs--) if (!doc()->highlight()->isInWord(l->at(cs))) { break; } } // ...and keep it selected if (cs + 1 < ce) { m_selectionCached.setStart(KTextEditor::Cursor(m_selectAnchor.line(), cs + 1)); m_selectionCached.setEnd(KTextEditor::Cursor(m_selectAnchor.line(), ce)); } else { m_selectionCached.setStart(m_selectAnchor); m_selectionCached.setEnd(m_selectAnchor); } // Now word select to the mouse cursor placeCursor(e->pos(), true); } else { // first clear the selection, otherwise we run into bug #106402 // ...and set the cursor position, for the same reason (otherwise there // are *other* idiosyncrasies we can't fix without reintroducing said // bug) // Parameters: don't redraw, and don't emit selectionChanged signal yet m_view->clearSelection(false, false); placeCursor(e->pos()); m_view->selectWord(primaryCursor()); cursorToMatchingBracket(true); if (m_view->selection()) { m_selectAnchor = m_view->selectionRange().start(); m_selectionCached = m_view->selectionRange(); } else { m_selectAnchor = primaryCursor(); m_selectionCached = KTextEditor::Range(primaryCursor(), primaryCursor()); } } #endif if (m_view->selection()) { #if !defined(Q_OS_OSX) QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); #endif } m_possibleTripleClick = true; QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); m_scrollX = 0; m_scrollY = 0; m_scrollTimer.start(50); e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::tripleClickTimeout() { m_possibleTripleClick = false; } void KateViewInternal::mouseReleaseEvent(QMouseEvent *e) { switch (e->button()) { case Qt::LeftButton: if ( selections()->currentlySelecting() ) { selections()->finishNewSelection(); Q_EMIT m_view->selectionChanged(m_view); updateCursorFlashTimer(); } if (m_selChangedByUser) { if (m_view->selection()) { QApplication::clipboard()->setText(m_view->selectionText(), QClipboard::Selection); } m_selChangedByUser = false; } if (m_dragInfo.state == diPending) { placeCursor(e->pos(), e->modifiers() & Qt::ShiftModifier); Q_EMIT m_view->selectionChanged(m_view); } else if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } m_dragInfo.state = diNone; e->accept(); break; case Qt::MidButton: placeCursor(e->pos()); if (doc()->isReadWrite()) { view()->m_clipboard.pasteFromClipboard(QClipboard::Selection); } e->accept(); break; default: e->ignore(); break; } } void KateViewInternal::leaveEvent(QEvent *) { m_textHintTimer.stop(); // fix bug 194452, scrolling keeps going if you scroll via mouse drag and press and other mouse // button outside the view area if (m_dragInfo.state == diNone) { m_scrollTimer.stop(); } } KTextEditor::Cursor KateViewInternal::coordinatesToCursor(const QPoint &_coord, bool includeBorder) const { QPoint coord(_coord); KTextEditor::Cursor ret = KTextEditor::Cursor::invalid(); if (includeBorder) { coord.rx() -= m_leftBorder->width(); } coord.rx() += startX(); const KateTextLayout &thisLine = yToKateTextLayout(coord.y()); if (thisLine.isValid()) { ret = renderer()->xToCursor(thisLine, coord.x(), !m_view->wrapCursor()); } if (ret.column() > view()->document()->lineLength(ret.line())) { // The cursor is beyond the end of the line; in that case the renderer // gives the index of the character behind the last one. return KTextEditor::Cursor::invalid(); } return ret; } void KateViewInternal::mouseMoveEvent(QMouseEvent *e) { KTextEditor::Cursor newPosition = coordinatesToCursor(e->pos(), false); if (newPosition != m_mouse) { m_mouse = newPosition; mouseMoved(); } if (e->buttons() & Qt::LeftButton) { if (m_dragInfo.state == diPending) { // we had a mouse down, but haven't confirmed a drag yet // if the mouse has moved sufficiently, we will confirm QPoint p(e->pos() - m_dragInfo.start); // we've left the drag square, we can start a real drag operation now if (p.manhattanLength() > QApplication::startDragDistance()) { doDrag(); } return; } else if (m_dragInfo.state == diDragging) { // Don't do anything after a canceled drag until the user lets go of // the mouse button! return; } m_mouseX = e->x(); m_mouseY = e->y(); m_scrollX = 0; m_scrollY = 0; int d = renderer()->lineHeight(); if (m_mouseX < 0) { m_scrollX = -d; } if (m_mouseX > width()) { m_scrollX = d; } if (m_mouseY < 0) { m_mouseY = 0; m_scrollY = -d; } if (m_mouseY > height()) { m_mouseY = height(); m_scrollY = d; } auto c = pointToCursor(QPoint(m_mouseX, m_mouseY)); selections()->updateNewSelection(c); updateCursorFlashTimer(); } else { if (isTargetSelected(e->pos())) { // mouse is over selected text. indicate that the text is draggable by setting // the arrow cursor as other Qt text editing widgets do if (m_mouseCursor != Qt::ArrowCursor) { m_mouseCursor = Qt::ArrowCursor; setCursor(m_mouseCursor); } } else { // normal text cursor if (m_mouseCursor != Qt::IBeamCursor) { m_mouseCursor = Qt::IBeamCursor; setCursor(m_mouseCursor); } } //We need to check whether the mouse position is actually within the widget, //because other widgets like the icon border forward their events to this, //and we will create invalid text hint requests if we don't check if (textHintsEnabled() && geometry().contains(parentWidget()->mapFromGlobal(e->globalPos()))) { if (QToolTip::isVisible()) { QToolTip::hideText(); } m_textHintTimer.start(m_textHintDelay); m_textHintPos = e->pos(); } } } void KateViewInternal::updateDirty() { const int h = renderer()->lineHeight(); int currentRectStart = -1; int currentRectEnd = -1; QRegion updateRegion; { for (int i = 0; i < cache()->viewCacheLineCount(); ++i) { if (cache()->viewLine(i).isDirty()) { if (currentRectStart == -1) { currentRectStart = h * i; currentRectEnd = h; } else { currentRectEnd += h; } } else if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); currentRectStart = -1; currentRectEnd = -1; } } } if (currentRectStart != -1) { updateRegion += QRect(0, currentRectStart, width(), currentRectEnd); } if (!updateRegion.isEmpty()) { if (debugPainting) { qCDebug(LOG_KTE) << "Update dirty region " << updateRegion; } update(updateRegion); } } void KateViewInternal::hideEvent(QHideEvent *e) { Q_UNUSED(e); if (m_view->isCompletionActive()) { m_view->completionWidget()->abortCompletion(); } } void KateViewInternal::paintEvent(QPaintEvent *e) { if (debugPainting) { qCDebug(LOG_KTE) << "GOT PAINT EVENT: Region" << e->region(); } const QRect &unionRect = e->rect(); int xStart = startX() + unionRect.x(); int xEnd = xStart + unionRect.width(); uint h = renderer()->lineHeight(); uint startz = (unionRect.y() / h); uint endz = startz + 1 + (unionRect.height() / h); uint lineRangesSize = cache()->viewCacheLineCount(); QPainter paint(this); paint.setRenderHints(QPainter::Antialiasing); paint.save(); renderer()->setCaretStyle(m_currentInputMode->caretStyle()); renderer()->setShowTabs(doc()->config()->showTabs()); renderer()->setShowTrailingSpaces(doc()->config()->showSpaces()); renderer()->updateMarkerSize(); int sy = startz * h; paint.translate(unionRect.x(), startz * h); for (uint z = startz; z <= endz; z++) { paint.save(); if ((z >= lineRangesSize) || (cache()->viewLine(z).line() == -1)) { if (!(z >= lineRangesSize)) { cache()->viewLine(z).setDirty(false); } paint.fillRect(0, 0, unionRect.width(), h, renderer()->config()->backgroundColor()); } else { //qCDebug(LOG_KTE)<<"KateViewInternal::paintEvent(QPaintEvent *e):cache()->viewLine"<viewLine(z); /* If viewLine() returns non-zero, then a document line was split in several visual lines, and we're trying to paint visual line that is not the first. In that case, this line was already painted previously, since KateRenderer::paintTextLine paints all visual lines. Except if we're at the start of the region that needs to be painted -- when no previous calls to paintTextLine were made. */ if (!thisLine.viewLine() || z == startz) { //qDebug() << "paint text: line: " << thisLine.line() << " viewLine " << thisLine.viewLine() << " x: " << unionRect.x() << " y: " << unionRect.y() << " width: " << xEnd-xStart << " height: " << h << endl; KTextEditor::Cursor pos = primaryCursor(); // first: paint our line paint.translate(QPoint(0, h * - thisLine.viewLine())); paint.setClipRect(QRect(0, 0, unionRect.width(), h * thisLine.kateLineLayout()->viewLineCount())); renderer()->paintTextLine(paint, thisLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * thisLine.viewLine()); // second: paint previous line elements, that span into our line like _, bug 335079 if (z > 0) { KateTextLayout &previousLine = cache()->viewLine(z-1); paint.translate(QPoint(0, h * - (previousLine.viewLine() + 1))); renderer()->paintTextLine(paint, previousLine.kateLineLayout(), xStart, xEnd, &pos); paint.translate(0, h * (previousLine.viewLine() + 1)); } /** * line painted, reset and state + mark line as non-dirty */ thisLine.setDirty(false); } } paint.restore(); paint.translate(0, h); sy += h; } paint.restore(); if (m_textAnimation) { m_textAnimation->draw(paint); } } void KateViewInternal::resizeEvent(QResizeEvent *e) { bool expandedHorizontally = width() > e->oldSize().width(); bool expandedVertically = height() > e->oldSize().height(); bool heightChanged = height() != e->oldSize().height(); m_dummy->setFixedSize(m_lineScroll->width(), m_columnScroll->sizeHint().height()); m_madeVisible = false; if (heightChanged) { setAutoCenterLines(m_autoCenterLines, false); m_cachedMaxStartPos.setPosition(-1, -1); } if (m_view->dynWordWrap()) { bool dirtied = false; for (int i = 0; i < cache()->viewCacheLineCount(); i++) { // find the first dirty line // the word wrap updateView algorithm is forced to check all lines after a dirty one KateTextLayout viewLine = cache()->viewLine(i); if (viewLine.wrap() || viewLine.isRightToLeft() || viewLine.width() > width()) { dirtied = true; viewLine.setDirty(); break; } } if (dirtied || heightChanged) { updateView(true); m_leftBorder->update(); } } else { updateView(); if (expandedHorizontally && startX() > 0) { scrollColumns(startX() - (width() - e->oldSize().width())); } } if (width() < e->oldSize().width() && !m_view->wrapCursor()) { // May have to restrain cursor to new smaller width... if (primaryCursor().column() > doc()->lineLength(primaryCursor().line())) { KateTextLayout thisLine = m_layoutCache->viewLine(primaryCursor().line()); KTextEditor::Cursor newCursor(primaryCursor().line(), thisLine.endCol() + ((width() - thisLine.xOffset() - (thisLine.width() - m_startX)) / renderer()->spaceWidth()) - 1); if (newCursor.column() < primaryCursor().column()) { cursors()->setPrimaryCursor(newCursor); } } } if (expandedVertically) { KTextEditor::Cursor max = maxStartPos(); if (startPos() > max) { scrollPos(max); return; // already fired displayRangeChanged } } emit m_view->displayRangeChanged(m_view); } void KateViewInternal::scrollTimeout() { if (m_scrollX || m_scrollY) { scrollLines(startPos().line() + (m_scrollY / (int) renderer()->lineHeight())); placeCursor(QPoint(m_mouseX, m_mouseY), true); } } void KateViewInternal::cursorTimeout() { if (!debugPainting && m_currentInputMode->blinkCaret()) { renderer()->setDrawCaret(!renderer()->drawCaret()); paintCursor(); } } void KateViewInternal::textHintTimeout() { m_textHintTimer.stop(); KTextEditor::Cursor c = coordinatesToCursor(m_textHintPos, false); if (!c.isValid()) { return; } QStringList textHints; foreach(KTextEditor::TextHintProvider * const p, m_textHintProviders) { const QString hint = p->textHint(m_view, c); if (!hint.isEmpty()) { textHints.append(hint); } } if (!textHints.isEmpty()) { qCDebug(LOG_KTE) << "Hint text: " << textHints; QString hint; foreach(const QString & str, textHints) { hint += QStringLiteral("

%1

").arg(str); } QPoint pos(startX() + m_textHintPos.x(), m_textHintPos.y()); QToolTip::showText(mapToGlobal(pos), hint); } } void KateViewInternal::focusInEvent(QFocusEvent *) { if (QApplication::cursorFlashTime() > 0) { m_cursorTimer.start(QApplication::cursorFlashTime() / 2); } paintCursor(); doc()->setActiveView(m_view); // this will handle focus stuff in kateview m_view->slotGotFocus(); } void KateViewInternal::focusOutEvent(QFocusEvent *) { //if (m_view->isCompletionActive()) //m_view->abortCompletion(); m_cursorTimer.stop(); m_view->renderer()->setDrawCaret(true); paintCursor(); m_textHintTimer.stop(); m_view->slotLostFocus(); } void KateViewInternal::doDrag() { m_dragInfo.state = diDragging; m_dragInfo.dragObject = new QDrag(this); QMimeData *mimeData = new QMimeData(); mimeData->setText(m_view->selectionText()); m_dragInfo.dragObject->setMimeData(mimeData); m_dragInfo.dragObject->start(Qt::MoveAction); } void KateViewInternal::dragEnterEvent(QDragEnterEvent *event) { if (event->source() == this) { event->setDropAction(Qt::MoveAction); } event->setAccepted((event->mimeData()->hasText() && doc()->isReadWrite()) || event->mimeData()->hasUrls()); } void KateViewInternal::fixDropEvent(QDropEvent *event) { if (event->source() != this) { event->setDropAction(Qt::CopyAction); } else { Qt::DropAction action = Qt::MoveAction; #ifdef Q_WS_MAC if (event->keyboardModifiers() & Qt::AltModifier) { action = Qt::CopyAction; } #else if (event->keyboardModifiers() & Qt::ControlModifier) { action = Qt::CopyAction; } #endif event->setDropAction(action); } } void KateViewInternal::dragMoveEvent(QDragMoveEvent *event) { // track the cursor to the current drop location placeCursor(event->pos(), true, false); qDebug() << "update drag:" << m_view->cursors()->cursors() << m_view->selections()->selections(); // important: accept action to switch between copy and move mode // without this, the text will always be copied. fixDropEvent(event); } void KateViewInternal::dropEvent(QDropEvent *event) { /** * if we have urls, pass this event off to the hosting application */ if (event->mimeData()->hasUrls()) { emit dropEventPass(event); return; } if (event->mimeData()->hasText() && doc()->isReadWrite()) { const QString text = event->mimeData()->text(); // is the source our own document? bool priv = false; if (KateViewInternal *vi = qobject_cast(event->source())) { priv = doc()->ownedView(vi->m_view); } // dropped on a text selection area? qDebug() << "have selections:" << m_view->selections()->selections(); bool selected = m_view->cursorSelected(primaryCursor()); fixDropEvent(event); if (priv && selected && event->dropAction() != Qt::CopyAction) { // this is a drag that we started and dropped on our selection // ignore this case return; } // fix the cursor position before editStart(), so that it is correctly // stored for the undo action KTextEditor::Cursor targetCursor(primaryCursor()); // backup current cursor int selectionWidth = m_view->selectionRange().columnWidth(); // for block selection int selectionHeight = m_view->selectionRange().numberOfLines(); // for block selection if (event->dropAction() == Qt::CopyAction) { m_view->clearSelection(); } // use one transaction doc()->editStart(); // on move: remove selected text; on copy: duplicate text qDebug() << "insert text:" << text << text.length() << "at" << targetCursor; doc()->insertText(targetCursor, text, m_view->blockSelection()); KTextEditor::DocumentCursor startCursor(doc(), targetCursor); if (event->dropAction() != Qt::CopyAction) { m_view->removeSelectedText(); auto selectionStartsAhead = m_view->primarySelection().start() < targetCursor; if ( selectionStartsAhead ) { startCursor.move(-text.length()); } } auto endCursor = startCursor; endCursor.move(text.length()); qDebug() << "end and taget cursor:" << endCursor << targetCursor; setSelection({startCursor, endCursor}); editSetCursor(endCursor); doc()->editEnd(); event->acceptProposedAction(); updateView(); } // finally finish drag and drop mode m_dragInfo.state = diNone; // important, because the eventFilter`s DragLeave does not occur stopDragScroll(); } //END EVENT HANDLING STUFF void KateViewInternal::clear() { m_startPos.setPosition(0, 0); m_displayCursor = KTextEditor::Cursor(0, 0); primaryCursor().setPosition(0, 0); cache()->clear(); updateView(true); } void KateViewInternal::wheelEvent(QWheelEvent *e) { // check if this event should change the font size (Ctrl pressed, angle reported and not accidentally so) // Note: if detectZoomingEvent() doesn't unset the ControlModifier we'll get accelerated scrolling. if (m_zoomEventFilter->detectZoomingEvent(e)) { if (e->angleDelta().y() > 0) { slotIncFontSizes(qreal(e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } else if (e->angleDelta().y() < 0) { slotDecFontSizes(qreal(-e->angleDelta().y()) / QWheelEvent::DefaultDeltasPerStep); } // accept always and be done for zooming e->accept(); return; } // handle vertical scrolling via the scrollbar if (e->orientation() == Qt::Vertical) { QWheelEvent copy = *e; QApplication::sendEvent(m_lineScroll, ©); if (copy.isAccepted()) { e->accept(); } } // handle horizontal scrolling via the scrollbar if (e->orientation() == Qt::Horizontal) { // if we have dyn word wrap, we should ignore the scroll events if (m_view->dynWordWrap()) { e->accept(); return; } QWheelEvent copy = *e; QApplication::sendEvent(m_columnScroll, ©); if (copy.isAccepted()) { e->accept(); } } } void KateViewInternal::startDragScroll() { if (!m_dragScrollTimer.isActive()) { m_dragScrollTimer.start(s_scrollTime); } } void KateViewInternal::stopDragScroll() { m_dragScrollTimer.stop(); updateView(); } void KateViewInternal::doDragScroll() { QPoint p = this->mapFromGlobal(QCursor::pos()); int dx = 0, dy = 0; if (p.y() < s_scrollMargin) { dy = p.y() - s_scrollMargin; } else if (p.y() > height() - s_scrollMargin) { dy = s_scrollMargin - (height() - p.y()); } if (p.x() < s_scrollMargin) { dx = p.x() - s_scrollMargin; } else if (p.x() > width() - s_scrollMargin) { dx = s_scrollMargin - (width() - p.x()); } dy /= 4; if (dy) { scrollLines(startPos().line() + dy); } if (columnScrollingPossible() && dx) { scrollColumns(qMin(m_startX + dx, m_columnScroll->maximum())); } if (!dy && !dx) { stopDragScroll(); } } void KateViewInternal::registerTextHintProvider(KTextEditor::TextHintProvider *provider) { if (! m_textHintProviders.contains(provider)) { m_textHintProviders.append(provider); } // we have a client, so start timeout m_textHintTimer.start(m_textHintDelay); } void KateViewInternal::unregisterTextHintProvider(KTextEditor::TextHintProvider *provider) { const int index = m_textHintProviders.indexOf(provider); if (index >= 0) { m_textHintProviders.removeAt(index); } if (m_textHintProviders.isEmpty()) { m_textHintTimer.stop(); } } void KateViewInternal::setTextHintDelay(int delay) { if (delay <= 0) { m_textHintDelay = 200; // ms } else { m_textHintDelay = delay; // ms } } int KateViewInternal::textHintDelay() const { return m_textHintDelay; } bool KateViewInternal::textHintsEnabled() { return ! m_textHintProviders.isEmpty(); } //BEGIN EDIT STUFF void KateViewInternal::editStart() { editSessionNumber++; if (editSessionNumber > 1) { return; } editIsRunning = true; editOldCursor = primaryCursor(); editOldSelection = m_view->selectionRange(); } void KateViewInternal::editEnd(int editTagLineStart, int editTagLineEnd, bool tagFrom) { if (editSessionNumber == 0) { return; } editSessionNumber--; if (editSessionNumber > 0) { return; } // fix start position, might have moved from column 0 // try to clever calculate the right start column for the tricky dyn word wrap case int col = 0; if (m_view->dynWordWrap()) { if (KateLineLayoutPtr layout = cache()->line(m_startPos.line())) { int index = layout->viewLineForColumn(m_startPos.column()); if (index >= 0 && index < layout->viewLineCount()) { col = layout->viewLine(index).startCol(); } } } m_startPos.setPosition(m_startPos.line(), col); if (tagFrom && (editTagLineStart <= int(m_view->textFolding().visibleLineToLine(startLine())))) { tagAll(); } else { tagLines(editTagLineStart, tagFrom ? qMax(doc()->lastLine() + 1, editTagLineEnd) : editTagLineEnd, true); } if (editOldCursor == primaryCursor()) { updateBracketMarks(); } updateView(true); if (editOldCursor != primaryCursor() || m_view == doc()->activeView()) { // Only scroll the view to the cursor if the insertion happens at the cursor. // This might not be the case for e.g. collaborative editing, when a remote user // inserts text at a position not at the caret. if (primaryCursor().line() >= editTagLineStart && primaryCursor().line() <= editTagLineEnd) { m_madeVisible = false; notifyPrimaryCursorChanged(primaryCursor(), true); } } /** * selection changed? * fixes bug 316226 */ if (editOldSelection != m_view->selectionRange() || (editOldSelection.isValid() && !editOldSelection.isEmpty() && !(editTagLineStart > editOldSelection.end().line() && editTagLineEnd < editOldSelection.start().line()))) { emit m_view->selectionChanged(m_view); } editIsRunning = false; } void KateViewInternal::editSetCursor(const KTextEditor::Cursor &_cursor) { if (primaryCursor() != _cursor) { cursors()->setPrimaryCursor(_cursor, false); } } //END KateLayoutCache *KateViewInternal::cache() const { return m_layoutCache; } void KateViewInternal::notifyLinesUpdated(const QVector& changed) { Q_FOREACH ( const auto& cursor, changed ) { tagLine(toVirtualCursor(cursor)); } updateCursorFlashTimer(); updateDirty(); } KTextEditor::Cursor KateViewInternal::toRealCursor(const KTextEditor::Cursor &virtualCursor) const { return KTextEditor::Cursor(m_view->textFolding().visibleLineToLine(virtualCursor.line()), virtualCursor.column()); } KTextEditor::Cursor KateViewInternal::toVirtualCursor(const KTextEditor::Cursor &realCursor) const { /** * only convert valid lines, folding doesn't like invalid input! * don't validate whole cursor, column might be -1 */ if (realCursor.line() < 0) { return KTextEditor::Cursor::invalid(); } return KTextEditor::Cursor(m_view->textFolding().lineToVisibleLine(realCursor.line()), realCursor.column()); } KateRenderer *KateViewInternal::renderer() const { return m_view->renderer(); } void KateViewInternal::mouseMoved() { m_view->notifyMousePositionChanged(m_mouse); m_view->updateRangesIn(KTextEditor::Attribute::ActivateMouseIn); } void KateViewInternal::cursorMoved() { m_view->updateRangesIn(KTextEditor::Attribute::ActivateCaretIn); #ifndef QT_NO_ACCESSIBILITY QAccessibleTextCursorEvent ev(this, KateViewAccessible::positionFromCursor(this, primaryCursor())); QAccessible::updateAccessibility(&ev); #endif } bool KateViewInternal::rangeAffectsView(const KTextEditor::Range &range, bool realCursors) const { int startLine = m_startPos.line(); int endLine = startLine + (int)m_visibleLineCount; if (realCursors) { startLine = (int)m_view->textFolding().visibleLineToLine(startLine); endLine = (int)m_view->textFolding().visibleLineToLine(endLine); } return (range.end().line() >= startLine) || (range.start().line() <= endLine); } //BEGIN IM INPUT STUFF QVariant KateViewInternal::inputMethodQuery(Qt::InputMethodQuery query) const { switch (query) { case Qt::ImCursorRectangle: { // Cursor placement code is changed for Asian input method that // shows candidate window. This behavior is same as Qt/E 2.3.7 // which supports Asian input methods. Asian input methods need // start point of IM selection text to place candidate window as // adjacent to the selection text. // // in Qt5, cursor rectangle is used as QRectF internally, and it // will be checked by QRectF::isValid(), which will mark rectangle // with width == 0 or height == 0 as invalid. auto lineHeight = renderer()->lineHeight(); return QRect(cursorToCoordinate(primaryCursor(), true, false), QSize(1, lineHeight ? lineHeight : 1)); } case Qt::ImFont: return renderer()->currentFont(); case Qt::ImCursorPosition: return primaryCursor().column(); case Qt::ImAnchorPosition: // If selectAnchor is at the same line, return the real anchor position // Otherwise return the same position of cursor if (m_view->selection() && m_selectAnchor.line() == primaryCursor().line()) { return m_selectAnchor.column(); } else { return primaryCursor().column(); } case Qt::ImSurroundingText: if (Kate::TextLine l = doc()->kateTextLine(primaryCursor().line())) { return l->string(); } else { return QString(); } case Qt::ImCurrentSelection: if (m_view->selection()) { return m_view->selectionText(); } else { return QString(); } default: /* values: ImMaximumTextLength */ break; } return QWidget::inputMethodQuery(query); } void KateViewInternal::inputMethodEvent(QInputMethodEvent *e) { if (doc()->readOnly()) { e->ignore(); return; } //qCDebug(LOG_KTE) << "Event: cursor" << primaryCursor() << "commit" << e->commitString() << "preedit" << e->preeditString() << "replacement start" << e->replacementStart() << "length" << e->replacementLength(); if (!m_imPreeditRange) { m_imPreeditRange = doc()->newMovingRange(KTextEditor::Range(primaryCursor(), primaryCursor()), KTextEditor::MovingRange::ExpandLeft | KTextEditor::MovingRange::ExpandRight); } if (!m_imPreeditRange->toRange().isEmpty()) { doc()->inputMethodStart(); doc()->removeText(*m_imPreeditRange); doc()->inputMethodEnd(); } if (!e->commitString().isEmpty() || e->replacementLength()) { m_view->removeSelectedText(); KTextEditor::Range preeditRange = *m_imPreeditRange; KTextEditor::Cursor start(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + e->replacementStart()); KTextEditor::Cursor removeEnd = start + KTextEditor::Cursor(0, e->replacementLength()); doc()->editStart(); if (start != removeEnd) { doc()->removeText(KTextEditor::Range(start, removeEnd)); } if (!e->commitString().isEmpty()) { // if the input method event is text that should be inserted, call KTextEditor::DocumentPrivate::typeChars() // with the text. that method will handle the input and take care of overwrite mode, etc. doc()->typeChars(m_view, e->commitString()); } doc()->editEnd(); // Revert to the same range as above m_imPreeditRange->setRange(preeditRange); } if (!e->preeditString().isEmpty()) { doc()->inputMethodStart(); doc()->insertText(m_imPreeditRange->start(), e->preeditString()); doc()->inputMethodEnd(); // The preedit range gets automatically repositioned } // Finished this input method context? if (m_imPreeditRange && e->preeditString().isEmpty()) { // delete the range and reset the pointer delete m_imPreeditRange; m_imPreeditRange = nullptr; qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); if (QApplication::cursorFlashTime() > 0) { renderer()->setDrawCaret(false); } renderer()->setCaretOverrideColor(QColor()); e->accept(); return; } KTextEditor::Cursor newCursor = primaryCursor(); bool hideCursor = false; QColor caretColor; if (m_imPreeditRange) { qDeleteAll(m_imPreeditRangeChildren); m_imPreeditRangeChildren.clear(); int decorationColumn = 0; foreach (const QInputMethodEvent::Attribute &a, e->attributes()) { if (a.type == QInputMethodEvent::Cursor) { newCursor = m_imPreeditRange->start() + KTextEditor::Cursor(0, a.start); hideCursor = !a.length; QColor c = qvariant_cast(a.value); if (c.isValid()) { caretColor = c; } } else if (a.type == QInputMethodEvent::TextFormat) { QTextCharFormat f = qvariant_cast(a.value).toCharFormat(); if (f.isValid() && decorationColumn <= a.start) { KTextEditor::Range fr(m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start, m_imPreeditRange->start().line(), m_imPreeditRange->start().column() + a.start + a.length); KTextEditor::MovingRange *formatRange = doc()->newMovingRange(fr); KTextEditor::Attribute::Ptr attribute(new KTextEditor::Attribute()); attribute->merge(f); formatRange->setAttribute(attribute); decorationColumn = a.start + a.length; m_imPreeditRangeChildren.push_back(formatRange); } } } } renderer()->setDrawCaret(hideCursor); renderer()->setCaretOverrideColor(caretColor); if (newCursor != primaryCursor()) { cursors()->setPrimaryCursor(newCursor); } e->accept(); } //END IM INPUT STUFF void KateViewInternal::flashChar(const KTextEditor::Cursor &pos, KTextEditor::Attribute::Ptr attribute) { Q_ASSERT(pos.isValid()); Q_ASSERT(attribute.constData()); // if line is folded away, do nothing if (!m_view->textFolding().isLineVisible(pos.line())) { return; } KTextEditor::Range range(pos, KTextEditor::Cursor(pos.line(), pos.column() + 1)); if (m_textAnimation) { m_textAnimation->deleteLater(); } m_textAnimation = new KateTextAnimation(range, attribute, this); } void KateViewInternal::documentTextInserted(KTextEditor::Document *document, const KTextEditor::Range &range) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextInsertEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), document->text(range)); QAccessible::updateAccessibility(&ev); } #endif } void KateViewInternal::documentTextRemoved(KTextEditor::Document * /*document*/, const KTextEditor::Range &range, const QString &oldText) { #ifndef QT_NO_ACCESSIBILITY if (QAccessible::isActive()) { QAccessibleTextRemoveEvent ev(this, KateViewAccessible::positionFromCursor(this, range.start()), oldText); QAccessible::updateAccessibility(&ev); } #endif } diff --git a/src/vimode/appcommands.cpp b/src/vimode/appcommands.cpp index a0a8ed5a..66502a01 100644 --- a/src/vimode/appcommands.cpp +++ b/src/vimode/appcommands.cpp @@ -1,523 +1,523 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Erlend Hamberg Copyright (C) 2011 Svyatoslav Kuzmich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include using namespace KateVi; //BEGIN AppCommands AppCommands *AppCommands::m_instance = nullptr; AppCommands::AppCommands() - : KTextEditor::Command(QStringList() << QStringLiteral("q") << QStringLiteral("qa") << QStringLiteral("qall") << QStringLiteral("q!") << QStringLiteral("qa!") << QStringLiteral("qall!") - << QStringLiteral("w") << QStringLiteral("wq") << QStringLiteral("wa") << QStringLiteral("wqa") << QStringLiteral("x") << QStringLiteral("xa") << QStringLiteral("new") - << QStringLiteral("vnew") << QStringLiteral("e") << QStringLiteral("edit") << QStringLiteral("enew") << QStringLiteral("sp") << QStringLiteral("split") << QStringLiteral("vs") - << QStringLiteral("vsplit") << QStringLiteral("only") << QStringLiteral("tabe") << QStringLiteral("tabedit") << QStringLiteral("tabnew") << QStringLiteral("bd") - << QStringLiteral("bdelete") << QStringLiteral("tabc") << QStringLiteral("tabclose") << QStringLiteral("clo") << QStringLiteral("close")) + : KTextEditor::Command({ QStringLiteral("q"), QStringLiteral("qa"), QStringLiteral("qall"), QStringLiteral("q!"), QStringLiteral("qa!"), QStringLiteral("qall!") + , QStringLiteral("w"), QStringLiteral("wq"), QStringLiteral("wa"), QStringLiteral("wqa"), QStringLiteral("x"), QStringLiteral("xa"), QStringLiteral("new") + , QStringLiteral("vnew"), QStringLiteral("e"), QStringLiteral("edit"), QStringLiteral("enew"), QStringLiteral("sp"), QStringLiteral("split"), QStringLiteral("vs") + , QStringLiteral("vsplit"), QStringLiteral("only"), QStringLiteral("tabe"), QStringLiteral("tabedit"), QStringLiteral("tabnew"), QStringLiteral("bd") + , QStringLiteral("bdelete"), QStringLiteral("tabc"), QStringLiteral("tabclose"), QStringLiteral("clo"), QStringLiteral("close") }) { re_write.setPattern(QStringLiteral("w(a)?")); re_close.setPattern(QStringLiteral("bd(elete)?|tabc(lose)?")); re_quit.setPattern(QStringLiteral("(w)?q(a|all)?(!)?")); re_exit.setPattern(QStringLiteral("x(a)?")); re_edit.setPattern(QStringLiteral("e(dit)?|tabe(dit)?|tabnew")); re_tabedit.setPattern(QStringLiteral("tabe(dit)?|tabnew")); re_new.setPattern(QStringLiteral("(v)?new")); re_split.setPattern(QStringLiteral("sp(lit)?")); re_vsplit.setPattern(QStringLiteral("vs(plit)?")); re_vclose.setPattern(QStringLiteral("clo(se)?")); re_only.setPattern(QStringLiteral("on(ly)?")); } AppCommands::~AppCommands() { m_instance = nullptr; } bool AppCommands::exec(KTextEditor::View *view, const QString &cmd, QString &msg, const KTextEditor::Range &) { QStringList args(cmd.split(QRegExp(QLatin1String("\\s+")), QString::SkipEmptyParts)) ; QString command(args.takeFirst()); KTextEditor::MainWindow *mainWin = view->mainWindow(); KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); if (re_write.exactMatch(command)) { //TODO: handle writing to specific file if (!re_write.cap(1).isEmpty()) { // [a]ll Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } msg = i18n("All documents written to disk"); } else { view->document()->documentSave(); msg = i18n("Document written to disk"); } } // Other buffer commands are implemented by the KateFileTree plugin else if (re_close.exactMatch(command)) { QTimer::singleShot(0, [app, view](){ app->closeDocument(view->document()); }); } else if (re_quit.exactMatch(command)) { const bool save = !re_quit.cap(1).isEmpty(); // :[w]q const bool allDocuments = !re_quit.cap(2).isEmpty(); // :q[all] const bool doNotPromptForSave = !re_quit.cap(3).isEmpty(); // :q[!] if (allDocuments) { if (save) { Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } } if (doNotPromptForSave) { Q_FOREACH(KTextEditor::Document *doc, app->documents()) { if (doc->isModified()) { doc->setModified(false); } } } QTimer::singleShot(0, this, SLOT(quit())); } else { if (save && view->document()->isModified()) { view->document()->documentSave(); } if (doNotPromptForSave) { view->document()->setModified(false); } if (mainWin->views().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentView())); } else { if (app->documents().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentDocument())); } else { QTimer::singleShot(0, this, SLOT(quit())); } } } } else if (re_exit.exactMatch(command)) { if (!re_exit.cap(1).isEmpty()) { // a[ll] Q_FOREACH(KTextEditor::Document *doc, app->documents()) { doc->save(); } QTimer::singleShot(0, this, SLOT(quit())); } else { if (view->document()->isModified()) { view->document()->documentSave(); } if (app->documents().size() > 1) { QTimer::singleShot(0, this, SLOT(closeCurrentDocument())); } else { QTimer::singleShot(0, this, SLOT(quit())); } } } else if (re_edit.exactMatch(command)) { QString argument = args.join(QLatin1Char(' ')); if (argument.isEmpty() || argument == QLatin1String("!")) { if (re_tabedit.exactMatch(command)) { if (auto doc = app->openUrl(QUrl())) { QTimer::singleShot(0, [mainWin, doc](){ mainWin->activateView(doc); }); } } else { view->document()->documentReload(); } } else { QUrl base = view->document()->url(); QUrl url; QUrl arg2path(argument); if (base.isValid()) { // first try to use the same path as the current open document has url = QUrl(base.resolved(arg2path)); //resolved handles the case where the args is a relative path, and is the same as using QUrl(args) elsewise } else { // else use the cwd url = QUrl(QUrl(QDir::currentPath() + QLatin1String("/")).resolved(arg2path)); // + "/" is needed because of http://lists.qt.nokia.com/public/qt-interest/2011-May/033913.html } QFileInfo file(url.toLocalFile()); KTextEditor::Document *doc = app->findUrl(url); if (!doc) { if (file.exists()) { doc = app->openUrl(url); } else { if ((doc = app->openUrl(QUrl()))) { doc->saveAs(url); } } } if (doc) { QTimer::singleShot(0, [mainWin, doc](){ mainWin->activateView(doc); }); } } // splitView() orientations are reversed from the usual editor convention. // 'vsplit' and 'vnew' use Qt::Horizontal to match vi and the Kate UI actions. } else if (re_new.exactMatch(command)) { if (re_new.cap(1) == QLatin1String("v")) { // vertical split mainWin->splitView(Qt::Horizontal); } else { // horizontal split mainWin->splitView(Qt::Vertical); } mainWin->openUrl(QUrl()); } else if (command == QLatin1String("enew")) { mainWin->openUrl(QUrl()); } else if (re_split.exactMatch(command)) { mainWin->splitView(Qt::Vertical); // see above } else if (re_vsplit.exactMatch(command)) { mainWin->splitView(Qt::Horizontal); } else if (re_vclose.exactMatch(command)) { QTimer::singleShot(0, this, SLOT(closeCurrentSplitView())); } else if (re_only.exactMatch(command)) { QTimer::singleShot(0, this, SLOT(closeOtherSplitViews())); } return true; } bool AppCommands::help(KTextEditor::View *view, const QString &cmd, QString &msg) { Q_UNUSED(view); if (re_write.exactMatch(cmd)) { msg = i18n("

w/wa — write document(s) to disk

" "

Usage: w[a]

" "

Writes the current document(s) to disk. " "It can be called in two ways:
" " w — writes the current document to disk
" " wa — writes all documents to disk.

" "

If no file name is associated with the document, " "a file dialog will be shown.

"); return true; } else if (re_quit.exactMatch(cmd)) { msg = i18n("

q/qa/wq/wqa — [write and] quit

" "

Usage: [w]q[a]

" "

Quits the application. If w is prepended, it also writes" " the document(s) to disk. This command " "can be called in several ways:
" " q — closes the current view.
" " qa — closes all views, effectively quitting the application.
" " wq — writes the current document to disk and closes its view.
" " wqa — writes all documents to disk and quits.

" "

In all cases, if the view being closed is the last view, the application quits. " "If no file name is associated with the document and it should be written to disk, " "a file dialog will be shown.

"); return true; } else if (re_exit.exactMatch(cmd)) { msg = i18n("

x/xa — write and quit

" "

Usage: x[a]

" "

Saves document(s) and quits (exits). This command " "can be called in two ways:
" " x — closes the current view.
" " xa — closes all views, effectively quitting the application.

" "

In all cases, if the view being closed is the last view, the application quits. " "If no file name is associated with the document and it should be written to disk, " "a file dialog will be shown.

" "

Unlike the 'w' commands, this command only writes the document if it is modified." "

"); return true; } else if (re_split.exactMatch(cmd)) { msg = i18n("

sp,split— Split horizontally the current view into two

" "

Usage: sp[lit]

" "

The result is two views on the same document.

"); return true; } else if (re_vsplit.exactMatch(cmd)) { msg = i18n("

vs,vsplit— Split vertically the current view into two

" "

Usage: vs[plit]

" "

The result is two views on the same document.

"); return true; } else if (re_vclose.exactMatch(cmd)) { msg = i18n("

clo[se]— Close the current view

" "

Usage: clo[se]

" "

After executing it, the current view will be closed.

"); return true; } else if (re_new.exactMatch(cmd)) { msg = i18n("

[v]new — split view and create new document

" "

Usage: [v]new

" "

Splits the current view and opens a new document in the new view." " This command can be called in two ways:
" " new — splits the view horizontally and opens a new document.
" " vnew — splits the view vertically and opens a new document.
" "

"); return true; } else if (re_edit.exactMatch(cmd)) { msg = i18n("

e[dit] — reload current document

" "

Usage: e[dit]

" "

Starts editing the current document again. This is useful to re-edit" " the current file, when it has been changed by another program.

"); return true; } return false; } KTextEditor::View * AppCommands::findViewInDifferentSplitView(KTextEditor::MainWindow *window, KTextEditor::View *view) { Q_FOREACH (KTextEditor::View *it, window->views()) { if (!window->viewsInSameSplitView(it, view)) { return it; } } return nullptr; } void AppCommands::closeCurrentDocument() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::Document *doc = app->activeMainWindow()->activeView()->document(); QTimer::singleShot(0, [app, doc](){ app->closeDocument(doc); }); } void AppCommands::closeCurrentView() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); mw->closeView(mw->activeView()); } void AppCommands::closeCurrentSplitView() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); mw->closeSplitView(mw->activeView()); } void AppCommands::closeOtherSplitViews() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); KTextEditor::MainWindow *mw = app->activeMainWindow(); KTextEditor::View *view = mw->activeView(); KTextEditor::View *viewToRemove = nullptr; while ((viewToRemove = findViewInDifferentSplitView(mw, view))) { mw->closeSplitView(viewToRemove); } } void AppCommands::quit() { KTextEditor::Editor::instance()->application()->quit(); } //END AppCommands //BEGIN KateViBufferCommand BufferCommands *BufferCommands::m_instance = nullptr; BufferCommands::BufferCommands() - : KTextEditor::Command(QStringList() << QStringLiteral("ls") - << QStringLiteral("b") << QStringLiteral("buffer") - << QStringLiteral("bn") << QStringLiteral("bnext") << QStringLiteral("bp") << QStringLiteral("bprevious") - << QStringLiteral("tabn") << QStringLiteral("tabnext") << QStringLiteral("tabp") << QStringLiteral("tabprevious") - << QStringLiteral("bf") << QStringLiteral("bfirst") << QStringLiteral("bl") << QStringLiteral("blast") - << QStringLiteral("tabf") << QStringLiteral("tabfirst") << QStringLiteral("tabl") << QStringLiteral("tablast")) + : KTextEditor::Command({ QStringLiteral("ls") + , QStringLiteral("b"), QStringLiteral("buffer") + , QStringLiteral("bn"), QStringLiteral("bnext"), QStringLiteral("bp"), QStringLiteral("bprevious") + , QStringLiteral("tabn"), QStringLiteral("tabnext"), QStringLiteral("tabp"), QStringLiteral("tabprevious") + , QStringLiteral("bf"), QStringLiteral("bfirst"), QStringLiteral("bl"), QStringLiteral("blast") + , QStringLiteral("tabf"), QStringLiteral("tabfirst"), QStringLiteral("tabl"), QStringLiteral("tablast")}) { } BufferCommands::~BufferCommands() { m_instance = nullptr; } bool BufferCommands::exec(KTextEditor::View *view, const QString &cmd, QString &, const KTextEditor::Range &) { // create list of args QStringList args(cmd.split(QLatin1Char(' '), QString::KeepEmptyParts)); QString command = args.takeFirst(); // same as cmd if split failed QString argument = args.join(QLatin1Char(' ')); if (command == QLatin1String("ls")) { // TODO: open quickview } else if (command == QLatin1String("b") || command == QLatin1String("buffer")) { switchDocument(view, argument); } else if (command == QLatin1String("bp") || command == QLatin1String("bprevious")) { prevBuffer(view); } else if (command == QLatin1String("bn") || command == QLatin1String("bnext")) { nextBuffer(view); } else if (command == QLatin1String("bf") || command == QLatin1String("bfirst")) { firstBuffer(view); } else if (command == QLatin1String("bl") || command == QLatin1String("blast")) { lastBuffer(view); } else if (command == QLatin1String("tabn") || command == QLatin1String("tabnext")) { nextTab(view); } else if (command == QLatin1String("tabp") || command == QLatin1String("tabprevious")) { prevTab(view); } else if (command == QLatin1String("tabf") || command == QLatin1String("tabfirst")) { firstTab(view); } else if (command == QLatin1String("tabl") || command == QLatin1String("tablast")) { lastTab(view); } return true; } void BufferCommands::switchDocument(KTextEditor::View *view, const QString &address) { if (address.isEmpty()) { // no argument: switch to the previous document prevBuffer(view); return; } const int idx = address.toInt(); QList docs = documents(); if (idx > 0 && idx <= docs.size()) { // numerical argument: switch to the nth document activateDocument(view, docs.at(idx - 1)); } else { // string argument: switch to the given file KTextEditor::Document *doc = nullptr; Q_FOREACH(KTextEditor::Document *it, docs) { if (it->documentName() == address) { doc = it; break; } } if (doc) { activateDocument(view, doc); } } } void BufferCommands::prevBuffer(KTextEditor::View *view) { QList docs = documents(); const int idx = docs.indexOf(view->document()); if (idx > 0) { activateDocument(view, docs.at(idx - 1)); } else if (!docs.isEmpty()) { // wrap activateDocument(view, docs.last()); } } void BufferCommands::nextBuffer(KTextEditor::View *view) { QList docs = documents(); const int idx = docs.indexOf(view->document()); if (idx + 1 < docs.size()) { activateDocument(view, docs.at(idx + 1)); } else if (!docs.isEmpty()) { // wrap activateDocument(view, docs.first()); } } void BufferCommands::firstBuffer(KTextEditor::View *view) { auto docs = documents(); if (!docs.isEmpty()) { activateDocument(view, documents().at(0)); } } void BufferCommands::lastBuffer(KTextEditor::View *view) { auto docs = documents(); if (!docs.isEmpty()) { activateDocument(view, documents().last()); } } void BufferCommands::prevTab(KTextEditor::View *view) { prevBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::nextTab(KTextEditor::View *view) { nextBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::firstTab(KTextEditor::View *view) { firstBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::lastTab(KTextEditor::View *view) { lastBuffer(view); // TODO: implement properly, when interface is added } void BufferCommands::activateDocument(KTextEditor::View *view, KTextEditor::Document *doc) { KTextEditor::MainWindow *mainWindow = view->mainWindow(); QTimer::singleShot(0, [mainWindow, doc]() { mainWindow->activateView(doc); }); } QList< KTextEditor::Document * > BufferCommands::documents() { KTextEditor::Application *app = KTextEditor::Editor::instance()->application(); return app->documents(); } bool BufferCommands::help(KTextEditor::View * /*view*/, const QString &cmd, QString &msg) { if (cmd == QLatin1String("b") || cmd == QLatin1String("buffer")) { msg = i18n("

b,buffer — Edit document N from the document list

" "

Usage: b[uffer] [N]

"); return true; } else if (cmd == QLatin1String("bp") || cmd == QLatin1String("bprevious") || cmd == QLatin1String("tabp") || cmd == QLatin1String("tabprevious")) { msg = i18n("

bp,bprev — previous buffer

" "

Usage: bp[revious] [N]

" "

Goes to [N]th previous document (\"buffer\") in document list.

" "

[N] defaults to one.

" "

Wraps around the start of the document list.

"); return true; } else if (cmd == QLatin1String("bn") || cmd == QLatin1String("bnext") || cmd == QLatin1String("tabn") || cmd == QLatin1String("tabnext")) { msg = i18n("

bn,bnext — switch to next document

" "

Usage: bn[ext] [N]

" "

Goes to [N]th next document (\"buffer\") in document list." "[N] defaults to one.

" "

Wraps around the end of the document list.

"); return true; } else if (cmd == QLatin1String("bf") || cmd == QLatin1String("bfirst") || cmd == QLatin1String("tabf") || cmd == QLatin1String("tabfirst")) { msg = i18n("

bf,bfirst — first document

" "

Usage: bf[irst]

" "

Goes to the first document (\"buffer\") in document list.

"); return true; } else if (cmd == QLatin1String("bl") || cmd == QLatin1String("blast") || cmd == QLatin1String("tabl") || cmd == QLatin1String("tablast")) { msg = i18n("

bl,blast — last document

" "

Usage: bl[ast]

" "

Goes to the last document (\"buffer\") in document list.

"); return true; } else if (cmd == QLatin1String("ls")) { msg = i18n("

ls

" "

list current buffers

"); } return false; } //END KateViBufferCommand diff --git a/src/vimode/emulatedcommandbar/commandmode.cpp b/src/vimode/emulatedcommandbar/commandmode.cpp index d26d9142..3c1f173c 100644 --- a/src/vimode/emulatedcommandbar/commandmode.cpp +++ b/src/vimode/emulatedcommandbar/commandmode.cpp @@ -1,407 +1,406 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2013-2016 Simon St James * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "commandmode.h" #include "emulatedcommandbar.h" #include "interactivesedreplacemode.h" #include "searchmode.h" #include "../commandrangeexpressionparser.h" #include #include #include #include "../globalstate.h" #include "../history.h" #include "katescriptmanager.h" #include "katecmds.h" #include #include #include using namespace KateVi; CommandMode::CommandMode ( EmulatedCommandBar* emulatedCommandBar, MatchHighlighter* matchHighlighter, InputModeManager* viInputModeManager, KTextEditor::ViewPrivate* view, QLineEdit* edit, InteractiveSedReplaceMode* interactiveSedReplaceMode, Completer* completer) : ActiveMode ( emulatedCommandBar, matchHighlighter, viInputModeManager, view), m_edit(edit), m_interactiveSedReplaceMode(interactiveSedReplaceMode), m_completer(completer) { QList cmds; cmds.push_back(KateCommands::CoreCommands::self()); cmds.push_back(Commands::self()); cmds.push_back(AppCommands::self()); cmds.push_back(SedReplace::self()); cmds.push_back(BufferCommands::self()); Q_FOREACH (KTextEditor::Command *cmd, KateScriptManager::self()->commandLineScripts()) { cmds.push_back(cmd); } Q_FOREACH (KTextEditor::Command *cmd, cmds) { QStringList l = cmd->cmds(); for (int z = 0; z < l.count(); z++) { m_cmdDict.insert(l[z], cmd); } m_cmdCompletion.insertItems(l); } } bool CommandMode::handleKeyPress ( const QKeyEvent* keyEvent ) { if (keyEvent->modifiers() == Qt::ControlModifier && (keyEvent->key() == Qt::Key_D || keyEvent->key() == Qt::Key_F)) { CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); if (parsedSedExpression.parsedSuccessfully) { const bool clearFindTerm = (keyEvent->key() == Qt::Key_D); if (clearFindTerm) { m_edit->setSelection(parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos - parsedSedExpression.findBeginPos + 1); m_edit->insert(QString()); } else { // Clear replace term. m_edit->setSelection(parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos - parsedSedExpression.replaceBeginPos + 1); m_edit->insert(QString()); } } return true; } return false; } void CommandMode::editTextChanged ( const QString& newText ) { Q_UNUSED(newText); // We read the current text from m_edit. if (m_completer->isCompletionActive()) return; // Command completion doesn't need to be manually invoked. if (!withoutRangeExpression().isEmpty() && !m_completer->isNextTextChangeDueToCompletionChange()) { // ... However, command completion mode should not be automatically invoked if this is not the current leading // word in the text edit (it gets annoying if completion pops up after ":s/se" etc). const bool commandBeforeCursorIsLeading = (commandBeforeCursorBegin() == rangeExpression().length()); if (commandBeforeCursorIsLeading) { CompletionStartParams completionStartParams = activateCommandCompletion(); startCompletion(completionStartParams); } } } void CommandMode::deactivate ( bool wasAborted ) { if (wasAborted) { // Appending the command to the history when it is executed is handled elsewhere; we can't // do it inside closed() as we may still be showing the command response display. viInputModeManager()->globalState()->commandHistory()->append(m_edit->text()); // With Vim, aborting a command returns us to Normal mode, even if we were in Visual Mode. // If we switch from Visual to Normal mode, we need to clear the selection. view()->clearSelection(); } } CompletionStartParams CommandMode::completionInvoked(Completer::CompletionInvocation invocationType) { CompletionStartParams completionStartParams; if (invocationType == Completer::CompletionInvocation::ExtraContext) { if (isCursorInFindTermOfSed()) { completionStartParams = activateSedFindHistoryCompletion(); } else if (isCursorInReplaceTermOfSed()) { completionStartParams = activateSedReplaceHistoryCompletion(); } else { completionStartParams = activateCommandHistoryCompletion(); } } else { // Normal context, so boring, ordinary History completion. completionStartParams = activateCommandHistoryCompletion(); } return completionStartParams; } void CommandMode::completionChosen() { QString commandToExecute = m_edit->text(); CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); if (parsedSedExpression.parsedSuccessfully) { const QString originalFindTerm = sedFindTerm(); const QString convertedFindTerm = vimRegexToQtRegexPattern(originalFindTerm); const QString commandWithSedSearchRegexConverted = withSedFindTermReplacedWith(convertedFindTerm); viInputModeManager()->globalState()->searchHistory()->append(originalFindTerm); const QString replaceTerm = sedReplaceTerm(); viInputModeManager()->globalState()->replaceHistory()->append(replaceTerm); commandToExecute = commandWithSedSearchRegexConverted; } const QString commandResponseMessage = executeCommand(commandToExecute); // Don't close the bar if executing the command switched us to Interactive Sed Replace mode. if (!m_interactiveSedReplaceMode->isActive()) { if (commandResponseMessage.isEmpty()) { emulatedCommandBar()->hideMe(); } else { closeWithStatusMessage(commandResponseMessage); } } viInputModeManager()->globalState()->commandHistory()->append(m_edit->text()); } QString CommandMode::executeCommand ( const QString& commandToExecute ) { // Silently ignore leading space characters and colon characters (for vi-heads). uint n = 0; const uint textlen = commandToExecute.length(); while ((n < textlen) && commandToExecute[n].isSpace()) { n++; } if (n >= textlen) { return QString(); } QString commandResponseMessage; QString cmd = commandToExecute.mid(n); KTextEditor::Range range = CommandRangeExpressionParser(viInputModeManager()).parseRange(cmd, cmd); if (cmd.length() > 0) { KTextEditor::Command *p = queryCommand(cmd); if (p) { KateViCommandInterface *ci = dynamic_cast(p); if (ci) { ci->setViInputModeManager(viInputModeManager()); ci->setViGlobal(viInputModeManager()->globalState()); } // The following commands changes the focus themselves, so bar should be hidden before execution. - // We got a range and a valid command, but the command does not inherit the RangeCommand - // extension. Bail out. + // We got a range and a valid command, but the command does not support ranges. if (range.isValid() && !p->supportsRange(cmd)) { commandResponseMessage = i18n("Error: No range allowed for command \"%1\".", cmd); } else { if (p->exec(view(), cmd, commandResponseMessage, range)) { if (commandResponseMessage.length() > 0) { commandResponseMessage = i18n("Success: ") + commandResponseMessage; } } else { if (commandResponseMessage.length() > 0) { if (commandResponseMessage.contains(QLatin1Char('\n'))) { // multiline error, use widget with more space QWhatsThis::showText(emulatedCommandBar()->mapToGlobal(QPoint(0, 0)), commandResponseMessage); } } else { commandResponseMessage = i18n("Command \"%1\" failed.", cmd); } } } } else { commandResponseMessage = i18n("No such command: \"%1\"", cmd); } } // the following commands change the focus themselves if (!QRegExp(QLatin1String("buffer|b|new|vnew|bp|bprev|tabp|tabprev|bn|bnext|tabn|tabnext|bf|bfirst|tabf|tabfirst|bl|blast|tabl|tablast|e|edit|tabe|tabedit|tabnew")).exactMatch(cmd.split(QLatin1Char(' ')).at(0))) { view()->setFocus(); } viInputModeManager()->reset(); return commandResponseMessage; } QString CommandMode::withoutRangeExpression() { const QString originalCommand = m_edit->text(); return originalCommand.mid(rangeExpression().length()); } QString CommandMode::rangeExpression() { const QString command = m_edit->text(); return CommandRangeExpressionParser(viInputModeManager()).parseRangeString(command); } CommandMode::ParsedSedExpression CommandMode::parseAsSedExpression() { const QString commandWithoutRangeExpression = withoutRangeExpression(); ParsedSedExpression parsedSedExpression; QString delimiter; parsedSedExpression.parsedSuccessfully = SedReplace::parse(commandWithoutRangeExpression, delimiter, parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos, parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos); if (parsedSedExpression.parsedSuccessfully) { parsedSedExpression.delimiter = delimiter.at(0); if (parsedSedExpression.replaceBeginPos == -1) { if (parsedSedExpression.findBeginPos != -1) { // The replace term was empty, and a quirk of the regex used is that replaceBeginPos will be -1. // It's actually the position after the first occurrence of the delimiter after the end of the find pos. parsedSedExpression.replaceBeginPos = commandWithoutRangeExpression.indexOf(delimiter, parsedSedExpression.findEndPos) + 1; parsedSedExpression.replaceEndPos = parsedSedExpression.replaceBeginPos - 1; } else { // Both find and replace terms are empty; replace term is at the third occurrence of the delimiter. parsedSedExpression.replaceBeginPos = 0; for (int delimiterCount = 1; delimiterCount <= 3; delimiterCount++) { parsedSedExpression.replaceBeginPos = commandWithoutRangeExpression.indexOf(delimiter, parsedSedExpression.replaceBeginPos + 1); } parsedSedExpression.replaceEndPos = parsedSedExpression.replaceBeginPos - 1; } } if (parsedSedExpression.findBeginPos == -1) { // The find term was empty, and a quirk of the regex used is that findBeginPos will be -1. // It's actually the position after the first occurrence of the delimiter. parsedSedExpression.findBeginPos = commandWithoutRangeExpression.indexOf(delimiter) + 1; parsedSedExpression.findEndPos = parsedSedExpression.findBeginPos - 1; } } if (parsedSedExpression.parsedSuccessfully) { parsedSedExpression.findBeginPos += rangeExpression().length(); parsedSedExpression.findEndPos += rangeExpression().length(); parsedSedExpression.replaceBeginPos += rangeExpression().length(); parsedSedExpression.replaceEndPos += rangeExpression().length(); } return parsedSedExpression; } QString CommandMode::sedFindTerm() { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(parsedSedExpression.findBeginPos, parsedSedExpression.findEndPos - parsedSedExpression.findBeginPos + 1); } QString CommandMode::sedReplaceTerm() { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(parsedSedExpression.replaceBeginPos, parsedSedExpression.replaceEndPos - parsedSedExpression.replaceBeginPos + 1); } QString CommandMode::withSedFindTermReplacedWith ( const QString& newFindTerm ) { const QString command = m_edit->text(); ParsedSedExpression parsedSedExpression = parseAsSedExpression(); Q_ASSERT(parsedSedExpression.parsedSuccessfully); return command.mid(0, parsedSedExpression.findBeginPos) + newFindTerm + command.mid(parsedSedExpression.findEndPos + 1); } QString CommandMode::withSedDelimiterEscaped ( const QString& text ) { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); QString delimiterEscaped = ensuredCharEscaped(text, parsedSedExpression.delimiter); return delimiterEscaped; } bool CommandMode::isCursorInFindTermOfSed() { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return parsedSedExpression.parsedSuccessfully && (m_edit->cursorPosition() >= parsedSedExpression.findBeginPos && m_edit->cursorPosition() <= parsedSedExpression.findEndPos + 1); } bool CommandMode::isCursorInReplaceTermOfSed() { ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return parsedSedExpression.parsedSuccessfully && m_edit->cursorPosition() >= parsedSedExpression.replaceBeginPos && m_edit->cursorPosition() <= parsedSedExpression.replaceEndPos + 1; } int CommandMode::commandBeforeCursorBegin() { const QString textWithoutRangeExpression = withoutRangeExpression(); const int cursorPositionWithoutRangeExpression = m_edit->cursorPosition() - rangeExpression().length(); int commandBeforeCursorBegin = cursorPositionWithoutRangeExpression - 1; while (commandBeforeCursorBegin >= 0 && (textWithoutRangeExpression[commandBeforeCursorBegin].isLetterOrNumber() || textWithoutRangeExpression[commandBeforeCursorBegin] == QLatin1Char('_') || textWithoutRangeExpression[commandBeforeCursorBegin] == QLatin1Char('-'))) { commandBeforeCursorBegin--; } commandBeforeCursorBegin++; commandBeforeCursorBegin += rangeExpression().length(); return commandBeforeCursorBegin; } CompletionStartParams CommandMode::activateCommandCompletion() { return CompletionStartParams::createModeSpecific(m_cmdCompletion.items(), commandBeforeCursorBegin()); } CompletionStartParams CommandMode::activateCommandHistoryCompletion() { return CompletionStartParams::createModeSpecific(reversed(viInputModeManager()->globalState()->commandHistory()->items()), 0); } CompletionStartParams CommandMode::activateSedFindHistoryCompletion() { if (viInputModeManager()->globalState()->searchHistory()->isEmpty()) { return CompletionStartParams::invalid(); } CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return CompletionStartParams::createModeSpecific(reversed(viInputModeManager()->globalState()->searchHistory()->items()), parsedSedExpression.findBeginPos, [this] (const QString& completion) -> QString { return withCaseSensitivityMarkersStripped(withSedDelimiterEscaped(completion)); }); } CompletionStartParams CommandMode::activateSedReplaceHistoryCompletion() { if (viInputModeManager()->globalState()->replaceHistory()->isEmpty()) { return CompletionStartParams::invalid(); } CommandMode::ParsedSedExpression parsedSedExpression = parseAsSedExpression(); return CompletionStartParams::createModeSpecific(reversed(viInputModeManager()->globalState()->replaceHistory()->items()), parsedSedExpression.replaceBeginPos, [this] (const QString& completion) -> QString { return withCaseSensitivityMarkersStripped(withSedDelimiterEscaped(completion)); }); } KTextEditor::Command* CommandMode::queryCommand ( const QString& cmd ) const { // a command can be named ".*[\w\-]+" with the constrain that it must // contain at least one letter. int f = 0; bool b = false; // special case: '-' and '_' can be part of a command name, but if the // command is 's' (substitute), it should be considered the delimiter and // should not be counted as part of the command name if (cmd.length() >= 2 && cmd.at(0) == QLatin1Char('s') && (cmd.at(1) == QLatin1Char('-') || cmd.at(1) == QLatin1Char('_'))) { return m_cmdDict.value(QStringLiteral("s")); } for (; f < cmd.length(); f++) { if (cmd[f].isLetter()) { b = true; } if (b && (! cmd[f].isLetterOrNumber() && cmd[f] != QLatin1Char('-') && cmd[f] != QLatin1Char('_'))) { break; } } return m_cmdDict.value(cmd.left(f)); } diff --git a/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate b/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate index cd491dbd..7d4a5c9d 100644 --- a/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate +++ b/templates/ktexteditor-plugin/ktexteditor-plugin.kdevtemplate @@ -1,65 +1,69 @@ # KDE Config File [General] Name=C++ Name[ast]=C++ Name[ca]=C++ Name[ca@valencia]=C++ Name[cs]=C++ Name[da]=C++ Name[de]=C++ Name[en_GB]=C++ Name[es]=C++ Name[eu]=C++ Name[fi]=C++ Name[fr]=C++ Name[gl]=C++ Name[ia]=C++ Name[it]=C++ +Name[ko]=C++ Name[nb]=C++ Name[nl]=C++ Name[nn]=C++ Name[pl]=C++ Name[pt]=C++ +Name[pt_BR]=C++ Name[ru]=C++ Name[sl]=C++ Name[sr]=Ц++ Name[sr@ijekavian]=Ц++ Name[sr@ijekavianlatin]=C++ Name[sr@latin]=C++ Name[sv]=C++ Name[tr]=C++ Name[uk]=C++ Name[x-test]=xxC++xx Name[zh_CN]=C++ Name[zh_TW]=C++ Comment=Generates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc. -Comment[ca]=Genera un connector en C++ del KTextEditor C++ per portar a terme operacions especials amb el text al KWrite, Kate, KDevelop etc. +Comment[ca]=Genera un connector en C++ del KTextEditor C++ per portar a terme operacions especials amb el text al KWrite, Kate, KDevelop, etc. Comment[ca@valencia]=Genera un connector en C++ del KTextEditor C++ per portar a terme operacions especials amb el text al KWrite, Kate, KDevelop etc. Comment[da]=Genererer en skabelon til et KTextEditor C++-plugin til at udføre særlige handlinger på tekst i KWrite, Kate, KDevelop osv. Comment[de]=Erstellt ein C++-KTextEditor-Module zur Ausführung spezieller Aufgaben an Texten in KWrite, Kate, KDevelop usw. Comment[en_GB]=Generates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc. Comment[es]=Genera un complemento de KTextEditor en C++ para realizar operaciones especiales sobre el texto en KWrite, Kate, KDevelop, etc. Comment[eu]=KTextEditor C++ plugin bat sortzen du testuan eragiketa bereziak egiteko KWrite, Kate, KDevelop eta abarretan. Comment[fi]=Luo C++-kielisen KTextEditor-liitännäisen, jolla voi käsitellä tekstiä KWritessa, Katessa, KDevelopissa jne. Comment[fr]=Génère un module C++ pour KTextEditor pour appliquer des opérations spéciales sur du texte dans KWrite, Kate, KDevelop etc. Comment[gl]=Xera un complemento de KTextEditor en C++ para realizar operacións especiais en texto en KWrite, Kate, KDevelop, etc. Comment[it]=Genera un'estensione KTextEditor C++ per eseguire particolari operazioni sul testo in KWrite, Kate, KDevelop ecc. +Comment[ko]=KWrite, Kate, KDevelop 등에서 텍스트에 작업을 수행하는 KTextEditor C++ 플러그인을 만듭니다. Comment[nl]=Genereert een KTextEditor C++ plug-in om speciale bewerkingen uit te voeren op tekst in KWrite, Kate, KDevelop etc. Comment[nn]=Genererer eit C++-basert programtillegg for KTextEditor som kan utføra spesielle handlingar på tekst i KWrite, Kate, KDevelop osv. Comment[pl]=Tworzy wtyczkę Co KTextEditor-a, umożliwiającej specjalne operacje na tekście w programach KWrite, Kate, KDevelop itp. Comment[pt]=Gera um 'plugin' em C++ do KTextEditor para efectuar operações especiais sobre o texto no KWrite, Kate, KDevelop etc. +Comment[pt_BR]=Gera um plugin do KTextEditor para executar operações especiais no texto no KWrite, Kate, KDevelop, etc. Comment[ru]=Модуль для KTextEditor на языке C++ для добавления новых действий по работе с текстом в KWrite, Kate, KDevelop и другие программы. Comment[sl]=Ustvari vstavek C++ za KTextEditor, ki omogoča izvajanje posebnih dejanj na besedilu v KWrite, Kate, KDevelop, itn. Comment[sr]=Генерише Ц++ прикључак за KTextEditor, за посебне поступке над текстом у К‑писању, Кејт, К‑девелопу итд. Comment[sr@ijekavian]=Генерише Ц++ прикључак за KTextEditor, за посебне поступке над текстом у К‑писању, Кејт, К‑девелопу итд. Comment[sr@ijekavianlatin]=Generiše C++ priključak za KTextEditor, za posebne postupke nad tekstom u K‑pisanju, Kate, KDevelopu itd. Comment[sr@latin]=Generiše C++ priključak za KTextEditor, za posebne postupke nad tekstom u K‑pisanju, Kate, KDevelopu itd. Comment[sv]=Skapar en Ktexteditor C++ insticksmodul för att utföra särskilda åtgärder med text i Kwrite, Kate, KDevlop, etc. Comment[tr]=KWrite, Kate, KDevelop vb. Metin üzerinde özel işlemleri yapmak için bir KTextEditor C++ eklentisi üretir. Comment[uk]=Створює додаток C++ до KTextEditor для виконання спеціальних дій над текстом у KWrite, Kate, KDevelop тощо. Comment[x-test]=xxGenerates a KTextEditor C++ plugin to perform special operations on text in KWrite, Kate, KDevelop etc.xx Comment[zh_CN]=生成一个 KTextEditor c + + 插件在 KWrite、 Kate、 KDevelop 等应用中执行特殊文本操作的文本。 Comment[zh_TW]=生成一個 KTextEditor C++ 外掛程式來對 KWrite、Kate 與 KDevelop 中的文字做出特殊操作 Category=KTextEditor/Plugin Icon=ktexteditor-plugin.png ShowFilesAfterGeneration=%{dest}/README.md