diff --git a/features/backend/files_manager/basic_sequence_files_manager.feature b/features/backend/files_manager/basic_sequence_files_manager.feature new file mode 100644 index 000000000..edf731ddf --- /dev/null +++ b/features/backend/files_manager/basic_sequence_files_manager.feature @@ -0,0 +1,86 @@ +@ProcessMakerMichelangelo @RestAPI +Feature: Files Manager Resources + + Background: + Given that I have a valid access_token + + Scenario: Get a list of main process files manager + Given I request "project/1265557095225ff5c688f46031700471/process-file-manager" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the content type is "application/json" + And the type is "array" + + Scenario: Get a list public folder of process files manager + Given I request "project/1265557095225ff5c688f46031700471/process-file-manager?path=public" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the content type is "application/json" + And the type is "array" + + Scenario: Get a list templates folder of process files manager + Given I request "project/1265557095225ff5c688f46031700471/process-file-manager?path=templates" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the content type is "application/json" + And the type is "array" + + Scenario Outline: Post files + Given POST this data: + """ + { + "file_name": "", + "path": "", + "content": "" + } + """ + And I request "project/1265557095225ff5c688f46031700471/process-file-manager" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the content type is "application/json" + And the type is "" + And store "prf_uid" in session array as variable "prf_uid" + + Examples: + | test_description | file_name | path | content | http_code | type | i | + | into public folder | test.txt | public/ | test | 200 | object | 0 | + | into maintemplates folder | test.txt | templates/ | test | 200 | object | 1 | + | into public subfolder | test.txt | public/test_folder | test | 200 | object | 2 | + | into public subfolder | test.txt | templates/test_folder | test | 200 | object | 3 | + + + + Scenario Outline: Post files + Given PUT this data: + """ + { + "file_name": "", + "content": "" + } + """ + And I request "project/1265557095225ff5c688f46031700471/process-file-manager?path=" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the content type is "application/json" + And the type is "" + + Examples: + | test_description | file_name | path | content | http_code | type | + | put into public folder | test.txt | public/ | put test | 200 | object | + | put into maintemplates folder | test.txt | templates/ | put test | 200 | object | + | put into public subfolder | test.txt | public/test_folder | put test | 200 | object | + | put into public subfolder | test.txt | templates/test_folder | put test | 200 | object | + + Scenario Outline: Delete User + Given that I want to delete a "" + And I request "project/1265557095225ff5c688f46031700471/process-file-manager?path=" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + + Examples: + | test_description | path | + | put into public folder | public/test.txt | + | put into maintemplates folder | templates/test.txt | + | put into public subfolder | public/test_folder/test.txt | + | put into public subfolder | templates/test_folder/test.txt | \ No newline at end of file diff --git a/features/backend/output_documents/main_tests_output.feature b/features/backend/output_documents/main_tests_output.feature index 335198ffd..e00c39f62 100644 --- a/features/backend/output_documents/main_tests_output.feature +++ b/features/backend/output_documents/main_tests_output.feature @@ -17,7 +17,7 @@ Feature: Output Documents Main Tests And the type is "array" And the response has 2 records And the "out_doc_title" property in row 0 equals "Endpoint Old Version (base)" - And the "out_doc_title" property in row 1 equals "Endpoint New Version (base) + And the "out_doc_title" property in row 1 equals "Endpoint New Version (base)" Scenario: Get a single output document of a project diff --git a/features/backend/pm_group/main_tests_pm_group.feature b/features/backend/pm_group/main_tests_pm_group.feature index ac88cbba9..8d5173348 100644 --- a/features/backend/pm_group/main_tests_pm_group.feature +++ b/features/backend/pm_group/main_tests_pm_group.feature @@ -32,8 +32,7 @@ Feature: PM Group Main Tests | Search letters 'de | de | 0 | 5 | 2 | 200 | - - Scenario Outline: Create new Group + Scenario Outline: Create 3 new Groups Given POST this data: """ { @@ -57,7 +56,7 @@ Feature: PM Group Main Tests | 2 | Demo Group3 for main behat | INACTIVE | - Scenario: Get list Groups of workspace + Scenario: Get the Groups list when there are 23 records And I request "groups?filter=&start=0&limit=50" And the content type is "application/json" Then the response status code should be 200 @@ -107,7 +106,103 @@ Feature: PM Group Main Tests | 1 | Update Demo Group2 for main behat | INACTIVE | | 2 | Update Demo Group3 for main behat | ACTIVE | - + + #ASSIGN USER TO GROUP + + Scenario Outline: Get list Users of workspace using different filters for a group + And I request "group/36775342552d5674146d9c2078497230/users?filter=&start=&limit=" + And the content type is "application/json" + Then the response status code should be + And the response charset is "UTF-8" + And the type is "array" + And the response has record + + Examples: + + | test_description | filter | start | limit | record | http_code | + | lowercase | admin | 0 | 9 | 0 | 200 | + | uppercase | ADMIN | 0 | 9 | 0 | 200 | + | limit=3 | a | 0 | 60 | 37 | 200 | + | limit and start | a | 1 | 2 | 2 | 200 | + | high number for start | a | 1000 | 1 | 0 | 200 | + | high number for start | a | 1000 | 0 | 0 | 200 | + | empty result | xyz | 0 | 0 | 0 | 200 | + | empty string | | 0 | 10000 | 43 | 200 | + | empty string | | 1 | 2 | 2 | 200 | + | search 0 | 0 | 0 | 0 | 0 | 200 | + | search 0 | 0 | 0 | 100 | 0 | 200 | + | Search letters 'c' | c | 0 | 40 | 21 | 200 | + | Search letters 'de | de | 0 | 5 | 1 | 200 | + + + Scenario Outline: Assign users to groups created from the endpoint + Given POST this data: + """ + { + "usr_uid": "" + } + """ + And I request "group/grp_uid/user" with the key "grp_uid" stored in session array as variable "grp_uid_" + And the content type is "application/json" + Then the response status code should be 201 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + | grp_uid_number | usr_uid | + | 0 | 00000000000000000000000000000001 | + | 1 | 51049032352d56710347233042615067 | + | 2 | 25286582752d56713231082039265791 | + + + Scenario Outline: List assigned Users to Group + And I request "group/grp_uid/users" with the key "grp_uid" stored in session array as variable "grp_uid_" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "array" + And the "usr_uid" property in row 0 equals "" + And the "usr_username" property in row 0 equals "" + And the "usr_status" property in row 0 equals "" + + Examples: + | grp_uid_number | usr_uid | usr_username | usr_status | + | 0 | 00000000000000000000000000000001 | admin | ACTIVE | + | 1 | 51049032352d56710347233042615067 | aaron | ACTIVE | + | 2 | 25286582752d56713231082039265791 | amy | ACTIVE | + + + Scenario Outline: List available Users to assign to Group + And I request "group/grp_uid/available-users?filter=none" with the key "grp_uid" stored in session array as variable "grp_uid_" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "array" + And the json data is an empty array + + Examples: + | grp_uid_number | + | 0 | + | 1 | + | 2 | + + + Scenario Outline: Unassign User of the Group + Given that I want to delete a resource with the key "" stored in session array + And I request "group/grp_uid/user/" with the key "grp_uid" stored in session array as variable "grp_uid_" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + + | grp_uid_number | usr_uid | + | 0 | 00000000000000000000000000000001 | + | 1 | 51049032352d56710347233042615067 | + | 2 | 25286582752d56713231082039265791 | + + Scenario Outline: Delete all Group created previously in this script Given that I want to delete a resource with the key "grp_uid" stored in session array as variable "grp_uid_" And I request "group" @@ -123,8 +218,6 @@ Feature: PM Group Main Tests | 1 | | 2 | - #GET /api/1.0/{workspace}/groups?filter=abc&start=0&limit=25 - # Get list Groups Scenario: Get list Groups And I request "groups?filter=Update Demo Gro" And the content type is "application/json" diff --git a/features/backend/pm_group/negative_tests_pm_group.feature b/features/backend/pm_group/negative_tests_pm_group.feature index ea12a9647..b4fa63ee4 100644 --- a/features/backend/pm_group/negative_tests_pm_group.feature +++ b/features/backend/pm_group/negative_tests_pm_group.feature @@ -35,8 +35,22 @@ Feature: PM Group Negative Tests Examples: - | grp_title | grp_status | grp_title | error_code | error_message | - | Field requered grp_title | ACTIVE | | 400 | grp_title | - | Field requered grp_status | | test | 400 | grp_status | - - \ No newline at end of file + | grp_title | grp_uid_number | grp_status | grp_title | error_code | error_message | + | Field required grp_title | 1 | ACTIVE | | 400 | grp_title | + | Field required grp_status | 2 | | test | 400 | grp_status | + | Create group with same name | 4 | ACTIVE | Employees | 400 | exists | + + + + + Scenario: Assign users to groups exist in workspace with bad parameters (negative tests) + Given POST this data: + """ + { + "usr_uid": "0000000000000000444500000001" + } + """ + And I request "group/66623507552d56742865613066097298/user" + And the content type is "application/json" + Then the response status code should be 400 + And the response status message should have the following text "usr_uid" \ No newline at end of file diff --git a/features/backend/pm_user/main_tests_pm_user.feature b/features/backend/pm_user/main_tests_pm_user.feature new file mode 100644 index 000000000..67339a3bb --- /dev/null +++ b/features/backend/pm_user/main_tests_pm_user.feature @@ -0,0 +1 @@ +ygyfgy \ No newline at end of file diff --git a/features/backend/pm_user/negative_tests_pm_user.feature b/features/backend/pm_user/negative_tests_pm_user.feature new file mode 100644 index 000000000..692f0135d --- /dev/null +++ b/features/backend/pm_user/negative_tests_pm_user.feature @@ -0,0 +1 @@ +jhjh \ No newline at end of file diff --git a/features/backend/process/basic_sequence_process.feature b/features/backend/process/basic_sequence_process.feature new file mode 100644 index 000000000..5c0d095c4 --- /dev/null +++ b/features/backend/process/basic_sequence_process.feature @@ -0,0 +1,51 @@ +@ProcessMakerMichelangelo @RestAPI +Feature: Process of a Project Resources + Requirements: + a workspace with the process 14414793652a5d718b65590036026581 ("Sample Project #1") already loaded + there are three activities in the process + + Background: + Given that I have a valid access_token + + #GET /api/1.0/{workspace}/project/{prj_uid}/process + # Get a single Process + Scenario Outline: Get a single Process + Given that I want to get a resource with the key "obj_uid" stored in session array + And I request "project/14414793652a5d718b65590036026581/process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + And that "pro_title" is set to "" + And that "pro_description" is set to "" + And that "pro_status" is set to "" + And that "pro_create_user" is set to "" + And that "pro_debug" is set to "" + + Examples: + | pro_title | pro_description | pro_status | pro_create_user | pro_debug | + | Sample Project #1 | | ACTIVE | 00000000000000000000000000000001 | 0 | + + #PUT /api/1.0/{workspace}/project/{prj_uid}/process + # Update Process + Scenario Outline: Update Process + Given PUT this data: + """ + { + "pro_title": "", + "pro_description": "", + "pro_status": "", + "pro_create_user": "", + "pro_debug": + } + """ + And I request "project/14414793652a5d718b65590036026581/process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + | pro_title | pro_description | pro_status | pro_create_user | pro_debug | + | Sample Project #1 | | ACTIVE | 00000000000000000000000000000001 | 0 | + diff --git a/features/backend/process/main_tests_process.feature b/features/backend/process/main_tests_process.feature new file mode 100644 index 000000000..6afd526c3 --- /dev/null +++ b/features/backend/process/main_tests_process.feature @@ -0,0 +1,229 @@ +@ProcessMakerMichelangelo @RestAPI +Feature: Process of a Project Resources + Requirements: + a workspace with the process 79409754952f8f5110c4342001470580 ("Test Process 2") and there are two activities + and workspace with the process 58773281752f50297d6bf00047802053 ("Test Process 1") and there are two activities, in the process already loaded + + + Background: + Given that I have a valid access_token + + Scenario Outline: Get a single Process + Given that I want to get a resource with the key "obj_uid" stored in session array + And I request "project//process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + And that "pro_title" is set to "" + And that "pro_description" is set to "" + And that "pro_parent" is set to "" + And that "pro_time" is set to "" + And that "pro_timeunit" is set to "" + And that "pro_status" is set to "" + And that "pro_type_day" is set to "" + And that "pro_type" is set to "" + And that "pro_assignment" is set to "" + And that "pro_show_map" is set to "" + And that "pro_show_message" is set to "" + And that "pro_subprocess" is set to "" + And that "pro_tri_deleted" is set to "" + And that "pro_tri_canceled" is set to "" + And that "pro_tri_paused" is set to "" + And that "pro_tri_reassigned" is set to "" + And that "pro_show_delegate" is set to "" + And that "pro_show_dynaform" is set to "" + And that "pro_category" is set to "" + And that "pro_sub_category" is set to "" + And that "pro_industry" is set to "" + And that "pro_update_date" is set to "" + And that "pro_create_date" is set to "" + And that "pro_create_user" is set to "" + And that "pro_debug" is set to "" + And that "pro_derivation_screen_tpl" is set to "" + And that "pro_summary_dynaform" is set to "" + And that "pro_calendar" is set to "" + + + Examples: + | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | + | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | null | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | + | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | null | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | + + + Scenario Outline: Update Process + Given PUT this data: + """ + { + "pro_title" : "", + "pro_description" : "", + "pro_parent" : "", + "pro_time" : "", + "pro_timeunit" : "", + "pro_status" : "", + "pro_type_day" : "", + "pro_type" : "", + "pro_assignment" : "", + "pro_show_map" : "", + "pro_show_message" : "", + "pro_subprocess" : "", + "pro_tri_deleted" : "", + "pro_tri_canceled" : "", + "pro_tri_paused" : "", + "pro_tri_reassigned" : "", + "pro_show_delegate" : "", + "pro_show_dynaform" : "", + "pro_category" : "", + "pro_sub_category" : "", + "pro_industry" : "", + "pro_update_date" : "", + "pro_create_date" : "", + "pro_create_user" : "", + "pro_debug" : "", + "pro_derivation_screen_tpl": "", + "pro_summary_dynaform" : "", + "pro_calendar" : "" + } + """ + And I request "project//process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | + | 79409754952f8f5110c4342001470580 | Update Test Process 1 | Update Process - PUT | 79409754952f8f5110c4342001470580 | 1 | DAYS | INACTIVE | | NORMAL | 1 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-01-10 09:43:36 | 2015-12-09 09:43:36 | 00000000000000000000000000000001 | 0 | | | | + | 58773281752f50297d6bf00047802053 | Update Test Process 2 | Update Process - PUT | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 1 | 1 | 1 | 69112537052f503b53142c2026229055 | 30169571352f50349539aa7005920345 | 45413889552f5037587e5a4073302257 | 23429991752f5035c3eab21091451118 | 1 | 0 | 77488943552f502c3d7f649000082980 | | 0 | 2014-01-10 09:43:36 | 2014-02-07 10:58:15 | 51049032352d56710347233042615067 | 1 | tplScreen.html | 94906672952f5058bf3f0f8012616448 | 99159704252f501c63f8c58025859967 | + + + Scenario Outline: Get a single Process + Given that I want to get a resource with the key "obj_uid" stored in session array + And I request "project//process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + And that "pro_title" is set to "" + And that "pro_description" is set to "" + And that "pro_parent" is set to "" + And that "pro_time" is set to "" + And that "pro_timeunit" is set to "" + And that "pro_status" is set to "" + And that "pro_type_day" is set to "" + And that "pro_type" is set to "" + And that "pro_assignment" is set to "" + And that "pro_show_map" is set to "" + And that "pro_show_message" is set to "" + And that "pro_subprocess" is set to "" + And that "pro_tri_deleted" is set to "" + And that "pro_tri_canceled" is set to "" + And that "pro_tri_paused" is set to "" + And that "pro_tri_reassigned" is set to "" + And that "pro_show_delegate" is set to "" + And that "pro_show_dynaform" is set to "" + And that "pro_category" is set to "" + And that "pro_sub_category" is set to "" + And that "pro_industry" is set to "" + And that "pro_update_date" is set to "" + And that "pro_create_date" is set to "" + And that "pro_create_user" is set to "" + And that "pro_debug" is set to "" + And that "pro_derivation_screen_tpl" is set to "" + And that "pro_summary_dynaform" is set to "" + And that "pro_calendar" is set to "" + + + Examples: + | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | + | 79409754952f8f5110c4342001470580 | Update Sample Project #1 | Update Process - PUT | 79409754952f8f5110c4342001470580 | 1 | DAYS | INACTIVE | | NORMAL | 1 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-01-10 09:43:36 | 2015-12-09 09:43:36 | 00000000000000000000000000000001 | 0 | | | | + | 58773281752f50297d6bf00047802053 | Update Test Process | Update Process - PUT | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 1 | 1 | 1 | 69112537052f503b53142c2026229055 | 30169571352f50349539aa7005920345 | 45413889552f5037587e5a4073302257 | 23429991752f5035c3eab21091451118 | 1 | 0 | 77488943552f502c3d7f649000082980 | | 0 | 2014-01-10 09:43:36 | 2014-02-07 10:58:15 | 51049032352d56710347233042615067 | 1 | tplScreen.html | 94906672952f5058bf3f0f8012616448 | 14606161052f50839307899033145440 | + + + Scenario Outline: Update Process + Given PUT this data: + """ + { + "pro_title" : "", + "pro_description" : "", + "pro_parent" : "", + "pro_time" : "", + "pro_timeunit" : "", + "pro_status" : "", + "pro_type_day" : "", + "pro_type" : "", + "pro_assignment" : "", + "pro_show_map" : "", + "pro_show_message" : "", + "pro_subprocess" : "", + "pro_tri_deleted" : "", + "pro_tri_canceled" : "", + "pro_tri_paused" : "", + "pro_tri_reassigned" : "", + "pro_show_delegate" : "", + "pro_show_dynaform" : "", + "pro_category" : "", + "pro_sub_category" : "", + "pro_industry" : "", + "pro_update_date" : "", + "pro_create_date" : "", + "pro_create_user" : "", + "pro_debug" : "", + "pro_derivation_screen_tpl": "", + "pro_summary_dynaform" : "", + "pro_calendar" : "" + } + """ + And I request "project//process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | + | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:49:37 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | + | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:49:37 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | + + + Scenario Outline: Get a single Process + Given that I want to get a resource with the key "obj_uid" stored in session array + And I request "project//process" + And the content type is "application/json" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + And that "pro_title" is set to "" + And that "pro_description" is set to "" + And that "pro_parent" is set to "" + And that "pro_time" is set to "" + And that "pro_timeunit" is set to "" + And that "pro_status" is set to "" + And that "pro_type_day" is set to "" + And that "pro_type" is set to "" + And that "pro_assignment" is set to "" + And that "pro_show_map" is set to "" + And that "pro_show_message" is set to "" + And that "pro_subprocess" is set to "" + And that "pro_tri_deleted" is set to "" + And that "pro_tri_canceled" is set to "" + And that "pro_tri_paused" is set to "" + And that "pro_tri_reassigned" is set to "" + And that "pro_show_delegate" is set to "" + And that "pro_show_dynaform" is set to "" + And that "pro_category" is set to "" + And that "pro_sub_category" is set to "" + And that "pro_industry" is set to "" + And that "pro_update_date" is set to "" + And that "pro_create_date" is set to "" + And that "pro_create_user" is set to "" + And that "pro_debug" is set to "" + And that "pro_derivation_screen_tpl" is set to "" + And that "pro_summary_dynaform" is set to "" + And that "pro_calendar" is set to "" + + + Examples: + | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | + | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | null | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | + | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | null | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | \ No newline at end of file diff --git a/features/backend/process/negative_tests_process.feature b/features/backend/process/negative_tests_process.feature new file mode 100644 index 000000000..f3f953744 --- /dev/null +++ b/features/backend/process/negative_tests_process.feature @@ -0,0 +1,76 @@ +@ProcessMakerMichelangelo @RestAPI +Feature: Process of a Project Resources Negative Tests + +Background: + Given that I have a valid access_token + +Scenario Outline: Update Process + Given PUT this data: + """ + { + "pro_title" : "", + "pro_description" : "", + "pro_parent" : "", + "pro_time" : "", + "pro_timeunit" : "", + "pro_status" : "", + "pro_type_day" : "", + "pro_type" : "", + "pro_assignment" : "", + "pro_show_map" : "", + "pro_show_message" : "", + "pro_subprocess" : "", + "pro_tri_deleted" : "", + "pro_tri_canceled" : "", + "pro_tri_paused" : "", + "pro_tri_reassigned" : "", + "pro_show_delegate" : "", + "pro_show_dynaform" : "", + "pro_category" : "", + "pro_sub_category" : "", + "pro_industry" : "", + "pro_update_date" : "", + "pro_create_date" : "", + "pro_create_user" : "", + "pro_debug" : "", + "pro_derivation_screen_tpl": "", + "pro_summary_dynaform" : "", + "pro_calendar" : "" + } + """ + And I request "project//process" + Then the response status code should be + And the response status message should have the following text "" + + Examples: + | test_description | project | pro_title | pro_description | pro_parent | pro_time | pro_timeunit | pro_status | pro_type_day | pro_type | pro_assignment | pro_show_map | pro_show_message | pro_subprocess | pro_tri_deleted | pro_tri_canceled | pro_tri_paused | pro_tri_reassigned | pro_show_delegate | pro_show_dynaform | pro_category | pro_sub_category | pro_industry | pro_update_date | pro_create_date | pro_create_user | pro_debug | pro_derivation_screen_tpl | pro_summary_dynaform | pro_calendar | error_code | error_message | + | Invalid prj_uid | 79409700000000000004342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | prj_uid | + | Invalid pro_parent | 58773281752f50297d6bf00047802053 | Test Process 1 | | 5877300000000000006bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_parent | + | Invalid pro_time | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 5 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_time | + | Invalid pro_timeunit | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | HOURS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_timeunit | + | Invalid pro_status | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | VALOR | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_status | + | Invalid pro_type | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | INPUT | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_type | + | Invalid pro_assignment | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 4 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_assignment | + | Invalid pro_show_map | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 5 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_show_map | + | Invalid pro_show_message | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 4 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_show_message | + | Invalid pro_subprocess | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 4 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_subprocess | + | Invalid pro_tri_deleted | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | 4541388955000000000e5a4073302257 | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_tri_deleted | + | Invalid pro_tri_canceled | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | 4541380000000000087e5a4073302257 | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_tri_canceled | + | Invalid pro_tri_paused | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | 4541388900000000087e5a4073302257 | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_tri_paused | + | Invalid pro_tri_reassigned | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | 4541380000000000000e5a4073302257 | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_tri_reassigned | + | Invalid pro_show_delegate | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 5 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_show_delegate | + | Invalid pro_show_dynaform | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 8 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_show_dynaform | + | Invalid pro_category | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | 4541380000000000000e5a4073302257 | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_category | + | Invalid pro_industry | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 4 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_industry | + | Invalid pro_create_date | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 01-98-2014 110:49:37 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_create_date | + | Invalid pro_create_user | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 0000000000003551 | 0 | | | | 400 | pro_create_user | + | Invalid pro_debug | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 5 | | | | 400 | pro_debug | + | Invalid pro_summary_dynaform | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | 954104947190420f51086854718 | | 400 | pro_summary_dynaform | + | Invalid pro_calendar | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | 954104947190420f51086854718 | 400 | pro_calendar | + | Field required prj_uid | | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | prj_uid | + | Same Name pro_title | 79409754952f8f5110c4342001470580 | Test | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 200 | | + | Same Name pro_title | 58773281752f50297d6bf00047802053 | Test | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-10 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 400 | pro_title | + | Set initial values | 79409754952f8f5110c4342001470580 | Test Process 2 | | 79409754952f8f5110c4342001470580 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-11 10:58:15 | 2014-02-10 10:49:37 | 00000000000000000000000000000001 | 0 | | | | 200 | | + | Set initial values | 58773281752f50297d6bf00047802053 | Test Process 1 | | 58773281752f50297d6bf00047802053 | 1 | DAYS | ACTIVE | | NORMAL | 0 | 0 | 0 | 0 | | | | | 0 | 0 | | | 0 | 2014-02-11 10:58:15 | 2014-02-07 10:58:15 | 00000000000000000000000000000001 | 0 | | | | 200 | | + + \ No newline at end of file diff --git a/features/backend/report_tables/main_tests_report_tables.feature b/features/backend/report_tables/main_tests_report_tables.feature index ab26fe2d2..1e5135b82 100644 --- a/features/backend/report_tables/main_tests_report_tables.feature +++ b/features/backend/report_tables/main_tests_report_tables.feature @@ -7,8 +7,6 @@ Requirements: Background: Given that I have a valid access_token - - Scenario: Verify that there are no report tables Given I request "project/922677707524ac7417ce345089010125/report-tables" Then the response status code should be 200 @@ -53,9 +51,10 @@ Scenario Outline: Create new report tables from dynaform and grid Examples: - | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | - | | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | - | | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc 2 | workflow | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | + | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | + | Create a Report Table - Normal | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | + | Create a Report Table - Grid | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc 2 | workflow | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | + Scenario: Verify that there are 2 report tables Given I request "project/922677707524ac7417ce345089010125/report-tables" @@ -64,8 +63,6 @@ Scenario: Verify that there are 2 report tables And the response has 2 record - - Scenario Outline: Update a created report tables Given PUT this data: """ @@ -103,9 +100,9 @@ Scenario Outline: Update a created report tables Examples: - | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | - | | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | - | | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | + | Update a Report Table - Normal | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | Update a Report Table - Grid | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | Scenario Outline: Get a details of created report tables @@ -119,9 +116,9 @@ Scenario Outline: Get a details of created report tables Examples: - | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | - | | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | - | | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | grid | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | + | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | grid | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | Scenario Outline: Populate report tables @@ -131,9 +128,9 @@ Scenario Outline: Populate report tables Examples: - | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | - | | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | - | | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | grid | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | + | Populate Report Table Normal | 922677707524ac7417ce345089010125 | 1 | REPORT_TABLE_1 | Report Table Desc Updated 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | + | Populate Report Table Grid | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | Report Table Desc Updated 2 | workflow | GRID | grid | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 150 | @@ -146,9 +143,10 @@ Scenario Outline: Delete a created report tables Examples: - | test_description | project | rep_uid_number | - | | 922677707524ac7417ce345089010125 | 100 | - | | 922677707524ac7417ce345089010125 | 200 | + | test_description | project | rep_uid_number | + | Delete Report Table Normal | 922677707524ac7417ce345089010125 | 1 | + | Delete Report Table Grid | 922677707524ac7417ce345089010125 | 2 | + Scenario: Verify that the report tables were deleted correctly Given I request "project/922677707524ac7417ce345089010125/report-tables" diff --git a/features/backend/report_tables/negative_tests_report_tables.feature b/features/backend/report_tables/negative_tests_report_tables.feature index dfe99092d..709f39240 100644 --- a/features/backend/report_tables/negative_tests_report_tables.feature +++ b/features/backend/report_tables/negative_tests_report_tables.feature @@ -1,5 +1,75 @@ @ProcessMakerMichelangelo @RestAPI -Feature: DataBase Connections +Feature: DataBase Connections Negative Tests Background: Given that I have a valid access_token + +Scenario Outline: Create new report tables from dynaform and grid with bad parameters (negative tests) + Given POST this data: + """ + { + "rep_tab_name" : "", + "rep_tab_dsc" : "", + "rep_tab_connection" : "", + "rep_tab_type" : "", + "rep_tab_grid" : "", + "fields" : [ + { + "fld_dyn" : "", + "fld_name" : "", + "fld_label" : "", + "fld_type" : "", + "fld_size" : "" + },{ + "fld_dyn" : "", + "fld_name" : "", + "fld_label" : "", + "fld_type" : "", + "fld_size" : "" + },{ + "fld_dyn" : "", + "fld_name" : "", + "fld_label" : "", + "fld_type" : "", + "fld_size" : "" + } + ] + } + """ + And I request "project//report-table" + Then the response status code should be + And store "rep_uid" in session array as variable "rep_uid_" + And the response status message should have the following text "" + + + Examples: + + | test_description | project | rep_uid_number | rep_tab_name | rep_tab_dsc | rep_tab_connection | rep_tab_type | rep_tab_grid | fld_dyn_1 | fld_name_1 | fld_label_1 | fld_type_1 | fld_size_1 | fld_dyn_2 | fld_name_2 | fld_label_2 | fld_type_2 | fld_size_2 | fld_dyn_3 | fld_name_3 | fld_label_3 | fld_type_3 | fld_size_3 | error_code | error_message | + | Field required rep_tab_name | 922677707524ac7417ce345089010125 | 1 | | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_name | + | Field required rep_tab_dsc | 922677707524ac7417ce345089010125 | 2 | REPORT_TABLE_2 | | workflow | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_dsc | + | Field required rep_tab_connection | 922677707524ac7417ce345089010125 | 3 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_connection | + | Field required fld_type | 922677707524ac7417ce345089010125 | 4 | REPORT_TABLE_2 | Report Table Desc 2 | workflow | | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | fld_type | + | Field required fld_name | 922677707524ac7417ce345089010125 | 5 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | | Name Any | VARCHAR | 64 | date1 | | Date | DATE | | | | Custom Field 1 | VARCHAR | 15 | 400 | fld_name | + | Field required fld_label | 922677707524ac7417ce345089010125 | 6 | REPORT_TABLE_2 | Report Table Desc 2 | | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | | VARCHAR | 64 | fecha1 | DATE_1 | | DATE | | | CUSTOM_FIELD_1 | | VARCHAR | 15 | 400 | fld_label | + | The name is too short | 922677707524ac7417ce345089010125 | 7 | RE | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | characters | + | Field required Project | 92267000000000000000000089010125 | 8 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | prj_uid | + | Invalid rep_tab_connection | 922677707524ac7417ce345089010125 | 9 | REPORT_TABLE_2 | Report Table Desc 2 | sample | GRID | 267480685524ac9b3bd5e23004484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_connection | + | Invalid rep_tab_type | 922677707524ac7417ce345089010125 | 10 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | INPUT | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_type | + | Invalid fld_type | 922677707524ac7417ce345089010125 | 11 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | SAMPLE | 64 | date1 | DATE_1 | Date | SAMPLE | | | CUSTOM_FIELD_1 | Custom Field 1 | SAMPLE | 15 | 400 | fld_type | + | Invalid fld_size | 922677707524ac7417ce345089010125 | 12 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64,34.55 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 64,34.55 | 400 | fld_size | + | Invalid rep_tab_grid | 922677707524ac7417ce345089010125 | 13 | REPORT_TABLE_2 | Report Table Desc 2 | workflow | GRID | 26748060000000000000000000484669 | text1 | TEXT_1 | Text 1 | VARCHAR | 64 | fecha1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | rep_tab_grid | + | Create same rep_tab_name | 922677707524ac7417ce345089010125 | 14 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 200 | | + | Create same rep_tab_name | 922677707524ac7417ce345089010125 | 15 | REPORT_TABLE_1 | Report Table Desc 1 | workflow | NORMAL | | nameany | NAME_ANY | Name Any | VARCHAR | 64 | date1 | DATE_1 | Date | DATE | | | CUSTOM_FIELD_1 | Custom Field 1 | VARCHAR | 15 | 400 | already exits | + + +Scenario Outline: Delete a created report tables + Given that I want to delete a resource with the key "rep_uid" stored in session array as variable "rep_uid_" + And I request "project//report-table" + Then the response status code should be 200 + And the response charset is "UTF-8" + And the type is "object" + + Examples: + + | test_description | project | rep_uid_number | + | Delete Report Table Normal | 922677707524ac7417ce345089010125 | 14 | \ No newline at end of file diff --git a/gulliver/js/codemirror/.gitattributes b/gulliver/js/codemirror/.gitattributes new file mode 100644 index 000000000..f8bdd60f4 --- /dev/null +++ b/gulliver/js/codemirror/.gitattributes @@ -0,0 +1,8 @@ +*.txt text +*.js text +*.html text +*.md text +*.json text +*.yml text +*.css text +*.svg text diff --git a/gulliver/js/codemirror/.gitignore b/gulliver/js/codemirror/.gitignore new file mode 100644 index 000000000..b471fe6e6 --- /dev/null +++ b/gulliver/js/codemirror/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/npm-debug.log +test.html +.tern-* +*~ +*.swp diff --git a/gulliver/js/codemirror/.travis.yml b/gulliver/js/codemirror/.travis.yml new file mode 100644 index 000000000..baa0031d5 --- /dev/null +++ b/gulliver/js/codemirror/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - 0.8 diff --git a/gulliver/js/codemirror/AUTHORS b/gulliver/js/codemirror/AUTHORS new file mode 100644 index 000000000..737acedab --- /dev/null +++ b/gulliver/js/codemirror/AUTHORS @@ -0,0 +1,300 @@ +List of CodeMirror contributors. Updated before every release. + +4r2r +Aaron Brooks +Abe Fettig +Adam King +adanlobato +Adán Lobato +aeroson +Ahmad Amireh +Ahmad M. Zawawi +ahoward +Akeksandr Motsjonov +Alberto Pose +Albert Xing +Alexander Pavlov +Alexander Schepanovski +Alexander Solovyov +alexey-k +Alex Piggott +Amy +Ananya Sen +AndersMad +Anderson Mesquita +Andre von Houck +Andrey Lushnikov +Andy Joslin +Andy Kimball +Andy Li +angelozerr +angelo.zerr@gmail.com +Ankit Ahuja +Ansel Santosa +Anthony Grimes +Anton Kovalyov +areos +AtomicPages LLC +Atul Bhouraskar +Aurelian Oancea +Bastian Müller +benbro +Beni Cherniavsky-Paskin +Benjamin DeCoste +Ben Keen +boomyjee +borawjm +Brandon Frohs +Brandon Wamboldt +Brett Zamir +Brian Sletten +Bruce Mitchener +Chandra Sekhar Pydi +Charles Skelton +Chris Coyier +Chris Granger +Chris Morgan +Christopher Brown +ciaranj +CodeAnimal +ComFreek +dagsta +Dan Heberden +Daniel, Dao Quang Minh +Daniel Faust +Daniel Huigens +Daniel KJ +Daniel Neel +Daniel Parnell +Danny Yoo +Darius Roberts +David Mignot +David Pathakjee +deebugger +Deep Thought +Dominator008 +Domizio Demichelis +Drew Bratcher +Drew Hintz +Drew Khoury +Dror BG +duralog +eborden +edsharp +ekhaled +Enam Mijbah Noor +Eric Allam +eustas +Fabio Zendhi Nagao +Fauntleroy +fbuchinger +feizhang365 +Felipe Lalanne +Felix Raab +Filip Noetzel +flack +ForbesLindesay +Forbes Lindesay +Ford_Lawnmower +Gabriel Nahmias +galambalazs +Gautam Mehta +Glenn Jorde +Glenn Ruehle +Golevka +Gordon Smith +greengiant +Guillaume Massé +Guillaume Massé +Hans Engel +Hardest +Hasan Karahan +hitsthings +Hocdoc +Ian Beck +Ian Dickinson +Ian Wehrman +Ian Wetherbee +Ice White +ICHIKAWA, Yuji +ilvalle +Ingo Richter +Irakli Gozalishvili +Ivan Kurnosov +Jacob Lee +Jakub Vrana +James Campos +James Thorne +Jamie Hill +Jan Jongboom +jankeromnes +Jan Keromnes +Jan Odvarko +Jan T. Sott +Jason +Jason Grout +Jason Johnston +Jason San Jose +Jason Siefken +Jean Boussier +jeffkenton +Jeff Pickhardt +jem (graphite) +Jochen Berger +Johan Ask +John Connor +John Lees-Miller +John Snelson +John Van Der Loo +jongalloway +Jon Malmaud +Joost-Wim Boekesteijn +Joseph Pecoraro +Joshua Newman +jots +jsoojeon +Juan Benavides Romero +Jucovschi Constantin +Juho Vuori +jwallers@gmail.com +kaniga +Ken Newman +Ken Rockot +Kevin Sawicki +Klaus Silveira +Koh Zi Han, Cliff +komakino +Konstantin Lopuhin +koops +ks-ifware +kubelsmieci +Lanny +Laszlo Vidacs +leaf corcoran +Leonya Khachaturov +Liam Newman +LM +Lorenzo Stoakes +Luciano Longo +lynschinzer +Maksim Lin +Maksym Taran +Marat Dreizin +Marco Aurélio +Marijn Haverbeke +Mario Pietsch +Mark Lentczner +Marko Bonaci +Martin Balek +Martín Gaitán +Martin Hasoň +Mason Malone +Mateusz Paprocki +mats cronqvist +Matthew Beale +Matthias BUSSONNIER +Matt McDonald +Matt Pass +Matt Sacks +Maximilian Hils +Maxim Kraev +Max Kirsch +mbarkhau +Metatheos +Micah Dubinko +Michael Lehenbauer +Michael Zhou +Mighty Guava +Miguel Castillo +Mike +Mike Brevoort +Mike Diaz +Mike Ivanov +Mike Kadin +MinRK +Miraculix87 +misfo +mloginov +mps +Narciso Jaramillo +Nathan Williams +nerbert +nguillaumin +Niels van Groningen +Nikita Beloglazov +Nikita Vasilyev +Nikolay Kostov +nlwillia +pablo +Page +Patrick Strawderman +Paul Garvin +Paul Ivanov +Pavel Feldman +Pavel Strashkin +Paweł Bartkiewicz +peteguhl +peterkroon +Peter Kroon +prasanthj +Prasanth J +Rahul +Randy Edmunds +Richard Z.H. Wang +robertop23 +Robert Plummer +Ruslan Osmanov +sabaca +Samuel Ainsworth +sandeepshetty +santec +Sascha Peilicke +satchmorun +sathyamoorthi +SCLINIC\jdecker +Sebastian Zaha +shaund +shaun gilchrist +Shawn A +Shiv Deepak +Shmuel Englard +soliton4 +sonson +spastorelli +Stanislav Oaserele +Stas Kobzar +Stefan Borsje +Steffen Beyer +Steve O'Hara +stoskov +Taha Jahangir +Tarmil +tfjgeorge +Thaddee Tyl +think +Thomas Dvornik +Thomas Schmid +Tim Baumann +Timothy Farrell +Timothy Hatcher +TobiasBg +Tomas-A +Tomas Varaneckas +Tom Erik Støwer +Tom MacWright +Tony Jian +Travis Heppe +Vestimir Markov +vf +Volker Mische +wenli +Wesley Wiser +William Jamieson +Wojtek Ptak +Xavier Mendez +YNH Webdev +Yunchi Luo +Yuvi Panda +Zachary Dremann +zziuni +魏鹏刚 diff --git a/gulliver/js/codemirror/CONTRIBUTING.md b/gulliver/js/codemirror/CONTRIBUTING.md index afc18373c..8938f6204 100644 --- a/gulliver/js/codemirror/CONTRIBUTING.md +++ b/gulliver/js/codemirror/CONTRIBUTING.md @@ -4,12 +4,12 @@ - [Submitting bug reports](#submitting-bug-reports-) - [Contributing code](#contributing-code-) -## Getting help [^](#how-to-contribute) +## Getting help Community discussion, questions, and informal bug reporting is done on the [CodeMirror Google group](http://groups.google.com/group/codemirror). -## Submitting bug reports [^](#how-to-contribute) +## Submitting bug reports The preferred way to report bugs is to use the [GitHub issue tracker](http://github.com/marijnh/CodeMirror/issues). Before @@ -45,7 +45,7 @@ should be asked on the [jsbin.com](http://jsbin.com/ihunin/edit), enter it there, press save, and include the resulting link in your bug report. -## Contributing code [^](#how-to-contribute) +## Contributing code - Make sure you have a [GitHub Account](https://github.com/signup/free) - Fork [CodeMirror](https://github.com/marijnh/CodeMirror/) @@ -56,6 +56,8 @@ should be asked on the test suite under `mode/XXX/test.js`. Feel free to add new test suites to modes that don't have one yet (be sure to link the new tests into `test/index.html`). +- Follow the general code style of the rest of the project (see + below). Run `bin/lint` to verify that the linter is happy. - Make sure all tests pass. Visit `test/index.html` in your browser to run them. - Submit a pull request @@ -65,6 +67,6 @@ should be asked on the - 2 spaces per indentation level, no tabs. - Include semicolons after statements. -- Note that the linter (`test/lint/lint.js`) which is run after each - commit complains about unused variables and functions. Prefix their - names with an underscore to muffle it. +- Note that the linter (`bin/lint`) which is run after each commit + complains about unused variables and functions. Prefix their names + with an underscore to muffle it. diff --git a/gulliver/js/codemirror/LICENSE b/gulliver/js/codemirror/LICENSE index 482d55eb7..442d11cdc 100644 --- a/gulliver/js/codemirror/LICENSE +++ b/gulliver/js/codemirror/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2013 by Marijn Haverbeke +Copyright (C) 2013 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -17,7 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Please note that some subdirectories of the CodeMirror distribution -include their own LICENSE files, and are released under different -licences. diff --git a/gulliver/js/codemirror/README.md b/gulliver/js/codemirror/README.md index 976584e3b..61f6b6452 100644 --- a/gulliver/js/codemirror/README.md +++ b/gulliver/js/codemirror/README.md @@ -1,4 +1,6 @@ -# CodeMirror [![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror) +# CodeMirror +[![Build Status](https://secure.travis-ci.org/marijnh/CodeMirror.png?branch=master)](http://travis-ci.org/marijnh/CodeMirror) +[![NPM version](https://badge.fury.io/js/codemirror.png)](http://badge.fury.io/js/codemirror) CodeMirror is a JavaScript component that provides a code editor in the browser. When a mode is available for the language you are coding diff --git a/gulliver/js/codemirror/addon/comment/comment.js b/gulliver/js/codemirror/addon/comment/comment.js new file mode 100644 index 000000000..5975b0bf6 --- /dev/null +++ b/gulliver/js/codemirror/addon/comment/comment.js @@ -0,0 +1,149 @@ +(function() { + "use strict"; + + var noOptions = {}; + var nonWS = /[^\s\u00a0]/; + var Pos = CodeMirror.Pos; + + function firstNonWS(str) { + var found = str.search(nonWS); + return found == -1 ? 0 : found; + } + + CodeMirror.commands.toggleComment = function(cm) { + var from = cm.getCursor("start"), to = cm.getCursor("end"); + cm.uncomment(from, to) || cm.lineComment(from, to); + }; + + CodeMirror.defineExtension("lineComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = self.getModeAt(from); + var commentString = options.lineComment || mode.lineComment; + if (!commentString) { + if (options.blockCommentStart || mode.blockCommentStart) { + options.fullLines = true; + self.blockComment(from, to, options); + } + return; + } + var firstLine = self.getLine(from.line); + if (firstLine == null) return; + var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1); + var pad = options.padding == null ? " " : options.padding; + var blankLines = options.commentBlankLines || from.line == to.line; + + self.operation(function() { + if (options.indent) { + var baseString = firstLine.slice(0, firstNonWS(firstLine)); + for (var i = from.line; i < end; ++i) { + var line = self.getLine(i), cut = baseString.length; + if (!blankLines && !nonWS.test(line)) continue; + if (line.slice(0, cut) != baseString) cut = firstNonWS(line); + self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut)); + } + } else { + for (var i = from.line; i < end; ++i) { + if (blankLines || nonWS.test(self.getLine(i))) + self.replaceRange(commentString + pad, Pos(i, 0)); + } + } + }); + }); + + CodeMirror.defineExtension("blockComment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = self.getModeAt(from); + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) { + if ((options.lineComment || mode.lineComment) && options.fullLines != false) + self.lineComment(from, to, options); + return; + } + + var end = Math.min(to.line, self.lastLine()); + if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end; + + var pad = options.padding == null ? " " : options.padding; + if (from.line > end) return; + + self.operation(function() { + if (options.fullLines != false) { + var lastLineHasText = nonWS.test(self.getLine(end)); + self.replaceRange(pad + endString, Pos(end)); + self.replaceRange(startString + pad, Pos(from.line, 0)); + var lead = options.blockCommentLead || mode.blockCommentLead; + if (lead != null) for (var i = from.line + 1; i <= end; ++i) + if (i != end || lastLineHasText) + self.replaceRange(lead + pad, Pos(i, 0)); + } else { + self.replaceRange(endString, to); + self.replaceRange(startString, from); + } + }); + }); + + CodeMirror.defineExtension("uncomment", function(from, to, options) { + if (!options) options = noOptions; + var self = this, mode = self.getModeAt(from); + var end = Math.min(to.line, self.lastLine()), start = Math.min(from.line, end); + + // Try finding line comments + var lineString = options.lineComment || mode.lineComment, lines = []; + var pad = options.padding == null ? " " : options.padding, didSomething; + lineComment: { + if (!lineString) break lineComment; + for (var i = start; i <= end; ++i) { + var line = self.getLine(i); + var found = line.indexOf(lineString); + if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1; + if (found == -1 && (i != end || i == start) && nonWS.test(line)) break lineComment; + if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment; + lines.push(line); + } + self.operation(function() { + for (var i = start; i <= end; ++i) { + var line = lines[i - start]; + var pos = line.indexOf(lineString), endPos = pos + lineString.length; + if (pos < 0) continue; + if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length; + didSomething = true; + self.replaceRange("", Pos(i, pos), Pos(i, endPos)); + } + }); + if (didSomething) return true; + } + + // Try block comments + var startString = options.blockCommentStart || mode.blockCommentStart; + var endString = options.blockCommentEnd || mode.blockCommentEnd; + if (!startString || !endString) return false; + var lead = options.blockCommentLead || mode.blockCommentLead; + var startLine = self.getLine(start), endLine = end == start ? startLine : self.getLine(end); + var open = startLine.indexOf(startString), close = endLine.lastIndexOf(endString); + if (close == -1 && start != end) { + endLine = self.getLine(--end); + close = endLine.lastIndexOf(endString); + } + if (open == -1 || close == -1 || + !/comment/.test(self.getTokenTypeAt(Pos(start, open + 1))) || + !/comment/.test(self.getTokenTypeAt(Pos(end, close + 1)))) + return false; + + self.operation(function() { + self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)), + Pos(end, close + endString.length)); + var openEnd = open + startString.length; + if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length; + self.replaceRange("", Pos(start, open), Pos(start, openEnd)); + if (lead) for (var i = start + 1; i <= end; ++i) { + var line = self.getLine(i), found = line.indexOf(lead); + if (found == -1 || nonWS.test(line.slice(0, found))) continue; + var foundEnd = found + lead.length; + if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length; + self.replaceRange("", Pos(i, found), Pos(i, foundEnd)); + } + }); + return true; + }); +})(); diff --git a/gulliver/js/codemirror/addon/comment/continuecomment.js b/gulliver/js/codemirror/addon/comment/continuecomment.js new file mode 100644 index 000000000..a3370a607 --- /dev/null +++ b/gulliver/js/codemirror/addon/comment/continuecomment.js @@ -0,0 +1,54 @@ +(function() { + var modes = ["clike", "css", "javascript"]; + for (var i = 0; i < modes.length; ++i) + CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "}); + + function continueComment(cm) { + var pos = cm.getCursor(), token = cm.getTokenAt(pos); + if (token.type != "comment" || cm.getOption("disableInput")) return CodeMirror.Pass; + var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; + + var insert; + if (mode.blockCommentStart && mode.blockCommentContinue) { + var end = token.string.indexOf(mode.blockCommentEnd); + var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; + if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) { + // Comment ended, don't continue it + } else if (token.string.indexOf(mode.blockCommentStart) == 0) { + insert = full.slice(0, token.start); + if (!/^\s*$/.test(insert)) { + insert = ""; + for (var i = 0; i < token.start; ++i) insert += " "; + } + } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && + found + mode.blockCommentContinue.length > token.start && + /^\s*$/.test(full.slice(0, found))) { + insert = full.slice(0, found); + } + if (insert != null) insert += mode.blockCommentContinue; + } + if (insert == null && mode.lineComment) { + var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment); + if (found > -1) { + insert = line.slice(0, found); + if (/\S/.test(insert)) insert = null; + else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0]; + } + } + + if (insert != null) + cm.replaceSelection("\n" + insert, "end"); + else + return CodeMirror.Pass; + } + + CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { + if (prev && prev != CodeMirror.Init) + cm.removeKeyMap("continueComment"); + if (val) { + var map = {name: "continueComment"}; + map[typeof val == "string" ? val : "Enter"] = continueComment; + cm.addKeyMap(map); + } + }); +})(); diff --git a/gulliver/js/codemirror/addon/dialog/dialog.js b/gulliver/js/codemirror/addon/dialog/dialog.js index 71e228744..41d7bf866 100644 --- a/gulliver/js/codemirror/addon/dialog/dialog.js +++ b/gulliver/js/codemirror/addon/dialog/dialog.js @@ -10,11 +10,22 @@ } else { dialog.className = "CodeMirror-dialog CodeMirror-dialog-top"; } - dialog.innerHTML = template; + if (typeof template == "string") { + dialog.innerHTML = template; + } else { // Assuming it's a detached DOM element. + dialog.appendChild(template); + } return dialog; } + function closeNotification(cm, newVal) { + if (cm.state.currentNotificationClose) + cm.state.currentNotificationClose(); + cm.state.currentNotificationClose = newVal; + } + CodeMirror.defineExtension("openDialog", function(template, callback, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var closed = false, me = this; function close() { @@ -24,6 +35,7 @@ } var inp = dialog.getElementsByTagName("input")[0], button; if (inp) { + if (options && options.value) inp.value = options.value; CodeMirror.on(inp, "keydown", function(e) { if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; } if (e.keyCode == 13 || e.keyCode == 27) { @@ -51,6 +63,7 @@ }); CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) { + closeNotification(this, null); var dialog = dialogDiv(this, template, options && options.bottom); var buttons = dialog.getElementsByTagName("button"); var closed = false, me = this, blurring = 1; @@ -77,4 +90,33 @@ CodeMirror.on(b, "focus", function() { ++blurring; }); } }); + + /* + * openNotification + * Opens a notification, that can be closed with an optional timer + * (default 5000ms timer) and always closes on click. + * + * If a notification is opened while another is opened, it will close the + * currently opened one and open the new one immediately. + */ + CodeMirror.defineExtension("openNotification", function(template, options) { + closeNotification(this, close); + var dialog = dialogDiv(this, template, options && options.bottom); + var duration = options && (options.duration === undefined ? 5000 : options.duration); + var closed = false, doneTimer; + + function close() { + if (closed) return; + closed = true; + clearTimeout(doneTimer); + dialog.parentNode.removeChild(dialog); + } + + CodeMirror.on(dialog, 'click', function(e) { + CodeMirror.e_preventDefault(e); + close(); + }); + if (duration) + doneTimer = setTimeout(close, options.duration); + }); })(); diff --git a/gulliver/js/codemirror/addon/display/fullscreen.css b/gulliver/js/codemirror/addon/display/fullscreen.css new file mode 100644 index 000000000..437acd89b --- /dev/null +++ b/gulliver/js/codemirror/addon/display/fullscreen.css @@ -0,0 +1,6 @@ +.CodeMirror-fullscreen { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + height: auto; + z-index: 9; +} diff --git a/gulliver/js/codemirror/addon/display/fullscreen.js b/gulliver/js/codemirror/addon/display/fullscreen.js new file mode 100644 index 000000000..a442f6a43 --- /dev/null +++ b/gulliver/js/codemirror/addon/display/fullscreen.js @@ -0,0 +1,31 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("fullScreen", false, function(cm, val, old) { + if (old == CodeMirror.Init) old = false; + if (!old == !val) return; + if (val) setFullscreen(cm); + else setNormal(cm); + }); + + function setFullscreen(cm) { + var wrap = cm.getWrapperElement(); + cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset, + width: wrap.style.width, height: wrap.style.height}; + wrap.style.width = ""; + wrap.style.height = "auto"; + wrap.className += " CodeMirror-fullscreen"; + document.documentElement.style.overflow = "hidden"; + cm.refresh(); + } + + function setNormal(cm) { + var wrap = cm.getWrapperElement(); + wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, ""); + document.documentElement.style.overflow = ""; + var info = cm.state.fullScreenRestore; + wrap.style.width = info.width; wrap.style.height = info.height; + window.scrollTo(info.scrollLeft, info.scrollTop); + cm.refresh(); + } +})(); diff --git a/gulliver/js/codemirror/addon/display/placeholder.js b/gulliver/js/codemirror/addon/display/placeholder.js index f85f2df12..748afe727 100644 --- a/gulliver/js/codemirror/addon/display/placeholder.js +++ b/gulliver/js/codemirror/addon/display/placeholder.js @@ -2,12 +2,10 @@ CodeMirror.defineOption("placeholder", "", function(cm, val, old) { var prev = old && old != CodeMirror.Init; if (val && !prev) { - cm.on("focus", onFocus); cm.on("blur", onBlur); cm.on("change", onChange); onChange(cm); } else if (!val && prev) { - cm.off("focus", onFocus); cm.off("blur", onBlur); cm.off("change", onChange); clearPlaceholder(cm); @@ -19,23 +17,20 @@ }); function clearPlaceholder(cm) { - if (cm._placeholder) { - cm._placeholder.parentNode.removeChild(cm._placeholder); - cm._placeholder = null; + if (cm.state.placeholder) { + cm.state.placeholder.parentNode.removeChild(cm.state.placeholder); + cm.state.placeholder = null; } } function setPlaceholder(cm) { clearPlaceholder(cm); - var elt = cm._placeholder = document.createElement("pre"); + var elt = cm.state.placeholder = document.createElement("pre"); elt.style.cssText = "height: 0; overflow: visible"; elt.className = "CodeMirror-placeholder"; elt.appendChild(document.createTextNode(cm.getOption("placeholder"))); cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild); } - function onFocus(cm) { - clearPlaceholder(cm); - } function onBlur(cm) { if (isEmpty(cm)) setPlaceholder(cm); } @@ -43,7 +38,6 @@ var wrapper = cm.getWrapperElement(), empty = isEmpty(cm); wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : ""); - if (cm.hasFocus()) return; if (empty) setPlaceholder(cm); else clearPlaceholder(cm); } diff --git a/gulliver/js/codemirror/addon/edit/closebrackets.js b/gulliver/js/codemirror/addon/edit/closebrackets.js index b46caca02..0575222be 100644 --- a/gulliver/js/codemirror/addon/edit/closebrackets.js +++ b/gulliver/js/codemirror/addon/edit/closebrackets.js @@ -1,34 +1,47 @@ (function() { var DEFAULT_BRACKETS = "()[]{}''\"\""; + var DEFAULT_EXPLODE_ON_ENTER = "[]{}"; var SPACE_CHAR_REGEX = /\s/; CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) { - var wasOn = old && old != CodeMirror.Init; - if (val && !wasOn) - cm.addKeyMap(buildKeymap(typeof val == "string" ? val : DEFAULT_BRACKETS)); - else if (!val && wasOn) + if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseBrackets"); + if (!val) return; + var pairs = DEFAULT_BRACKETS, explode = DEFAULT_EXPLODE_ON_ENTER; + if (typeof val == "string") pairs = val; + else if (typeof val == "object") { + if (val.pairs != null) pairs = val.pairs; + if (val.explode != null) explode = val.explode; + } + var map = buildKeymap(pairs); + if (explode) map.Enter = buildExplodeHandler(explode); + cm.addKeyMap(map); }); + function charsAround(cm, pos) { + var str = cm.getRange(CodeMirror.Pos(pos.line, pos.ch - 1), + CodeMirror.Pos(pos.line, pos.ch + 1)); + return str.length == 2 ? str : null; + } + function buildKeymap(pairs) { var map = { name : "autoCloseBrackets", Backspace: function(cm) { - if (cm.somethingSelected()) return CodeMirror.Pass; - var cur = cm.getCursor(), line = cm.getLine(cur.line); - if (cur.ch && cur.ch < line.length && - pairs.indexOf(line.slice(cur.ch - 1, cur.ch + 1)) % 2 == 0) + if (cm.somethingSelected() || cm.getOption("disableInput")) return CodeMirror.Pass; + var cur = cm.getCursor(), around = charsAround(cm, cur); + if (around && pairs.indexOf(around) % 2 == 0) cm.replaceRange("", CodeMirror.Pos(cur.line, cur.ch - 1), CodeMirror.Pos(cur.line, cur.ch + 1)); else return CodeMirror.Pass; } }; - var closingBrackets = []; + var closingBrackets = ""; for (var i = 0; i < pairs.length; i += 2) (function(left, right) { - if (left != right) closingBrackets.push(right); + if (left != right) closingBrackets += right; function surround(cm) { - var selection = cm.getSelection(); - cm.replaceSelection(left + selection + right); + var selection = cm.getSelection(); + cm.replaceSelection(left + selection + right); } function maybeOverwrite(cm) { var cur = cm.getCursor(), ahead = cm.getRange(cur, CodeMirror.Pos(cur.line, cur.ch + 1)); @@ -36,10 +49,15 @@ else cm.execCommand("goCharRight"); } map["'" + left + "'"] = function(cm) { + if (left == "'" && cm.getTokenAt(cm.getCursor()).type == "comment" || + cm.getOption("disableInput")) + return CodeMirror.Pass; if (cm.somethingSelected()) return surround(cm); if (left == right && maybeOverwrite(cm) != CodeMirror.Pass) return; var cur = cm.getCursor(), ahead = CodeMirror.Pos(cur.line, cur.ch + 1); - var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch); + var line = cm.getLine(cur.line), nextChar = line.charAt(cur.ch), curChar = cur.ch > 0 ? line.charAt(cur.ch - 1) : ""; + if (left == right && CodeMirror.isWordChar(curChar)) + return CodeMirror.Pass; if (line.length == cur.ch || closingBrackets.indexOf(nextChar) >= 0 || SPACE_CHAR_REGEX.test(nextChar)) cm.replaceSelection(left + right, {head: ahead, anchor: ahead}); else @@ -49,4 +67,18 @@ })(pairs.charAt(i), pairs.charAt(i + 1)); return map; } + + function buildExplodeHandler(pairs) { + return function(cm) { + var cur = cm.getCursor(), around = charsAround(cm, cur); + if (!around || pairs.indexOf(around) % 2 != 0 || cm.getOption("disableInput")) + return CodeMirror.Pass; + cm.operation(function() { + var newPos = CodeMirror.Pos(cur.line + 1, 0); + cm.replaceSelection("\n\n", {anchor: newPos, head: newPos}, "+input"); + cm.indentLine(cur.line + 1, null, true); + cm.indentLine(cur.line + 2, null, true); + }); + }; + } })(); diff --git a/gulliver/js/codemirror/addon/edit/closetag.js b/gulliver/js/codemirror/addon/edit/closetag.js index 54fa19546..cad776a78 100644 --- a/gulliver/js/codemirror/addon/edit/closetag.js +++ b/gulliver/js/codemirror/addon/edit/closetag.js @@ -24,16 +24,15 @@ (function() { CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) { - if (val && (old == CodeMirror.Init || !old)) { - var map = {name: "autoCloseTags"}; - if (typeof val != "object" || val.whenClosing) - map["'/'"] = function(cm) { return autoCloseTag(cm, '/'); }; - if (typeof val != "object" || val.whenOpening) - map["'>'"] = function(cm) { return autoCloseTag(cm, '>'); }; - cm.addKeyMap(map); - } else if (!val && (old != CodeMirror.Init && old)) { + if (old != CodeMirror.Init && old) cm.removeKeyMap("autoCloseTags"); - } + if (!val) return; + var map = {name: "autoCloseTags"}; + if (typeof val != "object" || val.whenClosing) + map["'/'"] = function(cm) { return autoCloseSlash(cm); }; + if (typeof val != "object" || val.whenOpening) + map["'>'"] = function(cm) { return autoCloseGT(cm); }; + cm.addKeyMap(map); }); var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", @@ -41,39 +40,48 @@ var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"]; - function autoCloseTag(cm, ch) { + function autoCloseGT(cm) { var pos = cm.getCursor(), tok = cm.getTokenAt(pos); var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; - if (inner.mode.name != "xml") return CodeMirror.Pass; + if (inner.mode.name != "xml" || !state.tagName || cm.getOption("disableInput")) return CodeMirror.Pass; var opt = cm.getOption("autoCloseTags"), html = inner.mode.configuration == "html"; var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose); var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent); - if (ch == ">" && state.tagName) { - var tagName = state.tagName; - if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); - var lowerTagName = tagName.toLowerCase(); - // Don't process the '>' at the end of an end-tag or self-closing tag - if (tok.type == "tag" && state.type == "closeTag" || tok.string.indexOf("/") > -1 || - dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1) - return CodeMirror.Pass; + var tagName = state.tagName; + if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch); + var lowerTagName = tagName.toLowerCase(); + // Don't process the '>' at the end of an end-tag or self-closing tag + if (!tagName || + tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) || + tok.type == "tag" && state.type == "closeTag" || + tok.string.indexOf("/") == (tok.string.length - 1) || // match something like + dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 || + CodeMirror.scanForClosingTag && CodeMirror.scanForClosingTag(cm, pos, tagName, + Math.min(cm.lastLine() + 1, pos.line + 50))) + return CodeMirror.Pass; - var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1; - var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1); - cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "", - {head: curPos, anchor: curPos}); - if (doIndent) { - cm.indentLine(pos.line + 1); - cm.indentLine(pos.line + 2); - } - return; - } else if (ch == "/" && tok.string == "<") { - var tagName = state.context && state.context.tagName; - if (tagName) cm.replaceSelection("/" + tagName + ">", "end"); - return; + var doIndent = indentTags && indexOf(indentTags, lowerTagName) > -1; + var curPos = doIndent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1); + cm.replaceSelection(">" + (doIndent ? "\n\n" : "") + "", + {head: curPos, anchor: curPos}); + if (doIndent) { + cm.indentLine(pos.line + 1, null, true); + cm.indentLine(pos.line + 2, null); } - return CodeMirror.Pass; + } + + function autoCloseSlash(cm) { + var pos = cm.getCursor(), tok = cm.getTokenAt(pos); + var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state; + if (tok.type == "string" || tok.string.charAt(0) != "<" || + tok.start != pos.ch - 1 || inner.mode.name != "xml" || + cm.getOption("disableInput")) + return CodeMirror.Pass; + + var tagName = state.context && state.context.tagName; + if (tagName) cm.replaceSelection("/" + tagName + ">", "end"); } function indexOf(collection, elt) { diff --git a/gulliver/js/codemirror/addon/edit/continuecomment.js b/gulliver/js/codemirror/addon/edit/continuecomment.js deleted file mode 100644 index 308026229..000000000 --- a/gulliver/js/codemirror/addon/edit/continuecomment.js +++ /dev/null @@ -1,44 +0,0 @@ -(function() { - var modes = ["clike", "css", "javascript"]; - for (var i = 0; i < modes.length; ++i) - CodeMirror.extendMode(modes[i], {blockCommentStart: "/*", - blockCommentEnd: "*/", - blockCommentContinue: " * "}); - - function continueComment(cm) { - var pos = cm.getCursor(), token = cm.getTokenAt(pos); - var mode = CodeMirror.innerMode(cm.getMode(), token.state).mode; - var space; - - if (token.type == "comment" && mode.blockCommentStart) { - var end = token.string.indexOf(mode.blockCommentEnd); - var full = cm.getRange(CodeMirror.Pos(pos.line, 0), CodeMirror.Pos(pos.line, token.end)), found; - if (end != -1 && end == token.string.length - mode.blockCommentEnd.length) { - // Comment ended, don't continue it - } else if (token.string.indexOf(mode.blockCommentStart) == 0) { - space = full.slice(0, token.start); - if (!/^\s*$/.test(space)) { - space = ""; - for (var i = 0; i < token.start; ++i) space += " "; - } - } else if ((found = full.indexOf(mode.blockCommentContinue)) != -1 && - found + mode.blockCommentContinue.length > token.start && - /^\s*$/.test(full.slice(0, found))) { - space = full.slice(0, found); - } - } - - if (space != null) - cm.replaceSelection("\n" + space + mode.blockCommentContinue, "end"); - else - return CodeMirror.Pass; - } - - CodeMirror.defineOption("continueComments", null, function(cm, val, prev) { - if (prev && prev != CodeMirror.Init) - cm.removeKeyMap("continueComment"); - var map = {name: "continueComment"}; - map[typeof val == "string" ? val : "Enter"] = continueComment; - cm.addKeyMap(map); - }); -})(); diff --git a/gulliver/js/codemirror/addon/edit/continuelist.js b/gulliver/js/codemirror/addon/edit/continuelist.js index fb1fc38ba..190b41dcb 100644 --- a/gulliver/js/codemirror/addon/edit/continuelist.js +++ b/gulliver/js/codemirror/addon/edit/continuelist.js @@ -5,8 +5,10 @@ unorderedBullets = '*+-'; CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) { + if (cm.getOption("disableInput")) return CodeMirror.Pass; + var pos = cm.getCursor(), - inList = cm.getStateAfter(pos.line).list, + inList = cm.getStateAfter(pos.line).list !== false, match; if (!inList || !(match = cm.getLine(pos.line).match(listRE))) { diff --git a/gulliver/js/codemirror/addon/edit/matchbrackets.js b/gulliver/js/codemirror/addon/edit/matchbrackets.js index f4925b725..465b6ccaa 100644 --- a/gulliver/js/codemirror/addon/edit/matchbrackets.js +++ b/gulliver/js/codemirror/addon/edit/matchbrackets.js @@ -3,25 +3,29 @@ (document.documentMode == null || document.documentMode < 8); var Pos = CodeMirror.Pos; - // Disable brace matching in long lines, since it'll cause hugely slow updates - var maxLineLen = 1000; var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; - function findMatchingBracket(cm) { - var cur = cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; + function findMatchingBracket(cm, where, strict) { + var state = cm.state.matchBrackets; + var maxScanLen = (state && state.maxScanLineLength) || 10000; + var maxScanLines = (state && state.maxScanLines) || 100; + + var cur = where || cm.getCursor(), line = cm.getLineHandle(cur.line), pos = cur.ch - 1; var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; if (!match) return null; var forward = match.charAt(1) == ">", d = forward ? 1 : -1; - var style = cm.getTokenAt(Pos(cur.line, pos + 1)).type; + if (strict && forward != (pos == cur.ch)) return null; + var style = cm.getTokenTypeAt(Pos(cur.line, pos + 1)); var stack = [line.text.charAt(pos)], re = /[(){}[\]]/; function scan(line, lineNo, start) { if (!line.text) return; var pos = forward ? 0 : line.text.length - 1, end = forward ? line.text.length : -1; + if (line.text.length > maxScanLen) return null; if (start != null) pos = start + d; for (; pos != end; pos += d) { var ch = line.text.charAt(pos); - if (re.test(ch) && cm.getTokenAt(Pos(lineNo, pos + 1)).type == style) { + if (re.test(ch) && cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style) { var match = matching[ch]; if (match.charAt(1) == ">" == forward) stack.push(ch); else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false}; @@ -29,25 +33,28 @@ } } } - for (var i = cur.line, found, e = forward ? Math.min(i + 100, cm.lineCount()) : Math.max(-1, i - 100); i != e; i+=d) { + for (var i = cur.line, found, e = forward ? Math.min(i + maxScanLines, cm.lineCount()) : Math.max(-1, i - maxScanLines); i != e; i+=d) { if (i == cur.line) found = scan(line, i, pos); else found = scan(cm.getLineHandle(i), i); if (found) break; } - return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), match: found && found.match}; + return {from: Pos(cur.line, pos), to: found && Pos(i, found.pos), + match: found && found.match, forward: forward}; } function matchBrackets(cm, autoclear) { + // Disable brace matching in long lines, since it'll cause hugely slow updates + var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000; var found = findMatchingBracket(cm); - if (!found || cm.getLine(found.from.line).length > maxLineLen || - found.to && cm.getLine(found.to.line).length > maxLineLen) + if (!found || cm.getLine(found.from.line).length > maxHighlightLen || + found.to && cm.getLine(found.to.line).length > maxHighlightLen) return; var style = found.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket"; var one = cm.markText(found.from, Pos(found.from.line, found.from.ch + 1), {className: style}); var two = found.to && cm.markText(found.to, Pos(found.to.line, found.to.ch + 1), {className: style}); // Kludge to work around the IE bug from issue #1193, where text - // input stops going to the textare whever this fires. + // input stops going to the textarea whenever this fires. if (ie_lt8 && cm.state.focused) cm.display.input.focus(); var clear = function() { cm.operation(function() { one.clear(); two && two.clear(); }); @@ -64,11 +71,17 @@ }); } - CodeMirror.defineOption("matchBrackets", false, function(cm, val) { - if (val) cm.on("cursorActivity", doMatchBrackets); - else cm.off("cursorActivity", doMatchBrackets); + CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) + cm.off("cursorActivity", doMatchBrackets); + if (val) { + cm.state.matchBrackets = typeof val == "object" ? val : {}; + cm.on("cursorActivity", doMatchBrackets); + } }); CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);}); - CodeMirror.defineExtension("findMatchingBracket", function(){return findMatchingBracket(this);}); + CodeMirror.defineExtension("findMatchingBracket", function(pos, strict){ + return findMatchingBracket(this, pos, strict); + }); })(); diff --git a/gulliver/js/codemirror/addon/edit/matchtags.js b/gulliver/js/codemirror/addon/edit/matchtags.js new file mode 100644 index 000000000..f189c1f8e --- /dev/null +++ b/gulliver/js/codemirror/addon/edit/matchtags.js @@ -0,0 +1,56 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("matchTags", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("cursorActivity", doMatchTags); + cm.off("viewportChange", maybeUpdateMatch); + clear(cm); + } + if (val) { + cm.state.matchBothTags = typeof val == "object" && val.bothTags; + cm.on("cursorActivity", doMatchTags); + cm.on("viewportChange", maybeUpdateMatch); + doMatchTags(cm); + } + }); + + function clear(cm) { + if (cm.state.tagHit) cm.state.tagHit.clear(); + if (cm.state.tagOther) cm.state.tagOther.clear(); + cm.state.tagHit = cm.state.tagOther = null; + } + + function doMatchTags(cm) { + cm.state.failedTagMatch = false; + cm.operation(function() { + clear(cm); + if (cm.somethingSelected()) return; + var cur = cm.getCursor(), range = cm.getViewport(); + range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to); + var match = CodeMirror.findMatchingTag(cm, cur, range); + if (!match) return; + if (cm.state.matchBothTags) { + var hit = match.at == "open" ? match.open : match.close; + if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"}); + } + var other = match.at == "close" ? match.open : match.close; + if (other) + cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"}); + else + cm.state.failedTagMatch = true; + }); + } + + function maybeUpdateMatch(cm) { + if (cm.state.failedTagMatch) doMatchTags(cm); + } + + CodeMirror.commands.toMatchingTag = function(cm) { + var found = CodeMirror.findMatchingTag(cm, cm.getCursor()); + if (found) { + var other = found.at == "close" ? found.open : found.close; + if (other) cm.setSelection(other.to, other.from); + } + }; +})(); diff --git a/gulliver/js/codemirror/addon/edit/trailingspace.js b/gulliver/js/codemirror/addon/edit/trailingspace.js new file mode 100644 index 000000000..f6bb02645 --- /dev/null +++ b/gulliver/js/codemirror/addon/edit/trailingspace.js @@ -0,0 +1,15 @@ +CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) { + if (prev == CodeMirror.Init) prev = false; + if (prev && !val) + cm.removeOverlay("trailingspace"); + else if (!prev && val) + cm.addOverlay({ + token: function(stream) { + for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {} + if (i > stream.pos) { stream.pos = i; return null; } + stream.pos = l; + return "trailingspace"; + }, + name: "trailingspace" + }); +}); diff --git a/gulliver/js/codemirror/addon/fold/brace-fold.js b/gulliver/js/codemirror/addon/fold/brace-fold.js index aad6e0141..2560b2b94 100644 --- a/gulliver/js/codemirror/addon/fold/brace-fold.js +++ b/gulliver/js/codemirror/addon/fold/brace-fold.js @@ -1,31 +1,93 @@ -CodeMirror.braceRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "brace", function(cm, start) { var line = start.line, lineText = cm.getLine(line); - var at = lineText.length, startChar, tokenType; - for (;;) { - var found = lineText.lastIndexOf("{", at); - if (found < start.ch) break; - tokenType = cm.getTokenAt(CodeMirror.Pos(line, found + 1)).type; - if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; } - at = found - 1; + var startCh, tokenType; + + function findOpening(openCh) { + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1); + if (found == -1) { + if (pass == 1) break; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) break; + tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)); + if (!/^(comment|string)/.test(tokenType)) return found + 1; + at = found - 1; + } } - if (startChar == null || lineText.lastIndexOf("}") > startChar) return; - var count = 1, lastLine = cm.lineCount(), end, endCh; - outer: for (var i = line + 1; i < lastLine; ++i) { - var text = cm.getLine(i), pos = 0; + + var startToken = "{", endToken = "}", startCh = findOpening("{"); + if (startCh == null) { + startToken = "[", endToken = "]"; + startCh = findOpening("["); + } + + if (startCh == null) return; + var count = 1, lastLine = cm.lastLine(), end, endCh; + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; for (;;) { - var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos); + var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); if (nextOpen < 0) nextOpen = text.length; if (nextClose < 0) nextClose = text.length; pos = Math.min(nextOpen, nextClose); if (pos == text.length) break; - if (cm.getTokenAt(CodeMirror.Pos(i, pos + 1)).type == tokenType) { + if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) { if (pos == nextOpen) ++count; else if (!--count) { end = i; endCh = pos; break outer; } } ++pos; } } - if (end == null || end == line + 1) return; - return {from: CodeMirror.Pos(line, startChar + 1), + if (end == null || line == end && endCh == startCh) return; + return {from: CodeMirror.Pos(line, startCh), to: CodeMirror.Pos(end, endCh)}; -}; +}); +CodeMirror.braceRangeFinder = CodeMirror.fold.brace; // deprecated + +CodeMirror.registerHelper("fold", "import", function(cm, start) { + function hasImport(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type != "keyword" || start.string != "import") return null; + // Now find closing semicolon, return its position + for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) { + var text = cm.getLine(i), semi = text.indexOf(";"); + if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)}; + } + } + + var start = start.line, has = hasImport(start), prev; + if (!has || hasImport(start - 1) || ((prev = hasImport(start - 2)) && prev.end.line == start - 1)) + return null; + for (var end = has.end;;) { + var next = hasImport(end.line + 1); + if (next == null) break; + end = next.end; + } + return {from: cm.clipPos(CodeMirror.Pos(start, has.startCh + 1)), to: end}; +}); +CodeMirror.importRangeFinder = CodeMirror.fold["import"]; // deprecated + +CodeMirror.registerHelper("fold", "include", function(cm, start) { + function hasInclude(line) { + if (line < cm.firstLine() || line > cm.lastLine()) return null; + var start = cm.getTokenAt(CodeMirror.Pos(line, 1)); + if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1)); + if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8; + } + + var start = start.line, has = hasInclude(start); + if (has == null || hasInclude(start - 1) != null) return null; + for (var end = start;;) { + var next = hasInclude(end + 1); + if (next == null) break; + ++end; + } + return {from: CodeMirror.Pos(start, has + 1), + to: cm.clipPos(CodeMirror.Pos(end))}; +}); +CodeMirror.includeRangeFinder = CodeMirror.fold.include; // deprecated diff --git a/gulliver/js/codemirror/addon/fold/comment-fold.js b/gulliver/js/codemirror/addon/fold/comment-fold.js new file mode 100644 index 000000000..26e72f09f --- /dev/null +++ b/gulliver/js/codemirror/addon/fold/comment-fold.js @@ -0,0 +1,42 @@ +CodeMirror.registerGlobalHelper("fold", "comment", function(mode) { + return mode.blockCommentStart && mode.blockCommentEnd; +}, function(cm, start) { + var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd; + if (!startToken || !endToken) return; + var line = start.line, lineText = cm.getLine(line); + + var startCh; + for (var at = start.ch, pass = 0;;) { + var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1); + if (found == -1) { + if (pass == 1) return; + pass = 1; + at = lineText.length; + continue; + } + if (pass == 1 && found < start.ch) return; + if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1)))) { + startCh = found + startToken.length; + break; + } + at = found - 1; + } + + var depth = 1, lastLine = cm.lastLine(), end, endCh; + outer: for (var i = line; i <= lastLine; ++i) { + var text = cm.getLine(i), pos = i == line ? startCh : 0; + for (;;) { + var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos); + if (nextOpen < 0) nextOpen = text.length; + if (nextClose < 0) nextClose = text.length; + pos = Math.min(nextOpen, nextClose); + if (pos == text.length) break; + if (pos == nextOpen) ++depth; + else if (!--depth) { end = i; endCh = pos; break outer; } + ++pos; + } + } + if (end == null || line == end && endCh == startCh) return; + return {from: CodeMirror.Pos(line, startCh), + to: CodeMirror.Pos(end, endCh)}; +}); diff --git a/gulliver/js/codemirror/addon/fold/foldcode.js b/gulliver/js/codemirror/addon/fold/foldcode.js index b8b4b0da9..5c00d7093 100644 --- a/gulliver/js/codemirror/addon/fold/foldcode.js +++ b/gulliver/js/codemirror/addon/fold/foldcode.js @@ -1,32 +1,86 @@ -CodeMirror.newFoldFunction = function(rangeFinder, widget) { - if (widget == null) widget = "\u2194"; - if (typeof widget == "string") { - var text = document.createTextNode(widget); - widget = document.createElement("span"); - widget.appendChild(text); - widget.className = "CodeMirror-foldmarker"; - } +(function() { + "use strict"; - return function(cm, pos) { + function doFold(cm, pos, options, force) { + var finder = options && (options.call ? options : options.rangeFinder); + if (!finder) finder = CodeMirror.fold.auto; if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0); - var range = rangeFinder(cm, pos); - if (!range) return; + var minSize = options && options.minFoldSize || 0; - var present = cm.findMarksAt(range.from), cleared = 0; - for (var i = 0; i < present.length; ++i) { - if (present[i].__isFold) { - ++cleared; - present[i].clear(); + function getRange(allowFolded) { + var range = finder(cm, pos); + if (!range || range.to.line - range.from.line < minSize) return null; + var marks = cm.findMarksAt(range.from); + for (var i = 0; i < marks.length; ++i) { + if (marks[i].__isFold && force !== "fold") { + if (!allowFolded) return null; + range.cleared = true; + marks[i].clear(); + } } + return range; } - if (cleared) return; - var myWidget = widget.cloneNode(true); - CodeMirror.on(myWidget, "mousedown", function() {myRange.clear();}); + var range = getRange(true); + if (options && options.scanUp) while (!range && pos.line > cm.firstLine()) { + pos = CodeMirror.Pos(pos.line - 1, 0); + range = getRange(false); + } + if (!range || range.cleared || force === "unfold") return; + + var myWidget = makeWidget(options); + CodeMirror.on(myWidget, "mousedown", function() { myRange.clear(); }); var myRange = cm.markText(range.from, range.to, { replacedWith: myWidget, clearOnEnter: true, __isFold: true }); + myRange.on("clear", function(from, to) { + CodeMirror.signal(cm, "unfold", cm, from, to); + }); + CodeMirror.signal(cm, "fold", cm, range.from, range.to); + } + + function makeWidget(options) { + var widget = (options && options.widget) || "\u2194"; + if (typeof widget == "string") { + var text = document.createTextNode(widget); + widget = document.createElement("span"); + widget.appendChild(text); + widget.className = "CodeMirror-foldmarker"; + } + return widget; + } + + // Clumsy backwards-compatible interface + CodeMirror.newFoldFunction = function(rangeFinder, widget) { + return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); }; }; -}; + + // New-style interface + CodeMirror.defineExtension("foldCode", function(pos, options, force) { + doFold(this, pos, options, force); + }); + + CodeMirror.commands.fold = function(cm) { + cm.foldCode(cm.getCursor()); + }; + + CodeMirror.registerHelper("fold", "combine", function() { + var funcs = Array.prototype.slice.call(arguments, 0); + return function(cm, start) { + for (var i = 0; i < funcs.length; ++i) { + var found = funcs[i](cm, start); + if (found) return found; + } + }; + }); + + CodeMirror.registerHelper("fold", "auto", function(cm, start) { + var helpers = cm.getHelpers(start, "fold"); + for (var i = 0; i < helpers.length; i++) { + var cur = helpers[i](cm, start); + if (cur) return cur; + } + }); +})(); diff --git a/gulliver/js/codemirror/addon/fold/foldgutter.css b/gulliver/js/codemirror/addon/fold/foldgutter.css new file mode 100644 index 000000000..49805393d --- /dev/null +++ b/gulliver/js/codemirror/addon/fold/foldgutter.css @@ -0,0 +1,21 @@ +.CodeMirror-foldmarker { + color: blue; + text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px; + font-family: arial; + line-height: .3; + cursor: pointer; +} +.CodeMirror-foldgutter { + width: .7em; +} +.CodeMirror-foldgutter-open, +.CodeMirror-foldgutter-folded { + color: #555; + cursor: pointer; +} +.CodeMirror-foldgutter-open:after { + content: "\25BE"; +} +.CodeMirror-foldgutter-folded:after { + content: "\25B8"; +} diff --git a/gulliver/js/codemirror/addon/fold/foldgutter.js b/gulliver/js/codemirror/addon/fold/foldgutter.js new file mode 100644 index 000000000..a4f3bb318 --- /dev/null +++ b/gulliver/js/codemirror/addon/fold/foldgutter.js @@ -0,0 +1,124 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("foldGutter", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.clearGutter(cm.state.foldGutter.options.gutter); + cm.state.foldGutter = null; + cm.off("gutterClick", onGutterClick); + cm.off("change", onChange); + cm.off("viewportChange", onViewportChange); + cm.off("fold", onFold); + cm.off("unfold", onFold); + cm.off("swapDoc", updateInViewport); + } + if (val) { + cm.state.foldGutter = new State(parseOptions(val)); + updateInViewport(cm); + cm.on("gutterClick", onGutterClick); + cm.on("change", onChange); + cm.on("viewportChange", onViewportChange); + cm.on("fold", onFold); + cm.on("unfold", onFold); + cm.on("swapDoc", updateInViewport); + } + }); + + var Pos = CodeMirror.Pos; + + function State(options) { + this.options = options; + this.from = this.to = 0; + } + + function parseOptions(opts) { + if (opts === true) opts = {}; + if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter"; + if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open"; + if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded"; + return opts; + } + + function isFolded(cm, line) { + var marks = cm.findMarksAt(Pos(line)); + for (var i = 0; i < marks.length; ++i) + if (marks[i].__isFold && marks[i].find().from.line == line) return true; + } + + function marker(spec) { + if (typeof spec == "string") { + var elt = document.createElement("div"); + elt.className = spec; + return elt; + } else { + return spec.cloneNode(true); + } + } + + function updateFoldInfo(cm, from, to) { + var opts = cm.state.foldGutter.options, cur = from; + cm.eachLine(from, to, function(line) { + var mark = null; + if (isFolded(cm, cur)) { + mark = marker(opts.indicatorFolded); + } else { + var pos = Pos(cur, 0), func = opts.rangeFinder || CodeMirror.fold.auto; + var range = func && func(cm, pos); + if (range && range.from.line + 1 < range.to.line) + mark = marker(opts.indicatorOpen); + } + cm.setGutterMarker(line, opts.gutter, mark); + ++cur; + }); + } + + function updateInViewport(cm) { + var vp = cm.getViewport(), state = cm.state.foldGutter; + if (!state) return; + cm.operation(function() { + updateFoldInfo(cm, vp.from, vp.to); + }); + state.from = vp.from; state.to = vp.to; + } + + function onGutterClick(cm, line, gutter) { + var opts = cm.state.foldGutter.options; + if (gutter != opts.gutter) return; + cm.foldCode(Pos(line, 0), opts.rangeFinder); + } + + function onChange(cm) { + var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + state.from = state.to = 0; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600); + } + + function onViewportChange(cm) { + var state = cm.state.foldGutter, opts = cm.state.foldGutter.options; + clearTimeout(state.changeUpdate); + state.changeUpdate = setTimeout(function() { + var vp = cm.getViewport(); + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + updateInViewport(cm); + } else { + cm.operation(function() { + if (vp.from < state.from) { + updateFoldInfo(cm, vp.from, state.from); + state.from = vp.from; + } + if (vp.to > state.to) { + updateFoldInfo(cm, state.to, vp.to); + state.to = vp.to; + } + }); + } + }, opts.updateViewportTimeSpan || 400); + } + + function onFold(cm, from) { + var state = cm.state.foldGutter, line = from.line; + if (line >= state.from && line < state.to) + updateFoldInfo(cm, line, line + 1); + } +})(); diff --git a/gulliver/js/codemirror/addon/fold/indent-fold.js b/gulliver/js/codemirror/addon/fold/indent-fold.js index 94a0a1ffa..434c2bc5c 100644 --- a/gulliver/js/codemirror/addon/fold/indent-fold.js +++ b/gulliver/js/codemirror/addon/fold/indent-fold.js @@ -1,11 +1,30 @@ -CodeMirror.indentRangeFinder = function(cm, start) { +CodeMirror.registerHelper("fold", "indent", function(cm, start) { var tabSize = cm.getOption("tabSize"), firstLine = cm.getLine(start.line); - var myIndent = CodeMirror.countColumn(firstLine, null, tabSize); - for (var i = start.line + 1, end = cm.lineCount(); i < end; ++i) { + if (!/\S/.test(firstLine)) return; + var getIndent = function(line) { + return CodeMirror.countColumn(line, null, tabSize); + }; + var myIndent = getIndent(firstLine); + var lastLineInFold = null; + // Go through lines until we find a line that definitely doesn't belong in + // the block we're folding, or to the end. + for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) { var curLine = cm.getLine(i); - if (CodeMirror.countColumn(curLine, null, tabSize) < myIndent && - CodeMirror.countColumn(cm.getLine(i-1), null, tabSize) > myIndent) - return {from: CodeMirror.Pos(start.line, firstLine.length), - to: CodeMirror.Pos(i, curLine.length)}; + var curIndent = getIndent(curLine); + if (curIndent > myIndent) { + // Lines with a greater indent are considered part of the block. + lastLineInFold = i; + } else if (!/\S/.test(curLine)) { + // Empty lines might be breaks within the block we're trying to fold. + } else { + // A non-empty line at an indent equal to or less than ours marks the + // start of another block. + break; + } } -}; + if (lastLineInFold) return { + from: CodeMirror.Pos(start.line, firstLine.length), + to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length) + }; +}); +CodeMirror.indentRangeFinder = CodeMirror.fold.indent; // deprecated diff --git a/gulliver/js/codemirror/addon/fold/xml-fold.js b/gulliver/js/codemirror/addon/fold/xml-fold.js index 79c524d48..db5aed704 100644 --- a/gulliver/js/codemirror/addon/fold/xml-fold.js +++ b/gulliver/js/codemirror/addon/fold/xml-fold.js @@ -1,64 +1,173 @@ -CodeMirror.tagRangeFinder = (function() { +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + function cmp(a, b) { return a.line - b.line || a.ch - b.ch; } + var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD"; var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040"; var xmlTagStart = new RegExp("<(/?)([" + nameStartChar + "][" + nameChar + "]*)", "g"); - return function(cm, start) { - var line = start.line, ch = start.ch, lineText = cm.getLine(line); + function Iter(cm, line, ch, range) { + this.line = line; this.ch = ch; + this.cm = cm; this.text = cm.getLine(line); + this.min = range ? range.from : cm.firstLine(); + this.max = range ? range.to - 1 : cm.lastLine(); + } - function nextLine() { - if (line >= cm.lastLine()) return; - ch = 0; - lineText = cm.getLine(++line); - return true; - } - function toTagEnd() { - for (;;) { - var gt = lineText.indexOf(">", ch); - if (gt == -1) { if (nextLine()) continue; else return; } - var lastSlash = lineText.lastIndexOf("/", gt); - var selfClose = lastSlash > -1 && /^\s*$/.test(lineText.slice(lastSlash + 1, gt)); - ch = gt + 1; - return selfClose ? "selfClose" : "regular"; - } - } - function toNextTag() { - for (;;) { - xmlTagStart.lastIndex = ch; - var found = xmlTagStart.exec(lineText); - if (!found) { if (nextLine()) continue; else return; } - ch = found.index + found[0].length; - return found; - } - } + function tagAt(iter, ch) { + var type = iter.cm.getTokenTypeAt(Pos(iter.line, ch)); + return type && /\btag\b/.test(type); + } - var stack = [], startCh; + function nextLine(iter) { + if (iter.line >= iter.max) return; + iter.ch = 0; + iter.text = iter.cm.getLine(++iter.line); + return true; + } + function prevLine(iter) { + if (iter.line <= iter.min) return; + iter.text = iter.cm.getLine(--iter.line); + iter.ch = iter.text.length; + return true; + } + + function toTagEnd(iter) { for (;;) { - var openTag = toNextTag(), end; - if (!openTag || line != start.line || !(end = toTagEnd())) return; - if (!openTag[1] && end != "selfClose") { - stack.push(openTag[2]); - startCh = ch; - break; - } + var gt = iter.text.indexOf(">", iter.ch); + if (gt == -1) { if (nextLine(iter)) continue; else return; } + if (!tagAt(iter, gt + 1)) { iter.ch = gt + 1; continue; } + var lastSlash = iter.text.lastIndexOf("/", gt); + var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); + iter.ch = gt + 1; + return selfClose ? "selfClose" : "regular"; } - + } + function toTagStart(iter) { for (;;) { - var next = toNextTag(), end, tagLine = line, tagCh = ch - (next ? next[0].length : 0); - if (!next || !(end = toTagEnd())) return; + var lt = iter.ch ? iter.text.lastIndexOf("<", iter.ch - 1) : -1; + if (lt == -1) { if (prevLine(iter)) continue; else return; } + if (!tagAt(iter, lt + 1)) { iter.ch = lt; continue; } + xmlTagStart.lastIndex = lt; + iter.ch = lt; + var match = xmlTagStart.exec(iter.text); + if (match && match.index == lt) return match; + } + } + + function toNextTag(iter) { + for (;;) { + xmlTagStart.lastIndex = iter.ch; + var found = xmlTagStart.exec(iter.text); + if (!found) { if (nextLine(iter)) continue; else return; } + if (!tagAt(iter, found.index + 1)) { iter.ch = found.index + 1; continue; } + iter.ch = found.index + found[0].length; + return found; + } + } + function toPrevTag(iter) { + for (;;) { + var gt = iter.ch ? iter.text.lastIndexOf(">", iter.ch - 1) : -1; + if (gt == -1) { if (prevLine(iter)) continue; else return; } + if (!tagAt(iter, gt + 1)) { iter.ch = gt; continue; } + var lastSlash = iter.text.lastIndexOf("/", gt); + var selfClose = lastSlash > -1 && !/\S/.test(iter.text.slice(lastSlash + 1, gt)); + iter.ch = gt + 1; + return selfClose ? "selfClose" : "regular"; + } + } + + function findMatchingClose(iter, tag) { + var stack = []; + for (;;) { + var next = toNextTag(iter), end, startLine = iter.line, startCh = iter.ch - (next ? next[0].length : 0); + if (!next || !(end = toTagEnd(iter))) return; if (end == "selfClose") continue; if (next[1]) { // closing tag for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == next[2]) { stack.length = i; break; } - if (!stack.length) return { - from: CodeMirror.Pos(start.line, startCh), - to: CodeMirror.Pos(tagLine, tagCh) + if (i < 0 && (!tag || tag == next[2])) return { + tag: next[2], + from: Pos(startLine, startCh), + to: Pos(iter.line, iter.ch) }; } else { // opening tag stack.push(next[2]); } } + } + function findMatchingOpen(iter, tag) { + var stack = []; + for (;;) { + var prev = toPrevTag(iter); + if (!prev) return; + if (prev == "selfClose") { toTagStart(iter); continue; } + var endLine = iter.line, endCh = iter.ch; + var start = toTagStart(iter); + if (!start) return; + if (start[1]) { // closing tag + stack.push(start[2]); + } else { // opening tag + for (var i = stack.length - 1; i >= 0; --i) if (stack[i] == start[2]) { + stack.length = i; + break; + } + if (i < 0 && (!tag || tag == start[2])) return { + tag: start[2], + from: Pos(iter.line, iter.ch), + to: Pos(endLine, endCh) + }; + } + } + } + + CodeMirror.registerHelper("fold", "xml", function(cm, start) { + var iter = new Iter(cm, start.line, 0); + for (;;) { + var openTag = toNextTag(iter), end; + if (!openTag || iter.line != start.line || !(end = toTagEnd(iter))) return; + if (!openTag[1] && end != "selfClose") { + var start = Pos(iter.line, iter.ch); + var close = findMatchingClose(iter, openTag[2]); + return close && {from: start, to: close.from}; + } + } + }); + CodeMirror.tagRangeFinder = CodeMirror.fold.xml; // deprecated + + CodeMirror.findMatchingTag = function(cm, pos, range) { + var iter = new Iter(cm, pos.line, pos.ch, range); + if (iter.text.indexOf(">") == -1 && iter.text.indexOf("<") == -1) return; + var end = toTagEnd(iter), to = end && Pos(iter.line, iter.ch); + var start = end && toTagStart(iter); + if (!end || end == "selfClose" || !start || cmp(iter, pos) > 0) return; + var here = {from: Pos(iter.line, iter.ch), to: to, tag: start[2]}; + + if (start[1]) { // closing tag + return {open: findMatchingOpen(iter, start[2]), close: here, at: "close"}; + } else { // opening tag + iter = new Iter(cm, to.line, to.ch, range); + return {open: here, close: findMatchingClose(iter, start[2]), at: "open"}; + } + }; + + CodeMirror.findEnclosingTag = function(cm, pos, range) { + var iter = new Iter(cm, pos.line, pos.ch, range); + for (;;) { + var open = findMatchingOpen(iter); + if (!open) break; + var forward = new Iter(cm, pos.line, pos.ch, range); + var close = findMatchingClose(forward, open.tag); + if (close) return {open: open, close: close}; + } + }; + + // Used by addon/edit/closetag.js + CodeMirror.scanForClosingTag = function(cm, pos, name, end) { + var iter = new Iter(cm, pos.line, pos.ch, end ? {from: 0, to: end} : null); + return !!findMatchingClose(iter, name); }; })(); diff --git a/gulliver/js/codemirror/addon/hint/anyword-hint.js b/gulliver/js/codemirror/addon/hint/anyword-hint.js new file mode 100644 index 000000000..a144768c8 --- /dev/null +++ b/gulliver/js/codemirror/addon/hint/anyword-hint.js @@ -0,0 +1,32 @@ +(function() { + "use strict"; + + var WORD = /[\w$]+/, RANGE = 500; + + CodeMirror.registerHelper("hint", "anyword", function(editor, options) { + var word = options && options.word || WORD; + var range = options && options.range || RANGE; + var cur = editor.getCursor(), curLine = editor.getLine(cur.line); + var start = cur.ch, end = start; + while (end < curLine.length && word.test(curLine.charAt(end))) ++end; + while (start && word.test(curLine.charAt(start - 1))) --start; + var curWord = start != end && curLine.slice(start, end); + + var list = [], seen = {}; + var re = new RegExp(word.source, "g"); + for (var dir = -1; dir <= 1; dir += 2) { + var line = cur.line, end = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir; + for (; line != end; line += dir) { + var text = editor.getLine(line), m; + while (m = re.exec(text)) { + if (line == cur.line && m[0] === curWord) continue; + if ((!curWord || m[0].lastIndexOf(curWord, 0) == 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) { + seen[m[0]] = true; + list.push(m[0]); + } + } + } + } + return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)}; + }); +})(); diff --git a/gulliver/js/codemirror/addon/hint/css-hint.js b/gulliver/js/codemirror/addon/hint/css-hint.js new file mode 100644 index 000000000..6789c458b --- /dev/null +++ b/gulliver/js/codemirror/addon/hint/css-hint.js @@ -0,0 +1,46 @@ +(function () { + "use strict"; + + var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1, + "first-letter": 1, "first-line": 1, "first-child": 1, + before: 1, after: 1, lang: 1}; + + CodeMirror.registerHelper("hint", "css", function(cm) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != "css") return; + + var word = token.string, start = token.start, end = token.end; + if (/[^\w$_-]/.test(word)) { + word = ""; start = end = cur.ch; + } + + var spec = CodeMirror.resolveMode("text/css"); + + var result = []; + function add(keywords) { + for (var name in keywords) + if (!word || name.lastIndexOf(word, 0) == 0) + result.push(name); + } + + var st = token.state.state; + if (st == "pseudo" || token.type == "variable-3") { + add(pseudoClasses); + } else if (st == "block" || st == "maybeprop") { + add(spec.propertyKeywords); + } else if (st == "prop" || st == "parens" || st == "at" || st == "params") { + add(spec.valueKeywords); + add(spec.colorKeywords); + } else if (st == "media" || st == "media_parens") { + add(spec.mediaTypes); + add(spec.mediaFeatures); + } + + if (result.length) return { + list: result, + from: CodeMirror.Pos(cur.line, start), + to: CodeMirror.Pos(cur.line, end) + }; + }); +})(); diff --git a/gulliver/js/codemirror/addon/hint/html-hint.js b/gulliver/js/codemirror/addon/hint/html-hint.js index 8b5dc6f00..cf256851e 100755 --- a/gulliver/js/codemirror/addon/hint/html-hint.js +++ b/gulliver/js/codemirror/addon/hint/html-hint.js @@ -1,582 +1,337 @@ (function () { - function htmlHint(editor, htmlStructure, getToken) { - var cur = editor.getCursor(); - var token = getToken(editor, cur); - var keywords = []; - var i = 0; - var j = 0; - var k = 0; - var from = {line: cur.line, ch: cur.ch}; - var to = {line: cur.line, ch: cur.ch}; - var flagClean = true; + var langs = "ab aa af ak sq am ar an hy as av ae ay az bm ba eu be bn bh bi bs br bg my ca ch ce ny zh cv kw co cr hr cs da dv nl dz en eo et ee fo fj fi fr ff gl ka de el gn gu ht ha he hz hi ho hu ia id ie ga ig ik io is it iu ja jv kl kn kr ks kk km ki rw ky kv kg ko ku kj la lb lg li ln lo lt lu lv gv mk mg ms ml mt mi mr mh mn na nv nb nd ne ng nn no ii nr oc oj cu om or os pa pi fa pl ps pt qu rm rn ro ru sa sc sd se sm sg sr gd sn si sk sl so st es su sw ss sv ta te tg th ti bo tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa cy wo fy xh yi yo za zu".split(" "); + var targets = ["_blank", "_self", "_top", "_parent"]; + var charsets = ["ascii", "utf-8", "utf-16", "latin1", "latin1"]; + var methods = ["get", "post", "put", "delete"]; + var encs = ["application/x-www-form-urlencoded", "multipart/form-data", "text/plain"]; + var media = ["all", "screen", "print", "embossed", "braille", "handheld", "print", "projection", "screen", "tty", "tv", "speech", + "3d-glasses", "resolution [>][<][=] [X]", "device-aspect-ratio: X/Y", "orientation:portrait", + "orientation:landscape", "device-height: [X]", "device-width: [X]"]; + var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags - var text = editor.getRange({line: 0, ch: 0}, cur); - - var open = text.lastIndexOf('<'); - var close = text.lastIndexOf('>'); - var tokenString = token.string.replace("<",""); - - if(open > close) { - var last = editor.getRange({line: cur.line, ch: cur.ch - 1}, cur); - if(last == "<") { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push(htmlStructure[i].tag); - } - from.ch = token.start + 1; - } else { - var counter = 0; - var found = function(token, type, position) { - counter++; - if(counter > 50) return; - if(token.type == type) { - return token; - } else { - position.ch = token.start; - var newToken = editor.getTokenAt(position); - return found(newToken, type, position); - } - }; - - var nodeToken = found(token, "tag", {line: cur.line, ch: cur.ch}); - var node = nodeToken.string.substring(1); - - if(token.type === null && token.string.trim() === "") { - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - keywords.push(htmlStructure[i].attr[j].key + "=\"\" "); - } - - for(k = 0; k < globalAttributes.length; k++) { - keywords.push(globalAttributes[k].key + "=\"\" "); - } - } - } - } else if(token.type == "string") { - tokenString = tokenString.substring(1, tokenString.length - 1); - var attributeToken = found(token, "attribute", {line: cur.line, ch: cur.ch}); - var attribute = attributeToken.string; - - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - if(htmlStructure[i].attr[j].key == attribute) { - for(k = 0; k < htmlStructure[i].attr[j].values.length; k++) { - keywords.push(htmlStructure[i].attr[j].values[k]); - } - } - } - - for(j = 0; j < globalAttributes.length; j++) { - if(globalAttributes[j].key == attribute) { - for(k = 0; k < globalAttributes[j].values.length; k++) { - keywords.push(globalAttributes[j].values[k]); - } - } - } - } - } - from.ch = token.start + 1; - } else if(token.type == "attribute") { - for(i = 0; i < htmlStructure.length; i++) { - if(htmlStructure[i].tag == node) { - for(j = 0; j < htmlStructure[i].attr.length; j++) { - keywords.push(htmlStructure[i].attr[j].key + "=\"\" "); - } - - for(k = 0; k < globalAttributes.length; k++) { - keywords.push(globalAttributes[k].key + "=\"\" "); - } - } - } - from.ch = token.start; - } else if(token.type == "tag") { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push(htmlStructure[i].tag); - } - - from.ch = token.start + 1; - } + var data = { + a: { + attrs: { + href: null, ping: null, type: null, + media: media, + target: targets, + hreflang: langs } - } else { - for(i = 0; i < htmlStructure.length; i++) { - keywords.push("<" + htmlStructure[i].tag); + }, + abbr: s, + acronym: s, + address: s, + applet: s, + area: { + attrs: { + alt: null, coords: null, href: null, target: null, ping: null, + media: media, hreflang: langs, type: null, + shape: ["default", "rect", "circle", "poly"] } + }, + article: s, + aside: s, + audio: { + attrs: { + src: null, mediagroup: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["none", "metadata", "auto"], + autoplay: ["", "autoplay"], + loop: ["", "loop"], + controls: ["", "controls"] + } + }, + b: s, + base: { attrs: { href: null, target: targets } }, + basefont: s, + bdi: s, + bdo: s, + big: s, + blockquote: { attrs: { cite: null } }, + body: s, + br: s, + button: { + attrs: { + form: null, formaction: null, name: null, value: null, + autofocus: ["", "autofocus"], + disabled: ["", "autofocus"], + formenctype: encs, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + type: ["submit", "reset", "button"] + } + }, + canvas: { attrs: { width: null, height: null } }, + caption: s, + center: s, + cite: s, + code: s, + col: { attrs: { span: null } }, + colgroup: { attrs: { span: null } }, + command: { + attrs: { + type: ["command", "checkbox", "radio"], + label: null, icon: null, radiogroup: null, command: null, title: null, + disabled: ["", "disabled"], + checked: ["", "checked"] + } + }, + data: { attrs: { value: null } }, + datagrid: { attrs: { disabled: ["", "disabled"], multiple: ["", "multiple"] } }, + datalist: { attrs: { data: null } }, + dd: s, + del: { attrs: { cite: null, datetime: null } }, + details: { attrs: { open: ["", "open"] } }, + dfn: s, + dir: s, + div: s, + dl: s, + dt: s, + em: s, + embed: { attrs: { src: null, type: null, width: null, height: null } }, + eventsource: { attrs: { src: null } }, + fieldset: { attrs: { disabled: ["", "disabled"], form: null, name: null } }, + figcaption: s, + figure: s, + font: s, + footer: s, + form: { + attrs: { + action: null, name: null, + "accept-charset": charsets, + autocomplete: ["on", "off"], + enctype: encs, + method: methods, + novalidate: ["", "novalidate"], + target: targets + } + }, + frame: s, + frameset: s, + h1: s, h2: s, h3: s, h4: s, h5: s, h6: s, + head: { + attrs: {}, + children: ["title", "base", "link", "style", "meta", "script", "noscript", "command"] + }, + header: s, + hgroup: s, + hr: s, + html: { + attrs: { manifest: null }, + children: ["head", "body"] + }, + i: s, + iframe: { + attrs: { + src: null, srcdoc: null, name: null, width: null, height: null, + sandbox: ["allow-top-navigation", "allow-same-origin", "allow-forms", "allow-scripts"], + seamless: ["", "seamless"] + } + }, + img: { + attrs: { + alt: null, src: null, ismap: null, usemap: null, width: null, height: null, + crossorigin: ["anonymous", "use-credentials"] + } + }, + input: { + attrs: { + alt: null, dirname: null, form: null, formaction: null, + height: null, list: null, max: null, maxlength: null, min: null, + name: null, pattern: null, placeholder: null, size: null, src: null, + step: null, value: null, width: null, + accept: ["audio/*", "video/*", "image/*"], + autocomplete: ["on", "off"], + autofocus: ["", "autofocus"], + checked: ["", "checked"], + disabled: ["", "disabled"], + formenctype: encs, + formmethod: methods, + formnovalidate: ["", "novalidate"], + formtarget: targets, + multiple: ["", "multiple"], + readonly: ["", "readonly"], + required: ["", "required"], + type: ["hidden", "text", "search", "tel", "url", "email", "password", "datetime", "date", "month", + "week", "time", "datetime-local", "number", "range", "color", "checkbox", "radio", + "file", "submit", "image", "reset", "button"] + } + }, + ins: { attrs: { cite: null, datetime: null } }, + kbd: s, + keygen: { + attrs: { + challenge: null, form: null, name: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + keytype: ["RSA"] + } + }, + label: { attrs: { "for": null, form: null } }, + legend: s, + li: { attrs: { value: null } }, + link: { + attrs: { + href: null, type: null, + hreflang: langs, + media: media, + sizes: ["all", "16x16", "16x16 32x32", "16x16 32x32 64x64"] + } + }, + map: { attrs: { name: null } }, + mark: s, + menu: { attrs: { label: null, type: ["list", "context", "toolbar"] } }, + meta: { + attrs: { + content: null, + charset: charsets, + name: ["viewport", "application-name", "author", "description", "generator", "keywords"], + "http-equiv": ["content-language", "content-type", "default-style", "refresh"] + } + }, + meter: { attrs: { value: null, min: null, low: null, high: null, max: null, optimum: null } }, + nav: s, + noframes: s, + noscript: s, + object: { + attrs: { + data: null, type: null, name: null, usemap: null, form: null, width: null, height: null, + typemustmatch: ["", "typemustmatch"] + } + }, + ol: { attrs: { reversed: ["", "reversed"], start: null, type: ["1", "a", "A", "i", "I"] } }, + optgroup: { attrs: { disabled: ["", "disabled"], label: null } }, + option: { attrs: { disabled: ["", "disabled"], label: null, selected: ["", "selected"], value: null } }, + output: { attrs: { "for": null, form: null, name: null } }, + p: s, + param: { attrs: { name: null, value: null } }, + pre: s, + progress: { attrs: { value: null, max: null } }, + q: { attrs: { cite: null } }, + rp: s, + rt: s, + ruby: s, + s: s, + samp: s, + script: { + attrs: { + type: ["text/javascript"], + src: null, + async: ["", "async"], + defer: ["", "defer"], + charset: charsets + } + }, + section: s, + select: { + attrs: { + form: null, name: null, size: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + multiple: ["", "multiple"] + } + }, + small: s, + source: { attrs: { src: null, type: null, media: null } }, + span: s, + strike: s, + strong: s, + style: { + attrs: { + type: ["text/css"], + media: media, + scoped: null + } + }, + sub: s, + summary: s, + sup: s, + table: s, + tbody: s, + td: { attrs: { colspan: null, rowspan: null, headers: null } }, + textarea: { + attrs: { + dirname: null, form: null, maxlength: null, name: null, placeholder: null, + rows: null, cols: null, + autofocus: ["", "autofocus"], + disabled: ["", "disabled"], + readonly: ["", "readonly"], + required: ["", "required"], + wrap: ["soft", "hard"] + } + }, + tfoot: s, + th: { attrs: { colspan: null, rowspan: null, headers: null, scope: ["row", "col", "rowgroup", "colgroup"] } }, + thead: s, + time: { attrs: { datetime: null } }, + title: s, + tr: s, + track: { + attrs: { + src: null, label: null, "default": null, + kind: ["subtitles", "captions", "descriptions", "chapters", "metadata"], + srclang: langs + } + }, + tt: s, + u: s, + ul: s, + "var": s, + video: { + attrs: { + src: null, poster: null, width: null, height: null, + crossorigin: ["anonymous", "use-credentials"], + preload: ["auto", "metadata", "none"], + autoplay: ["", "autoplay"], + mediagroup: ["movie"], + muted: ["", "muted"], + controls: ["", "controls"] + } + }, + wbr: s + }; - tokenString = ("<" + tokenString).trim(); - from.ch = token.start; - } - - if(flagClean === true && tokenString.trim() === "") { - flagClean = false; - } - - if(flagClean) { - keywords = cleanResults(tokenString, keywords); - } - - return {list: keywords, from: from, to: to}; + var globalAttrs = { + accesskey: ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + "class": null, + contenteditable: ["true", "false"], + contextmenu: null, + dir: ["ltr", "rtl", "auto"], + draggable: ["true", "false", "auto"], + dropzone: ["copy", "move", "link", "string:", "file:"], + hidden: ["hidden"], + id: null, + inert: ["inert"], + itemid: null, + itemprop: null, + itemref: null, + itemscope: ["itemscope"], + itemtype: null, + lang: ["en", "es"], + spellcheck: ["true", "false"], + style: null, + tabindex: ["1", "2", "3", "4", "5", "6", "7", "8", "9"], + title: null, + translate: ["yes", "no"], + onclick: null, + rel: ["stylesheet", "alternate", "author", "bookmark", "help", "license", "next", "nofollow", "noreferrer", "prefetch", "prev", "search", "tag"] + }; + function populate(obj) { + for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr)) + obj.attrs[attr] = globalAttrs[attr]; } + populate(s); + for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s) + populate(data[tag]); - var cleanResults = function(text, keywords) { - var results = []; - var i = 0; - - for(i = 0; i < keywords.length; i++) { - if(keywords[i].substring(0, text.length) == text) { - results.push(keywords[i]); - } - } - - return results; - }; - - var htmlStructure = [ - {tag: '!DOCTYPE', attr: []}, - {tag: 'a', attr: [ - {key: 'href', values: ["#"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]}, - {key: 'ping', values: [""]}, - {key: 'media', values: ["#"]}, - {key: 'hreflang', values: ["en","es"]}, - {key: 'type', values: []} - ]}, - {tag: 'abbr', attr: []}, - {tag: 'acronym', attr: []}, - {tag: 'address', attr: []}, - {tag: 'applet', attr: []}, - {tag: 'area', attr: [ - {key: 'alt', values: [""]}, - {key: 'coords', values: ["rect: left, top, right, bottom","circle: center-x, center-y, radius","poly: x1, y1, x2, y2, ..."]}, - {key: 'shape', values: ["default","rect","circle","poly"]}, - {key: 'href', values: ["#"]}, - {key: 'target', values: ["#"]}, - {key: 'ping', values: []}, - {key: 'media', values: []}, - {key: 'hreflang', values: []}, - {key: 'type', values: []} - - ]}, - {tag: 'article', attr: []}, - {tag: 'aside', attr: []}, - {tag: 'audio', attr: [ - {key: 'src', values: []}, - {key: 'crossorigin', values: ["anonymous","use-credentials"]}, - {key: 'preload', values: ["none","metadata","auto"]}, - {key: 'autoplay', values: ["","autoplay"]}, - {key: 'mediagroup', values: []}, - {key: 'loop', values: ["","loop"]}, - {key: 'controls', values: ["","controls"]} - ]}, - {tag: 'b', attr: []}, - {tag: 'base', attr: [ - {key: 'href', values: ["#"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]} - ]}, - {tag: 'basefont', attr: []}, - {tag: 'bdi', attr: []}, - {tag: 'bdo', attr: []}, - {tag: 'big', attr: []}, - {tag: 'blockquote', attr: [ - {key: 'cite', values: ["http://"]} - ]}, - {tag: 'body', attr: []}, - {tag: 'br', attr: []}, - {tag: 'button', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'formaction', values: []}, - {key: 'formenctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'formmethod', values: ["get","post","put","delete"]}, - {key: 'formnovalidate', values: ["","novalidate"]}, - {key: 'formtarget', values: ["_blank","_self","_top","_parent"]}, - {key: 'name', values: []}, - {key: 'type', values: ["submit","reset","button"]}, - {key: 'value', values: []} - ]}, - {tag: 'canvas', attr: [ - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'caption', attr: []}, - {tag: 'center', attr: []}, - {tag: 'cite', attr: []}, - {tag: 'code', attr: []}, - {tag: 'col', attr: [ - {key: 'span', values: []} - ]}, - {tag: 'colgroup', attr: [ - {key: 'span', values: []} - ]}, - {tag: 'command', attr: [ - {key: 'type', values: ["command","checkbox","radio"]}, - {key: 'label', values: []}, - {key: 'icon', values: []}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'checked', values: ["","checked"]}, - {key: 'radiogroup', values: []}, - {key: 'command', values: []}, - {key: 'title', values: []} - ]}, - {tag: 'data', attr: [ - {key: 'value', values: []} - ]}, - {tag: 'datagrid', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'multiple', values: ["","multiple"]} - ]}, - {tag: 'datalist', attr: [ - {key: 'data', values: []} - ]}, - {tag: 'dd', attr: []}, - {tag: 'del', attr: [ - {key: 'cite', values: []}, - {key: 'datetime', values: []} - ]}, - {tag: 'details', attr: [ - {key: 'open', values: ["","open"]} - ]}, - {tag: 'dfn', attr: []}, - {tag: 'dir', attr: []}, - {tag: 'div', attr: [ - {key: 'id', values: []}, - {key: 'class', values: []}, - {key: 'style', values: []} - ]}, - {tag: 'dl', attr: []}, - {tag: 'dt', attr: []}, - {tag: 'em', attr: []}, - {tag: 'embed', attr: [ - {key: 'src', values: []}, - {key: 'type', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'eventsource', attr: [ - {key: 'src', values: []} - ]}, - {tag: 'fieldset', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'name', values: []} - ]}, - {tag: 'figcaption', attr: []}, - {tag: 'figure', attr: []}, - {tag: 'font', attr: []}, - {tag: 'footer', attr: []}, - {tag: 'form', attr: [ - {key: 'accept-charset', values: ["UNKNOWN","utf-8"]}, - {key: 'action', values: []}, - {key: 'autocomplete', values: ["on","off"]}, - {key: 'enctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'method', values: ["get","post","put","delete","dialog"]}, - {key: 'name', values: []}, - {key: 'novalidate', values: ["","novalidate"]}, - {key: 'target', values: ["_blank","_self","_top","_parent"]} - ]}, - {tag: 'frame', attr: []}, - {tag: 'frameset', attr: []}, - {tag: 'h1', attr: []}, - {tag: 'h2', attr: []}, - {tag: 'h3', attr: []}, - {tag: 'h4', attr: []}, - {tag: 'h5', attr: []}, - {tag: 'h6', attr: []}, - {tag: 'head', attr: []}, - {tag: 'header', attr: []}, - {tag: 'hgroup', attr: []}, - {tag: 'hr', attr: []}, - {tag: 'html', attr: [ - {key: 'manifest', values: []} - ]}, - {tag: 'i', attr: []}, - {tag: 'iframe', attr: [ - {key: 'src', values: []}, - {key: 'srcdoc', values: []}, - {key: 'name', values: []}, - {key: 'sandbox', values: ["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"]}, - {key: 'seamless', values: ["","seamless"]}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'img', attr: [ - {key: 'alt', values: []}, - {key: 'src', values: []}, - {key: 'crossorigin', values: ["anonymous","use-credentials"]}, - {key: 'ismap', values: []}, - {key: 'usemap', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'input', attr: [ - {key: 'accept', values: ["audio/*","video/*","image/*"]}, - {key: 'alt', values: []}, - {key: 'autocomplete', values: ["on","off"]}, - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'checked', values: ["","checked"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'dirname', values: []}, - {key: 'form', values: []}, - {key: 'formaction', values: []}, - {key: 'formenctype', values: ["application/x-www-form-urlencoded","multipart/form-data","text/plain"]}, - {key: 'formmethod', values: ["get","post","put","delete"]}, - {key: 'formnovalidate', values: ["","novalidate"]}, - {key: 'formtarget', values: ["_blank","_self","_top","_parent"]}, - {key: 'height', values: []}, - {key: 'list', values: []}, - {key: 'max', values: []}, - {key: 'maxlength', values: []}, - {key: 'min', values: []}, - {key: 'multiple', values: ["","multiple"]}, - {key: 'name', values: []}, - {key: 'pattern', values: []}, - {key: 'placeholder', values: []}, - {key: 'readonly', values: ["","readonly"]}, - {key: 'required', values: ["","required"]}, - {key: 'size', values: []}, - {key: 'src', values: []}, - {key: 'step', values: []}, - {key: 'type', values: [ - "hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local", - "number","range","color","checkbox","radio","file","submit","image","reset","button" - ]}, - {key: 'value', values: []}, - {key: 'width', values: []} - ]}, - {tag: 'ins', attr: [ - {key: 'cite', values: []}, - {key: 'datetime', values: []} - ]}, - {tag: 'kbd', attr: []}, - {tag: 'keygen', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'challenge', values: []}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'form', values: []}, - {key: 'keytype', values: ["RSA"]}, - {key: 'name', values: []} - ]}, - {tag: 'label', attr: [ - {key: 'for', values: []}, - {key: 'form', values: []} - ]}, - {tag: 'legend', attr: []}, - {tag: 'li', attr: [ - {key: 'value', values: []} - ]}, - {tag: 'link', attr: [ - {key: 'href', values: []}, - {key: 'hreflang', values: ["en","es"]}, - {key: 'media', values: [ - "all","screen","print","embossed","braille","handheld","print","projection","screen","tty","tv","speech","3d-glasses", - "resolution [>][<][=] [X]dpi","resolution [>][<][=] [X]dpcm","device-aspect-ratio: 16/9","device-aspect-ratio: 4/3", - "device-aspect-ratio: 32/18","device-aspect-ratio: 1280/720","device-aspect-ratio: 2560/1440","orientation:portrait", - "orientation:landscape","device-height: [X]px","device-width: [X]px","-webkit-min-device-pixel-ratio: 2" - ]}, - {key: 'type', values: []}, - {key: 'sizes', values: ["all","16x16","16x16 32x32","16x16 32x32 64x64"]} - ]}, - {tag: 'map', attr: [ - {key: 'name', values: []} - ]}, - {tag: 'mark', attr: []}, - {tag: 'menu', attr: [ - {key: 'type', values: ["list","context","toolbar"]}, - {key: 'label', values: []} - ]}, - {tag: 'meta', attr: [ - {key: 'charset', attr: ["utf-8"]}, - {key: 'name', attr: ["viewport","application-name","author","description","generator","keywords"]}, - {key: 'content', attr: ["","width=device-width","initial-scale=1, maximum-scale=1, minimun-scale=1, user-scale=no"]}, - {key: 'http-equiv', attr: ["content-language","content-type","default-style","refresh"]} - ]}, - {tag: 'meter', attr: [ - {key: 'value', values: []}, - {key: 'min', values: []}, - {key: 'low', values: []}, - {key: 'high', values: []}, - {key: 'max', values: []}, - {key: 'optimum', values: []} - ]}, - {tag: 'nav', attr: []}, - {tag: 'noframes', attr: []}, - {tag: 'noscript', attr: []}, - {tag: 'object', attr: [ - {key: 'data', values: []}, - {key: 'type', values: []}, - {key: 'typemustmatch', values: ["","typemustmatch"]}, - {key: 'name', values: []}, - {key: 'usemap', values: []}, - {key: 'form', values: []}, - {key: 'width', values: []}, - {key: 'height', values: []} - ]}, - {tag: 'ol', attr: [ - {key: 'reversed', values: ["", "reversed"]}, - {key: 'start', values: []}, - {key: 'type', values: ["1","a","A","i","I"]} - ]}, - {tag: 'optgroup', attr: [ - {key: 'disabled', values: ["","disabled"]}, - {key: 'label', values: []} - ]}, - {tag: 'option', attr: [ - {key: 'disabled', values: ["", "disabled"]}, - {key: 'label', values: []}, - {key: 'selected', values: ["", "selected"]}, - {key: 'value', values: []} - ]}, - {tag: 'output', attr: [ - {key: 'for', values: []}, - {key: 'form', values: []}, - {key: 'name', values: []} - ]}, - {tag: 'p', attr: []}, - {tag: 'param', attr: [ - {key: 'name', values: []}, - {key: 'value', values: []} - ]}, - {tag: 'pre', attr: []}, - {tag: 'progress', attr: [ - {key: 'value', values: []}, - {key: 'max', values: []} - ]}, - {tag: 'q', attr: [ - {key: 'cite', values: []} - ]}, - {tag: 'rp', attr: []}, - {tag: 'rt', attr: []}, - {tag: 'ruby', attr: []}, - {tag: 's', attr: []}, - {tag: 'samp', attr: []}, - {tag: 'script', attr: [ - {key: 'type', values: ["text/javascript"]}, - {key: 'src', values: []}, - {key: 'async', values: ["","async"]}, - {key: 'defer', values: ["","defer"]}, - {key: 'charset', values: ["utf-8"]} - ]}, - {tag: 'section', attr: []}, - {tag: 'select', attr: [ - {key: 'autofocus', values: ["", "autofocus"]}, - {key: 'disabled', values: ["", "disabled"]}, - {key: 'form', values: []}, - {key: 'multiple', values: ["", "multiple"]}, - {key: 'name', values: []}, - {key: 'size', values: []} - ]}, - {tag: 'small', attr: []}, - {tag: 'source', attr: [ - {key: 'src', values: []}, - {key: 'type', values: []}, - {key: 'media', values: []} - ]}, - {tag: 'span', attr: []}, - {tag: 'strike', attr: []}, - {tag: 'strong', attr: []}, - {tag: 'style', attr: [ - {key: 'type', values: ["text/css"]}, - {key: 'media', values: ["all","braille","print","projection","screen","speech"]}, - {key: 'scoped', values: []} - ]}, - {tag: 'sub', attr: []}, - {tag: 'summary', attr: []}, - {tag: 'sup', attr: []}, - {tag: 'table', attr: [ - {key: 'border', values: []} - ]}, - {tag: 'tbody', attr: []}, - {tag: 'td', attr: [ - {key: 'colspan', values: []}, - {key: 'rowspan', values: []}, - {key: 'headers', values: []} - ]}, - {tag: 'textarea', attr: [ - {key: 'autofocus', values: ["","autofocus"]}, - {key: 'disabled', values: ["","disabled"]}, - {key: 'dirname', values: []}, - {key: 'form', values: []}, - {key: 'maxlength', values: []}, - {key: 'name', values: []}, - {key: 'placeholder', values: []}, - {key: 'readonly', values: ["","readonly"]}, - {key: 'required', values: ["","required"]}, - {key: 'rows', values: []}, - {key: 'cols', values: []}, - {key: 'wrap', values: ["soft","hard"]} - ]}, - {tag: 'tfoot', attr: []}, - {tag: 'th', attr: [ - {key: 'colspan', values: []}, - {key: 'rowspan', values: []}, - {key: 'headers', values: []}, - {key: 'scope', values: ["row","col","rowgroup","colgroup"]} - ]}, - {tag: 'thead', attr: []}, - {tag: 'time', attr: [ - {key: 'datetime', values: []} - ]}, - {tag: 'title', attr: []}, - {tag: 'tr', attr: []}, - {tag: 'track', attr: [ - {key: 'kind', values: ["subtitles","captions","descriptions","chapters","metadata"]}, - {key: 'src', values: []}, - {key: 'srclang', values: ["en","es"]}, - {key: 'label', values: []}, - {key: 'default', values: []} - ]}, - {tag: 'tt', attr: []}, - {tag: 'u', attr: []}, - {tag: 'ul', attr: []}, - {tag: 'var', attr: []}, - {tag: 'video', attr: [ - {key: "src", values: []}, - {key: "crossorigin", values: ["anonymous","use-credentials"]}, - {key: "poster", values: []}, - {key: "preload", values: ["auto","metadata","none"]}, - {key: "autoplay", values: ["","autoplay"]}, - {key: "mediagroup", values: ["movie"]}, - {key: "loop", values: ["","loop"]}, - {key: "muted", values: ["","muted"]}, - {key: "controls", values: ["","controls"]}, - {key: "width", values: []}, - {key: "height", values: []} - ]}, - {tag: 'wbr', attr: []} - ]; - - var globalAttributes = [ - {key: "accesskey", values: ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"]}, - {key: "class", values: []}, - {key: "contenteditable", values: ["true", "false"]}, - {key: "contextmenu", values: []}, - {key: "dir", values: ["ltr","rtl","auto"]}, - {key: "draggable", values: ["true","false","auto"]}, - {key: "dropzone", values: ["copy","move","link","string:","file:"]}, - {key: "hidden", values: ["hidden"]}, - {key: "id", values: []}, - {key: "inert", values: ["inert"]}, - {key: "itemid", values: []}, - {key: "itemprop", values: []}, - {key: "itemref", values: []}, - {key: "itemscope", values: ["itemscope"]}, - {key: "itemtype", values: []}, - {key: "lang", values: ["en","es"]}, - {key: "spellcheck", values: ["true","false"]}, - {key: "style", values: []}, - {key: "tabindex", values: ["1","2","3","4","5","6","7","8","9"]}, - {key: "title", values: []}, - {key: "translate", values: ["yes","no"]}, - {key: "onclick", values: []}, - {key: 'rel', values: ["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"]} - ]; - - CodeMirror.htmlHint = function(editor) { - if(String.prototype.trim == undefined) { - String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');}; - } - return htmlHint(editor, htmlStructure, function (e, cur) { return e.getTokenAt(cur); }); - }; + CodeMirror.htmlSchema = data; + function htmlHint(cm, options) { + var local = {schemaInfo: data}; + if (options) for (var opt in options) local[opt] = options[opt]; + return CodeMirror.hint.xml(cm, local); + } + CodeMirror.htmlHint = htmlHint; // deprecated + CodeMirror.registerHelper("hint", "html", htmlHint); })(); diff --git a/gulliver/js/codemirror/addon/hint/javascript-hint.js b/gulliver/js/codemirror/addon/hint/javascript-hint.js index fe2240af1..c347c206e 100644 --- a/gulliver/js/codemirror/addon/hint/javascript-hint.js +++ b/gulliver/js/codemirror/addon/hint/javascript-hint.js @@ -4,7 +4,7 @@ function forEach(arr, f) { for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); } - + function arrayContains(arr, item) { if (!Array.prototype.indexOf) { var i = arr.length; @@ -21,8 +21,11 @@ function scriptHint(editor, keywords, getToken, options) { // Find the token at the cursor var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; + if (/\b(?:string|comment)\b/.test(token.type)) return; + token.state = CodeMirror.innerMode(editor.getMode(), token.state).state; + // If it's not a 'word-style' token, ignore the token. - if (!/^[\w$_]*$/.test(token.string)) { + if (!/^[\w$_]*$/.test(token.string)) { token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, type: token.string == "." ? "property" : null}; } @@ -31,21 +34,6 @@ tprop = getToken(editor, Pos(cur.line, tprop.start)); if (tprop.string != ".") return; tprop = getToken(editor, Pos(cur.line, tprop.start)); - if (tprop.string == ')') { - var level = 1; - do { - tprop = getToken(editor, Pos(cur.line, tprop.start)); - switch (tprop.string) { - case ')': level++; break; - case '(': level--; break; - default: break; - } - } while (level > 0); - tprop = getToken(editor, Pos(cur.line, tprop.start)); - if (tprop.type.indexOf("variable") === 0) - tprop.type = "function"; - else return; // no clue - } if (!context) var context = []; context.push(tprop); } @@ -54,11 +42,13 @@ to: Pos(cur.line, token.end)}; } - CodeMirror.javascriptHint = function(editor, options) { + function javascriptHint(editor, options) { return scriptHint(editor, javascriptKeywords, function (e, cur) {return e.getTokenAt(cur);}, options); }; + CodeMirror.javascriptHint = javascriptHint; // deprecated + CodeMirror.registerHelper("hint", "javascript", javascriptHint); function getCoffeeScriptToken(editor, cur) { // This getToken, it is for coffeescript, imitates the behavior of @@ -78,9 +68,11 @@ return token; } - CodeMirror.coffeescriptHint = function(editor, options) { + function coffeescriptHint(editor, options) { return scriptHint(editor, coffeescriptKeywords, getCoffeeScriptToken, options); - }; + } + CodeMirror.coffeescriptHint = coffeescriptHint; // deprecated + CodeMirror.registerHelper("hint", "coffeescript", coffeescriptHint); var stringProps = ("charAt charCodeAt indexOf lastIndexOf substring substr slice trim trimLeft trimRight " + "toUpperCase toLowerCase split concat match replace search").split(" "); @@ -95,7 +87,7 @@ function getCompletions(token, context, keywords, options) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } function gatherCompletions(obj) { if (typeof obj == "string") forEach(stringProps, maybeAdd); @@ -104,11 +96,11 @@ for (var name in obj) maybeAdd(name); } - if (context) { + if (context && context.length) { // If this is a property, see if it belongs to some object we can // find in the current environment. var obj = context.pop(), base; - if (obj.type.indexOf("variable") === 0) { + if (obj.type && obj.type.indexOf("variable") === 0) { if (options && options.additionalContext) base = options.additionalContext[obj.string]; base = base || window[obj.string]; @@ -126,8 +118,7 @@ while (base != null && context.length) base = base[context.pop().string]; if (base != null) gatherCompletions(base); - } - else { + } else { // If not, just look in the window object and any local scope // (reading into JS mode internals to get at the local and global variables) for (var v = token.state.localVars; v; v = v.next) maybeAdd(v.name); diff --git a/gulliver/js/codemirror/addon/hint/php-hint.js b/gulliver/js/codemirror/addon/hint/php-hint.js deleted file mode 100644 index 9723cb250..000000000 --- a/gulliver/js/codemirror/addon/hint/php-hint.js +++ /dev/null @@ -1,267 +0,0 @@ -(function () { - var Pos = CodeMirror.Pos; - - function forEach(arr, f) { - for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); - } - - function arrayContains(arr, item) { - if (!Array.prototype.indexOf) { - var i = arr.length; - while (i--) { - if (arr[i] === item) { - return true; - } - } - return false; - } - return arr.indexOf(item) != -1; - } - - function scriptHint(editor, keywords, getToken, options) { - // Find the token at the cursor - var cur = editor.getCursor(), token = getToken(editor, cur), tprop = token; - var sToken = token.string.trim(); - - if ( sToken == "(") { - token = tprop = getToken(editor, Pos(cur.line, tprop.start)); - return {list: getCompletions(token.string, keywords, options), - from: Pos(cur.line, token.start), - to: Pos(cur.line, token.end + 1)}; - } - if ( sToken == "=") { - return {list: getCompletions(token.string, keywords, options), - from: Pos(cur.line, token.start + 1), - to: Pos(cur.line, token.end)}; - } - return {list: getCompletions(token.string, keywords, options), - from: Pos(cur.line, token.start), - to: Pos(cur.line, token.end)}; - } - - CodeMirror.phpHint = function(editor, options) { - return scriptHint(editor, phpPMFunctions, function (e, cur) {return e.getTokenAt(cur);}, options); - }; - - var SPACE = " "; - var arrayFunctions = []; - - var formatDate = "formatDate"; - var formatDateFunction = [formatDate+"($date,$format,$language);",formatDate+"($date,$format);"]; - arrayFunctions[formatDate] = formatDateFunction; - - var getCurrentDate = "getCurrentDate"; - var getCurrentDateFunction = [getCurrentDate+"()"]; - arrayFunctions[getCurrentDate] = getCurrentDateFunction; - - var getCurrentTime = "getCurrentTime"; - var getCurrentTimeFunction = [getCurrentTime+"()"]; - arrayFunctions[getCurrentTime] = getCurrentTimeFunction; - - var literalDate = "literalDate"; - var literalDateFunction = [literalDate+"($date,$Language)",literalDate+"($date)"]; - arrayFunctions[literalDate] = literalDateFunction; - - var capitalize = "capitalize"; - var capitalizeFunction = [capitalize+"($textToConvert)"]; - arrayFunctions[capitalize] = capitalizeFunction; - - var lowerCase = "lowerCase"; - var lowerCaseFunction = [lowerCase+"($textToConvert)"]; - arrayFunctions[lowerCase] = lowerCaseFunction; - - var upperCase = "upperCase"; - var upperCaseFunction = [upperCase+"($textToConvert)"]; - arrayFunctions[upperCase] = upperCaseFunction; - - var userInfo = "userInfo"; - var userInfoFunction = [userInfo+"($USER_ID)"]; - arrayFunctions[userInfo] = userInfoFunction; - - var executeQuery = "executeQuery"; - var executeQueryFunction = [executeQuery+"($sqlStatement,$DBConnectionUID)",executeQuery+"($sqlStatement)"]; - arrayFunctions[executeQuery] = executeQueryFunction; - - var orderGrid = "orderGrid"; - var orderGridFunction = ("orderGrid($gridName,$field,$criteria) orderGrid($gridName,$field)").split(SPACE); - arrayFunctions[orderGrid] = orderGridFunction; - - var evaluateFunction = "evaluateFunction"; - var evaluateFunctionFunction = [evaluateFunction+"($gridName,$Expression)"]; - arrayFunctions[evaluateFunction] = evaluateFunctionFunction; - - var PMFTaskCase = "PMFTaskCase"; - var PMFTaskCaseFunction = [PMFTaskCase+"($caseId)"]; - arrayFunctions[PMFTaskCase] = PMFTaskCaseFunction; - - var PMFTaskList = "PMFTaskList"; - var PMFTaskListFunction = [PMFTaskList+"($userId)"]; - arrayFunctions[PMFTaskList] = PMFTaskListFunction; - - var PMFUserList = "PMFUserList"; - var PMFUserListFunction = [PMFUserList+"()"]; - arrayFunctions[PMFUserList] = PMFUserListFunction; - - var PMFGroupList = "PMFGroupList"; - var PMFGroupListFunction = [PMFGroupList+"()"]; - arrayFunctions[PMFGroupList] = PMFGroupListFunction; - - var PMFRoleList = "PMFRoleList"; - var PMFRoleListFunction = [PMFRoleList+"()"]; - arrayFunctions[PMFRoleList] = PMFRoleListFunction; - - var PMFCaseList = "PMFCaseList"; - var PMFCaseListFunction = [PMFCaseList+"($userId)",PMFCaseList+"()"]; - arrayFunctions[PMFCaseList] = PMFCaseListFunction; - - var PMFProcessList = "PMFProcessList"; - var PMFProcessListFunction = [PMFProcessList+"()"]; - arrayFunctions[PMFProcessList] = PMFProcessListFunction; - - var PMFSendVariables = "PMFSendVariables"; - var PMFSendVariablesFunction = [PMFSendVariables+"($caseId,$variables)"]; - arrayFunctions[PMFSendVariables] = PMFSendVariablesFunction; - - var PMFDerivateCase = "PMFDerivateCase"; - var PMFDerivateCaseFunction = [PMFDerivateCase+"($caseId,$delegation,$executeTriggersBeforeAssigment)",PMFDerivateCase+"($caseId,$delegation)"]; - arrayFunctions[PMFDerivateCase] = PMFDerivateCaseFunction; - - var PMFNewCaseImpersonate = "PMFNewCaseImpersonate"; - var PMFNewCaseImpersonateFunction = [PMFNewCaseImpersonate+"($processId,$userId,$variables)"]; - arrayFunctions[PMFNewCaseImpersonate] = PMFNewCaseImpersonateFunction; - - var PMFNewCase = "PMFNewCase"; - var PMFNewCaseFunction = [PMFNewCase+"($processId,$userId,$taskId,$variables)"]; - arrayFunctions[PMFNewCase] = PMFNewCaseFunction; - - var PMFPauseCase = "PMFPauseCase"; - var PMFPauseCaseFunction = [PMFPauseCase+"($caseUid,$delIndex,$userUid,$unpauseDate)",PMFPauseCase+"($caseUid,$delIndex,$userUid)"]; - arrayFunctions[PMFPauseCase] = PMFPauseCaseFunction; - - var PMFAssignUserToGroup = "PMFAssignUserToGroup"; - var PMFAssignUserToGroupFunction = [PMFAssignUserToGroup+"($userId,$groupId)"]; - arrayFunctions[PMFAssignUserToGroup] = PMFAssignUserToGroupFunction; - - var PMFCreateUser = "PMFCreateUser"; - var PMFCreateUserFunction = [PMFCreateUser+"($userId,$password,$firstname,$lastname,$email,$role)"]; - arrayFunctions[PMFCreateUser] = PMFCreateUserFunction; - - var PMFUpdateUser = "PMFUpdateUser"; - var PMFUpdateUserFunction = [PMFUpdateUser+"($userUid,$userName,$firstName,$lastName,$email,$dueDate,$status,$role,$password)"]; - arrayFunctions[PMFUpdateUser] = PMFUpdateUserFunction; - - var PMFInformationUser = "PMFInformationUser"; - var PMFInformationUserFunction = [PMFInformationUser+"($userUid)"]; - arrayFunctions[PMFInformationUser] = PMFInformationUserFunction; - - var generateCode = "generateCode"; - var generateCodeFunction = [generateCode+"($size,$type)"]; - arrayFunctions[generateCode] = generateCodeFunction; - - var setCaseTrackerCode = "setCaseTrackerCode"; - var setCaseTrackerCodeFunction = [setCaseTrackerCode+"($caseId,$code,$pin)"]; - arrayFunctions[setCaseTrackerCode] = setCaseTrackerCodeFunction; - - var jumping = "jumping"; - var jumpingFunction = [jumping+"($caseId,$delegation)"]; - arrayFunctions[jumping] = jumpingFunction; - - var PMFRedirectToStep = "PMFRedirectToStep"; - var PMFRedirectToStepFunction = [PMFRedirectToStep+"($caseId,$delegation,$stepType,$stepId)"]; - arrayFunctions[PMFRedirectToStep] = PMFRedirectToStepFunction; - - var pauseCase = "pauseCase"; - var pauseCaseFunction = [pauseCase+"($caseId,$delegation,$userId,$unpauseDate)",pauseCase+"($caseId,$delegation,$userId)"]; - arrayFunctions[pauseCase] = pauseCaseFunction; - - var PMFUnpauseCase = "PMFUnpauseCase"; - var PMFUnpauseCaseFunction = [PMFUnpauseCase+"($caseId,$delegation,$userId,$unpauseDate)",PMFUnpauseCase+"($caseId,$delegation,$userId)"]; - arrayFunctions[PMFUnpauseCase] = PMFUnpauseCaseFunction; - - var PMFSendMessage = "PMFSendMessage"; - var PMFSendMessageFunction = [PMFSendMessage+"($caseId,$from,$to,$cc,$bcc,$subject,$template,$fields,$attachments)",PMFSendMessage+"($caseId,$from,$to,$cc,$bcc,$subject,$template,$fields)",PMFSendMessage+"($caseId,$from,$to,$cc,$bcc,$subject,$template)"]; - arrayFunctions[PMFSendMessage] = PMFSendMessageFunction; - - var PMFgetLabelOption = "PMFgetLabelOption"; - var PMFgetLabelOptionFunction = [PMFgetLabelOption+"($processId,$dynaformId,$fieldName,$optionId)"]; - arrayFunctions[PMFgetLabelOption] = PMFgetLabelOptionFunction; - - var PMFGenerateOutputDocument = "PMFGenerateOutputDocument"; - var PMFGenerateOutputDocumentFunction = [PMFGenerateOutputDocument+"($outputID)"]; - arrayFunctions[PMFGenerateOutputDocument] = PMFGenerateOutputDocumentFunction; - - var PMFGetUserEmailAddress = "PMFGetUserEmailAddress"; - var PMFGetUserEmailAddressFunction = [PMFGetUserEmailAddress+"($id,$APP_UID,$prefix)",PMFGetUserEmailAddress+"($id,$APP_UID)",PMFGetUserEmailAddress+"($id)"]; - arrayFunctions[PMFGetUserEmailAddress] = PMFGetUserEmailAddressFunction; - - var PMFGetNextAssignedUser = "PMFGetNextAssignedUser"; - var PMFGetNextAssignedUserFunction = (PMFGetNextAssignedUser+"($application,$task)").split(SPACE); - arrayFunctions[PMFGetNextAssignedUser] = PMFGetNextAssignedUserFunction; - - var PMFDeleteCase = "PMFDeleteCase"; - var PMFDeleteCaseFunction = ("PMFDeleteCase($caseId)").split(SPACE); - arrayFunctions[PMFDeleteCase] = PMFDeleteCaseFunction; - - var PMFCancelCase = "PMFCancelCase"; - var PMFCancelCaseFunction = [PMFCancelCase+"($caseUid,$delIndex,$userUid)"]; - arrayFunctions[PMFCancelCase] = PMFCancelCaseFunction; - - var PMFAddInputDocument = "PMFAddInputDocument"; - var PMFAddInputDocumentFunction = [PMFAddInputDocument+"($inputDocumentUid,$appDocUid,$docVersion,$appDocType,$appDocComment,$inputDocumentAction,$caseUid,$delIndex,$taskUid,$userUid,$option,$file)",PMFAddInputDocument+"($inputDocumentUid,$appDocUid,$docVersion,$appDocType,$appDocComment,$inputDocumentAction,$caseUid,$delIndex,$taskUid,$userUid,$option)",PMFAddInputDocument+"($inputDocumentUid,$appDocUid,$docVersion,$appDocType,$appDocComment,$inputDocumentAction,$caseUid,$delIndex,$taskUid,$userUid)"]; - arrayFunctions[PMFAddInputDocument] = PMFAddInputDocumentFunction; - - var PMFAddCaseNote = "PMFAddCaseNote"; - var PMFAddCaseNoteFunction = [PMFAddCaseNote+"($caseUid,$processUid,$taskUid,$userUid,$note,$sendMail)"]; - arrayFunctions[PMFAddCaseNote] = PMFAddCaseNoteFunction; - - var PMFGetCaseNotes = "PMFGetCaseNotes"; - var PMFGetCaseNotesFunction = [PMFGetCaseNotes+"($applicationID,$type,$userUid);",PMFGetCaseNotes+"($applicationID,$type)",PMFGetCaseNotes+"($applicationID)"]; - arrayFunctions[PMFGetCaseNotes] = PMFGetCaseNotesFunction; - - var phpPMFunctions = [formatDate,getCurrentDate,getCurrentTime,literalDate,capitalize,lowerCase,upperCase,userInfo,executeQuery,orderGrid, - evaluateFunction,PMFTaskCase,PMFTaskList,PMFUserList,PMFGroupList,PMFRoleList,PMFCaseList,PMFProcessList,PMFSendVariables,PMFDerivateCase, - PMFNewCaseImpersonate,PMFNewCase,PMFPauseCase,PMFUnpauseCase,PMFAssignUserToGroup,PMFCreateUser,PMFUpdateUser,PMFInformationUser, - generateCode,setCaseTrackerCode,jumping,PMFRedirectToStep,pauseCase,PMFSendMessage,PMFgetLabelOption,PMFGenerateOutputDocument, - PMFGetUserEmailAddress,PMFGetNextAssignedUser,PMFDeleteCase,PMFCancelCase,PMFAddInputDocument,PMFAddCaseNote,PMFGetCaseNotes]; - - var phpKeywords = ("break case catch continue default do else false for function " + - "if new return switch throw true try var while").split(SPACE); - - function getCompletions(functionName, keywords, options) { - - var found = []; - - function maybeAdd(str) {// for keywords ? - if ( str.indexOf(functionName) == 0 && !arrayContains(found, str)) { - found.push(str); - } - } - - function yesAdd(str) { - if ( !arrayContains(found, str)) { - found.push(str); - } - } - - arrayFunction = arrayFunctions[functionName]; - - if (arrayFunction != undefined) { - forEach( arrayFunction, yesAdd); - } else { - if (functionName.trim() == "") { - forEach (phpKeywords, yesAdd); - forEach (keywords, yesAdd); - } else if (functionName == "=") { - forEach (phpPMFunctions, yesAdd); - } else { - for (index = 0; index < phpKeywords.length; index++) { - if ( phpKeywords[index].indexOf(functionName) == 0 ) { - found.push(phpKeywords[index]); - } - } - forEach(keywords, maybeAdd); - } - } - return found; - } -})(); diff --git a/gulliver/js/codemirror/addon/hint/pig-hint.js b/gulliver/js/codemirror/addon/hint/pig-hint.js index 9847996be..6c0c52377 100644 --- a/gulliver/js/codemirror/addon/hint/pig-hint.js +++ b/gulliver/js/codemirror/addon/hint/pig-hint.js @@ -1,8 +1,10 @@ (function () { + "use strict"; + function forEach(arr, f) { for (var i = 0, e = arr.length; i < e; ++i) f(arr[i]); } - + function arrayContains(arr, item) { if (!Array.prototype.indexOf) { var i = arr.length; @@ -25,11 +27,11 @@ token = tprop = {start: cur.ch, end: cur.ch, string: "", state: token.state, className: token.string == ":" ? "pig-type" : null}; } - + if (!context) var context = []; context.push(tprop); - - var completionList = getCompletions(token, context); + + var completionList = getCompletions(token, context); completionList = completionList.sort(); //prevent autocomplete for last word, instead show dropdown with one word if(completionList.length == 1) { @@ -40,24 +42,26 @@ from: CodeMirror.Pos(cur.line, token.start), to: CodeMirror.Pos(cur.line, token.end)}; } - - CodeMirror.pigHint = function(editor) { + + function pigHint(editor) { return scriptHint(editor, pigKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); - }; - + } + CodeMirror.pigHint = pigHint; // deprecated + CodeMirror.registerHelper("hint", "pig", pigHint); + var pigKeywords = "VOID IMPORT RETURNS DEFINE LOAD FILTER FOREACH ORDER CUBE DISTINCT COGROUP " + "JOIN CROSS UNION SPLIT INTO IF OTHERWISE ALL AS BY USING INNER OUTER ONSCHEMA PARALLEL " + "PARTITION GROUP AND OR NOT GENERATE FLATTEN ASC DESC IS STREAM THROUGH STORE MAPREDUCE " - + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " + + "SHIP CACHE INPUT OUTPUT STDERROR STDIN STDOUT LIMIT SAMPLE LEFT RIGHT FULL EQ GT LT GTE LTE " + "NEQ MATCHES TRUE FALSE"; var pigKeywordsU = pigKeywords.split(" "); var pigKeywordsL = pigKeywords.toLowerCase().split(" "); - + var pigTypes = "BOOLEAN INT LONG FLOAT DOUBLE CHARARRAY BYTEARRAY BAG TUPLE MAP"; var pigTypesU = pigTypes.split(" "); var pigTypesL = pigTypes.toLowerCase().split(" "); - - var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " + + var pigBuiltins = "ABS ACOS ARITY ASIN ATAN AVG BAGSIZE BINSTORAGE BLOOM BUILDBLOOM CBRT CEIL " + "CONCAT COR COS COSH COUNT COUNT_STAR COV CONSTANTSIZE CUBEDIMENSIONS DIFF DISTINCT DOUBLEABS " + "DOUBLEAVG DOUBLEBASE DOUBLEMAX DOUBLEMIN DOUBLEROUND DOUBLESUM EXP FLOOR FLOATABS FLOATAVG " + "FLOATMAX FLOATMIN FLOATROUND FLOATSUM GENERICINVOKER INDEXOF INTABS INTAVG INTMAX INTMIN " @@ -66,9 +70,9 @@ + "LONGAVG LONGMAX LONGMIN LONGSUM MAX MIN MAPSIZE MONITOREDUDF NONDETERMINISTIC OUTPUTSCHEMA " + "PIGSTORAGE PIGSTREAMING RANDOM REGEX_EXTRACT REGEX_EXTRACT_ALL REPLACE ROUND SIN SINH SIZE " + "SQRT STRSPLIT SUBSTRING SUM STRINGCONCAT STRINGMAX STRINGMIN STRINGSIZE TAN TANH TOBAG " - + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER"; - var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" "); - var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" "); + + "TOKENIZE TOMAP TOP TOTUPLE TRIM TEXTLOADER TUPLESIZE UCFIRST UPPER UTF8STORAGECONVERTER"; + var pigBuiltinsU = pigBuiltins.split(" ").join("() ").split(" "); + var pigBuiltinsL = pigBuiltins.toLowerCase().split(" ").join("() ").split(" "); var pigBuiltinsC = ("BagSize BinStorage Bloom BuildBloom ConstantSize CubeDimensions DoubleAbs " + "DoubleAvg DoubleBase DoubleMax DoubleMin DoubleRound DoubleSum FloatAbs FloatAvg FloatMax " + "FloatMin FloatRound FloatSum GenericInvoker IntAbs IntAvg IntMax IntMin IntSum " @@ -76,13 +80,13 @@ + "IsEmpty JsonLoader JsonMetadata JsonStorage LongAbs LongAvg LongMax LongMin LongSum MapSize " + "MonitoredUDF Nondeterministic OutputSchema PigStorage PigStreaming StringConcat StringMax " + "StringMin StringSize TextLoader TupleSize Utf8StorageConverter").split(" ").join("() ").split(" "); - + function getCompletions(token, context) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } - + function gatherCompletions(obj) { if(obj == ":") { forEach(pigTypesL, maybeAdd); @@ -103,11 +107,11 @@ // find in the current environment. var obj = context.pop(), base; - if (obj.type == "variable") + if (obj.type == "variable") base = obj.string; else if(obj.type == "variable-3") base = ":" + obj.string; - + while (base != null && context.length) base = base[context.pop().string]; if (base != null) gatherCompletions(base); diff --git a/gulliver/js/codemirror/addon/hint/python-hint.js b/gulliver/js/codemirror/addon/hint/python-hint.js index 60221b89e..248284e8c 100644 --- a/gulliver/js/codemirror/addon/hint/python-hint.js +++ b/gulliver/js/codemirror/addon/hint/python-hint.js @@ -31,19 +31,17 @@ var completionList = getCompletions(token, context); completionList = completionList.sort(); - //prevent autocomplete for last word, instead show dropdown with one word - if(completionList.length == 1) { - completionList.push(" "); - } return {list: completionList, from: CodeMirror.Pos(cur.line, token.start), to: CodeMirror.Pos(cur.line, token.end)}; } - CodeMirror.pythonHint = function(editor) { + function pythonHint(editor) { return scriptHint(editor, pythonKeywordsU, function (e, cur) {return e.getTokenAt(cur);}); - }; + } + CodeMirror.pythonHint = pythonHint; // deprecated + CodeMirror.registerHelper("hint", "python", pythonHint); var pythonKeywords = "and del from not while as elif global or with assert else if pass yield" + "break except import print class exec in raise continue finally is return def for lambda try"; @@ -64,7 +62,7 @@ function getCompletions(token, context) { var found = [], start = token.string; function maybeAdd(str) { - if (str.indexOf(start) == 0 && !arrayContains(found, str)) found.push(str); + if (str.lastIndexOf(start, 0) == 0 && !arrayContains(found, str)) found.push(str); } function gatherCompletions(_obj) { diff --git a/gulliver/js/codemirror/addon/hint/show-hint.css b/gulliver/js/codemirror/addon/hint/show-hint.css index 47fb7921f..8a4ff052e 100644 --- a/gulliver/js/codemirror/addon/hint/show-hint.css +++ b/gulliver/js/codemirror/addon/hint/show-hint.css @@ -25,7 +25,7 @@ margin: 0; padding: 0 4px; border-radius: 2px; - max-width: 150em; + max-width: 19em; overflow: hidden; white-space: pre; color: black; diff --git a/gulliver/js/codemirror/addon/hint/show-hint.js b/gulliver/js/codemirror/addon/hint/show-hint.js index 42e8caac7..7e03d1139 100644 --- a/gulliver/js/codemirror/addon/hint/show-hint.js +++ b/gulliver/js/codemirror/addon/hint/show-hint.js @@ -1,80 +1,199 @@ -CodeMirror.showHint = function(cm, getHints, options) { - if (!options) options = {}; - var startCh = cm.getCursor().ch, continued = false; - var closeOn = options.closeCharacters || /[\s()\[\]{};:]/; +(function() { + "use strict"; - function startHinting() { + var HINT_ELEMENT_CLASS = "CodeMirror-hint"; + var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active"; + + CodeMirror.showHint = function(cm, getHints, options) { // We want a single cursor position. if (cm.somethingSelected()) return; + if (getHints == null) { + if (options && options.async) return; + else getHints = CodeMirror.hint.auto; + } - if (options.async) - getHints(cm, showHints, options); + if (cm.state.completionActive) cm.state.completionActive.close(); + + var completion = cm.state.completionActive = new Completion(cm, getHints, options || {}); + CodeMirror.signal(cm, "startCompletion", cm); + if (completion.options.async) + getHints(cm, function(hints) { completion.showHints(hints); }, completion.options); else - return showHints(getHints(cm, options)); + return completion.showHints(getHints(cm, completion.options)); + }; + + function Completion(cm, getHints, options) { + this.cm = cm; + this.getHints = getHints; + this.options = options; + this.widget = this.onClose = null; } + Completion.prototype = { + close: function() { + if (!this.active()) return; + this.cm.state.completionActive = null; + + if (this.widget) this.widget.close(); + if (this.onClose) this.onClose(); + CodeMirror.signal(this.cm, "endCompletion", this.cm); + }, + + active: function() { + return this.cm.state.completionActive == this; + }, + + pick: function(data, i) { + var completion = data.list[i]; + if (completion.hint) completion.hint(this.cm, data, completion); + else this.cm.replaceRange(getText(completion), data.from, data.to); + CodeMirror.signal(data, "pick", completion); + this.close(); + }, + + showHints: function(data) { + if (!data || !data.list.length || !this.active()) return this.close(); + + if (this.options.completeSingle != false && data.list.length == 1) + this.pick(data, 0); + else + this.showWidget(data); + }, + + showWidget: function(data) { + this.widget = new Widget(this, data); + CodeMirror.signal(data, "shown"); + + var debounce = 0, completion = this, finished; + var closeOn = this.options.closeCharacters || /[\s()\[\]{};:>,]/; + var startPos = this.cm.getCursor(), startLen = this.cm.getLine(startPos.line).length; + + var requestAnimationFrame = window.requestAnimationFrame || function(fn) { + return setTimeout(fn, 1000/60); + }; + var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout; + + function done() { + if (finished) return; + finished = true; + completion.close(); + completion.cm.off("cursorActivity", activity); + if (data) CodeMirror.signal(data, "close"); + } + + function update() { + if (finished) return; + CodeMirror.signal(data, "update"); + if (completion.options.async) + completion.getHints(completion.cm, finishUpdate, completion.options); + else + finishUpdate(completion.getHints(completion.cm, completion.options)); + } + function finishUpdate(data_) { + data = data_; + if (finished) return; + if (!data || !data.list.length) return done(); + completion.widget = new Widget(completion, data); + } + + function clearDebounce() { + if (debounce) { + cancelAnimationFrame(debounce); + debounce = 0; + } + } + + function activity() { + clearDebounce(); + var pos = completion.cm.getCursor(), line = completion.cm.getLine(pos.line); + if (pos.line != startPos.line || line.length - pos.ch != startLen - startPos.ch || + pos.ch < startPos.ch || completion.cm.somethingSelected() || + (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) { + completion.close(); + } else { + debounce = requestAnimationFrame(update); + if (completion.widget) completion.widget.close(); + } + } + this.cm.on("cursorActivity", activity); + this.onClose = done; + } + }; + function getText(completion) { if (typeof completion == "string") return completion; else return completion.text; } - function pickCompletion(cm, data, completion) { - if (completion.hint) completion.hint(cm, data, completion); - else cm.replaceRange(getText(completion), data.from, data.to); + function buildKeyMap(options, handle) { + var baseMap = { + Up: function() {handle.moveFocus(-1);}, + Down: function() {handle.moveFocus(1);}, + PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);}, + PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);}, + Home: function() {handle.setFocus(0);}, + End: function() {handle.setFocus(handle.length - 1);}, + Enter: handle.pick, + Tab: handle.pick, + Esc: handle.close + }; + var ourMap = options.customKeys ? {} : baseMap; + function addBinding(key, val) { + var bound; + if (typeof val != "string") + bound = function(cm) { return val(cm, handle); }; + // This mechanism is deprecated + else if (baseMap.hasOwnProperty(val)) + bound = baseMap[val]; + else + bound = val; + ourMap[key] = bound; + } + if (options.customKeys) + for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key)) + addBinding(key, options.customKeys[key]); + if (options.extraKeys) + for (var key in options.extraKeys) if (options.extraKeys.hasOwnProperty(key)) + addBinding(key, options.extraKeys[key]); + return ourMap; } - function getMaxAccordingList(list) { - var max = 0; - for (var i = 0; i < list.length; i++) { - if (list[i].length > max) - max = list[i].length; + function getHintElement(hintsElement, el) { + while (el && el != hintsElement) { + if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el; + el = el.parentNode; } - return max; } - function showHints(data) { - if (!data || !data.list.length) return; - var completions = data.list; - var maxWidth = getMaxAccordingList(data.list); - // When there is only one completion, use it directly. - if (!continued && options.completeSingle !== false && completions.length == 1) { - pickCompletion(cm, data, completions[0]); - return true; - } + function Widget(completion, data) { + this.completion = completion; + this.data = data; + var widget = this, cm = completion.cm, options = completion.options; - // Build the select widget - var hints = document.createElement("ul"), selectedHint = 0; + var hints = this.hints = document.createElement("ul"); hints.className = "CodeMirror-hints"; - for (var i = 0; i < completions.length; ++i) { - var elt = hints.appendChild(document.createElement("li")), completion = completions[i]; - var className = "CodeMirror-hint" + (i ? "" : " CodeMirror-hint-active"); - if (completion.className != null) className = completion.className + " " + className; + this.selectedHint = options.getDefaultSelection ? options.getDefaultSelection(cm,options,data) : 0; + + var completions = data.list; + for (var i = 0; i < completions.length; ++i) { + var elt = hints.appendChild(document.createElement("li")), cur = completions[i]; + var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS); + if (cur.className != null) className = cur.className + " " + className; elt.className = className; - if (completion.render) {completion.render(elt, data, completion);} - else elt.appendChild(document.createTextNode(getText(completion))); + if (cur.render) cur.render(elt, data, cur); + else elt.appendChild(document.createTextNode(cur.displayText || getText(cur))); elt.hintId = i; } - + var pos = cm.cursorCoords(options.alignWithWord !== false ? data.from : null); var left = pos.left, top = pos.bottom, below = true; hints.style.left = left + "px"; hints.style.top = top + "px"; - hints.style.width = (maxWidth * 18) + "px"; - - ie = /MSIE \d/.test(navigator.userAgent); - if( ie.length > 0 ) { - hints.style.left = (left + 2) + "px"; - hints.style.top = (top + 20) + "px"; - } - - hints.style.zIndex = "5000"; - document.body.appendChild(hints); - // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor. var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth); var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); + (options.container || document.body).appendChild(hints); var box = hints.getBoundingClientRect(); - var overlapX = box.right - winW, overlapY = box.bottom - winH; if (overlapX > 0) { if (box.right - box.left > winW) { @@ -95,99 +214,122 @@ CodeMirror.showHint = function(cm, getHints, options) { hints.style.top = (top = pos.bottom - overlapY) + "px"; } - function changeActive(i) { - i = Math.max(0, Math.min(i, completions.length - 1)); - if (selectedHint == i) return; - var node = hints.childNodes[selectedHint]; - node.className = node.className.replace(" CodeMirror-hint-active", ""); - node = hints.childNodes[selectedHint = i]; - node.className += " CodeMirror-hint-active"; - if (node.offsetTop < hints.scrollTop) - hints.scrollTop = node.offsetTop - 3; - else if (node.offsetTop + node.offsetHeight > hints.scrollTop + hints.clientHeight) - hints.scrollTop = node.offsetTop + node.offsetHeight - hints.clientHeight + 3; + cm.addKeyMap(this.keyMap = buildKeyMap(options, { + moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); }, + setFocus: function(n) { widget.changeActive(n); }, + menuSize: function() { return widget.screenAmount(); }, + length: completions.length, + close: function() { completion.close(); }, + pick: function() { widget.pick(); } + })); + + if (options.closeOnUnfocus !== false) { + var closingOnBlur; + cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); }); + cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); }); } - function screenAmount() { - return Math.floor(hints.clientHeight / hints.firstChild.offsetHeight) || 1; - } - - var ourMap = { - Up: function() {changeActive(selectedHint - 1);}, - Down: function() {changeActive(selectedHint + 1);}, - PageUp: function() {changeActive(selectedHint - screenAmount());}, - PageDown: function() {changeActive(selectedHint + screenAmount());}, - Home: function() {changeActive(0);}, - End: function() {changeActive(completions.length - 1);}, - Enter: pick, - Tab: pick, - Esc: close - }; - if (options.customKeys) for (var key in options.customKeys) if (options.customKeys.hasOwnProperty(key)) { - var val = options.customKeys[key]; - if (/^(Up|Down|Enter|Esc)$/.test(key)) val = ourMap[val]; - ourMap[key] = val; - } - - cm.addKeyMap(ourMap); - cm.on("cursorActivity", cursorActivity); - var closingOnBlur; - function onBlur(){ closingOnBlur = setTimeout(close, 100); }; - function onFocus(){ clearTimeout(closingOnBlur); }; - cm.on("blur", onBlur); - cm.on("focus", onFocus); var startScroll = cm.getScrollInfo(); - function onScroll() { + cm.on("scroll", this.onScroll = function() { var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect(); - var newTop = top + startScroll.top - curScroll.top, point = newTop; + var newTop = top + startScroll.top - curScroll.top; + var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop); if (!below) point += hints.offsetHeight; - if (point <= editor.top || point >= editor.bottom) return close(); + if (point <= editor.top || point >= editor.bottom) return completion.close(); hints.style.top = newTop + "px"; hints.style.left = (left + startScroll.left - curScroll.left) + "px"; - } - cm.on("scroll", onScroll); + }); + CodeMirror.on(hints, "dblclick", function(e) { - var t = e.target || e.srcElement; - if (t.hintId != null) {selectedHint = t.hintId; pick();} + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();} }); + CodeMirror.on(hints, "click", function(e) { - var t = e.target || e.srcElement; - if (t.hintId != null) changeActive(t.hintId); + var t = getHintElement(hints, e.target || e.srcElement); + if (t && t.hintId != null) { + widget.changeActive(t.hintId); + if (options.completeOnSingleClick) widget.pick(); + } }); + CodeMirror.on(hints, "mousedown", function() { setTimeout(function(){cm.focus();}, 20); }); - var done = false, once; - function close() { - if (done) return; - done = true; - clearTimeout(once); - hints.parentNode.removeChild(hints); - cm.removeKeyMap(ourMap); - cm.off("cursorActivity", cursorActivity); - cm.off("blur", onBlur); - cm.off("focus", onFocus); - cm.off("scroll", onScroll); - } - function pick() { - pickCompletion(cm, data, completions[selectedHint]); - close(); - } - var once, lastPos = cm.getCursor(), lastLen = cm.getLine(lastPos.line).length; - function cursorActivity() { - clearTimeout(once); - - var pos = cm.getCursor(), line = cm.getLine(pos.line); - if (pos.line != lastPos.line || line.length - pos.ch != lastLen - lastPos.ch || - pos.ch < startCh || cm.somethingSelected() || - (pos.ch && closeOn.test(line.charAt(pos.ch - 1)))) - close(); - else - once = setTimeout(function(){close(); continued = true; startHinting();}, 70); - } + CodeMirror.signal(data, "select", completions[0], hints.firstChild); return true; } - return startHinting(); -}; + Widget.prototype = { + close: function() { + if (this.completion.widget != this) return; + this.completion.widget = null; + this.hints.parentNode.removeChild(this.hints); + this.completion.cm.removeKeyMap(this.keyMap); + + var cm = this.completion.cm; + if (this.completion.options.closeOnUnfocus !== false) { + cm.off("blur", this.onBlur); + cm.off("focus", this.onFocus); + } + cm.off("scroll", this.onScroll); + }, + + pick: function() { + this.completion.pick(this.data, this.selectedHint); + }, + + changeActive: function(i, avoidWrap) { + if (i >= this.data.list.length) + i = avoidWrap ? this.data.list.length - 1 : 0; + else if (i < 0) + i = avoidWrap ? 0 : this.data.list.length - 1; + if (this.selectedHint == i) return; + var node = this.hints.childNodes[this.selectedHint]; + node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, ""); + node = this.hints.childNodes[this.selectedHint = i]; + node.className += " " + ACTIVE_HINT_ELEMENT_CLASS; + if (node.offsetTop < this.hints.scrollTop) + this.hints.scrollTop = node.offsetTop - 3; + else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight) + this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3; + CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node); + }, + + screenAmount: function() { + return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1; + } + }; + + CodeMirror.registerHelper("hint", "auto", function(cm, options) { + var helpers = cm.getHelpers(cm.getCursor(), "hint"); + if (helpers.length) { + for (var i = 0; i < helpers.length; i++) { + var cur = helpers[i](cm, options); + if (cur && cur.list.length) return cur; + } + } else { + var words = cm.getHelper(cm.getCursor(), "hintWords"); + if (words) return CodeMirror.hint.fromList(cm, {words: words}); + } + }); + + CodeMirror.registerHelper("hint", "fromList", function(cm, options) { + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var found = []; + for (var i = 0; i < options.words.length; i++) { + var word = options.words[i]; + if (word.slice(0, token.string.length) == token.string) + found.push(word); + } + + if (found.length) return { + list: found, + from: CodeMirror.Pos(cur.line, token.start), + to: CodeMirror.Pos(cur.line, token.end) + }; + }); + + CodeMirror.commands.autocomplete = CodeMirror.showHint; +})(); diff --git a/gulliver/js/codemirror/addon/hint/sql-hint.js b/gulliver/js/codemirror/addon/hint/sql-hint.js new file mode 100644 index 000000000..d00781cff --- /dev/null +++ b/gulliver/js/codemirror/addon/hint/sql-hint.js @@ -0,0 +1,146 @@ +(function () { + "use strict"; + + var tables; + var keywords; + var CONS = { + QUERY_DIV: ";", + ALIAS_KEYWORD: "AS" + }; + + function getKeywords(editor) { + var mode = editor.doc.modeOption; + if(mode === "sql") mode = "text/x-sql"; + return CodeMirror.resolveMode(mode).keywords; + } + + function match(string, word) { + var len = string.length; + var sub = word.substr(0, len); + return string.toUpperCase() === sub.toUpperCase(); + } + + function addMatches(result, search, wordlist, formatter) { + for(var word in wordlist) { + if(!wordlist.hasOwnProperty(word)) continue; + if(Array.isArray(wordlist)) { + word = wordlist[word]; + } + if(match(search, word)) { + result.push(formatter(word)); + } + } + } + + function columnCompletion(result, editor) { + var cur = editor.getCursor(); + var token = editor.getTokenAt(cur); + var string = token.string.substr(1); + var prevCur = CodeMirror.Pos(cur.line, token.start); + var table = editor.getTokenAt(prevCur).string; + if( !tables.hasOwnProperty( table ) ){ + table = findTableByAlias(table, editor); + } + var columns = tables[table]; + if(!columns) { + return; + } + addMatches(result, string, columns, + function(w) {return "." + w;}); + } + + function eachWord(lineText, f) { + if( !lineText ){return;} + var excepted = /[,;]/g; + var words = lineText.split( " " ); + for( var i = 0; i < words.length; i++ ){ + f( words[i]?words[i].replace( excepted, '' ) : '' ); + } + } + + function convertCurToNumber( cur ){ + // max characters of a line is 999,999. + return cur.line + cur.ch / Math.pow( 10, 6 ); + } + + function convertNumberToCur( num ){ + return CodeMirror.Pos(Math.floor( num ), +num.toString().split( '.' ).pop()); + } + + function findTableByAlias(alias, editor) { + var doc = editor.doc; + var fullQuery = doc.getValue(); + var aliasUpperCase = alias.toUpperCase(); + var previousWord = ""; + var table = ""; + var separator = []; + var validRange = { + start: CodeMirror.Pos( 0, 0 ), + end: CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).length ) + }; + + //add separator + var indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV ); + while( indexOfSeparator != -1 ){ + separator.push( doc.posFromIndex(indexOfSeparator)); + indexOfSeparator = fullQuery.indexOf( CONS.QUERY_DIV, indexOfSeparator+1); + } + separator.unshift( CodeMirror.Pos( 0, 0 ) ); + separator.push( CodeMirror.Pos( editor.lastLine(), editor.getLineHandle( editor.lastLine() ).text.length ) ); + + //find valieRange + var prevItem = 0; + var current = convertCurToNumber( editor.getCursor() ); + for( var i=0; i< separator.length; i++){ + var _v = convertCurToNumber( separator[i] ); + if( current > prevItem && current <= _v ){ + validRange = { start: convertNumberToCur( prevItem ), end: convertNumberToCur( _v ) }; + break; + } + prevItem = _v; + } + + var query = doc.getRange(validRange.start, validRange.end, false); + + for(var i=0; i < query.length; i++){ + var lineText = query[i]; + eachWord( lineText, function( word ){ + var wordUpperCase = word.toUpperCase(); + if( wordUpperCase === aliasUpperCase && tables.hasOwnProperty( previousWord ) ){ + table = previousWord; + } + if( wordUpperCase !== CONS.ALIAS_KEYWORD ){ + previousWord = word; + } + }); + if( table ){ break; } + } + return table; + } + + function sqlHint(editor, options) { + tables = (options && options.tables) || {}; + keywords = keywords || getKeywords(editor); + var cur = editor.getCursor(); + var token = editor.getTokenAt(cur); + var result = []; + var search = token.string.trim(); + + addMatches(result, search, keywords, + function(w) {return w.toUpperCase();}); + + addMatches(result, search, tables, + function(w) {return w;}); + + if(search.lastIndexOf('.') === 0) { + columnCompletion(result, editor); + } + + return { + list: result, + from: CodeMirror.Pos(cur.line, token.start), + to: CodeMirror.Pos(cur.line, token.end) + }; + } + CodeMirror.registerHelper("hint", "sql", sqlHint); +})(); diff --git a/gulliver/js/codemirror/addon/hint/xml-hint.js b/gulliver/js/codemirror/addon/hint/xml-hint.js index 42eab6f3b..69f2b771f 100644 --- a/gulliver/js/codemirror/addon/hint/xml-hint.js +++ b/gulliver/js/codemirror/addon/hint/xml-hint.js @@ -1,118 +1,69 @@ (function() { + "use strict"; - CodeMirror.xmlHints = []; + var Pos = CodeMirror.Pos; - CodeMirror.xmlHint = function(cm) { - - var cursor = cm.getCursor(); - - if (cursor.ch > 0) { - - var text = cm.getRange(CodeMirror.Pos(0, 0), cursor); - var typed = ''; - var simbol = ''; - for(var i = text.length - 1; i >= 0; i--) { - if(text[i] == ' ' || text[i] == '<') { - simbol = text[i]; - break; - } - else { - typed = text[i] + typed; - } - } - - text = text.slice(0, text.length - typed.length); - - var path = getActiveElement(text) + simbol; - var hints = CodeMirror.xmlHints[path]; - - if(typeof hints === 'undefined') - hints = ['']; - else { - hints = hints.slice(0); - for (var i = hints.length - 1; i >= 0; i--) { - if(hints[i].indexOf(typed) != 0) - hints.splice(i, 1); - } - } - - return { - list: hints, - from: CodeMirror.Pos(cursor.line, cursor.ch - typed.length), - to: cursor - }; + function getHints(cm, options) { + var tags = options && options.schemaInfo; + var quote = (options && options.quoteChar) || '"'; + if (!tags) return; + var cur = cm.getCursor(), token = cm.getTokenAt(cur); + var inner = CodeMirror.innerMode(cm.getMode(), token.state); + if (inner.mode.name != "xml") return; + var result = [], replaceToken = false, prefix; + var isTag = token.string.charAt(0) == "<"; + if (!inner.state.tagName || isTag) { // Tag completion + if (isTag) { + prefix = token.string.slice(1); + replaceToken = true; + } + var cx = inner.state.context, curTag = cx && tags[cx.tagName]; + var childList = cx ? curTag && curTag.children : tags["!top"]; + if (childList) { + for (var i = 0; i < childList.length; ++i) if (!prefix || childList[i].lastIndexOf(prefix, 0) == 0) + result.push("<" + childList[i]); + } else { + for (var name in tags) if (tags.hasOwnProperty(name) && name != "!top" && (!prefix || name.lastIndexOf(prefix, 0) == 0)) + result.push("<" + name); + } + if (cx && (!prefix || ("/" + cx.tagName).lastIndexOf(prefix, 0) == 0)) + result.push(""); + } else { + // Attribute completion + var curTag = tags[inner.state.tagName], attrs = curTag && curTag.attrs; + if (!attrs) return; + if (token.type == "string" || token.string == "=") { // A value + var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)), + Pos(cur.line, token.type == "string" ? token.start : token.end)); + var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues; + if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return; + if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget + if (token.type == "string") { + prefix = token.string; + if (/['"]/.test(token.string.charAt(0))) { + quote = token.string.charAt(0); + prefix = token.string.slice(1); + } + replaceToken = true; } - }; - - var getActiveElement = function(text) { - - var element = ''; - - if(text.length >= 0) { - - var regex = new RegExp('<([^!?][^\\s/>]*)[\\s\\S]*?>', 'g'); - - var matches = []; - var match; - while ((match = regex.exec(text)) != null) { - matches.push({ - tag: match[1], - selfclose: (match[0].slice(match[0].length - 2) === '/>') - }); - } - - for (var i = matches.length - 1, skip = 0; i >= 0; i--) { - - var item = matches[i]; - - if (item.tag[0] == '/') - { - skip++; - } - else if (item.selfclose == false) - { - if (skip > 0) - { - skip--; - } - else - { - element = '<' + item.tag + '>' + element; - } - } - } - - element += getOpenTag(text); + for (var i = 0; i < atValues.length; ++i) if (!prefix || atValues[i].lastIndexOf(prefix, 0) == 0) + result.push(quote + atValues[i] + quote); + } else { // An attribute name + if (token.type == "attribute") { + prefix = token.string; + replaceToken = true; } - - return element; - }; - - var getOpenTag = function(text) { - - var open = text.lastIndexOf('<'); - var close = text.lastIndexOf('>'); - - if (close < open) - { - text = text.slice(open); - - if(text != '<') { - - var space = text.indexOf(' '); - if(space < 0) - space = text.indexOf('\t'); - if(space < 0) - space = text.indexOf('\n'); - - if (space < 0) - space = text.length; - - return text.slice(0, space); - } - } - - return ''; + for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || attr.lastIndexOf(prefix, 0) == 0)) + result.push(attr); + } + } + return { + list: result, + from: replaceToken ? Pos(cur.line, token.start) : cur, + to: replaceToken ? Pos(cur.line, token.end) : cur }; + } + CodeMirror.xmlHint = getHints; // deprecated + CodeMirror.registerHelper("hint", "xml", getHints); })(); diff --git a/gulliver/js/codemirror/addon/lint/coffeescript-lint.js b/gulliver/js/codemirror/addon/lint/coffeescript-lint.js new file mode 100644 index 000000000..7f55a294d --- /dev/null +++ b/gulliver/js/codemirror/addon/lint/coffeescript-lint.js @@ -0,0 +1,27 @@ +// Depends on coffeelint.js from http://www.coffeelint.org/js/coffeelint.js + +// declare global: coffeelint + +CodeMirror.registerHelper("lint", "coffeescript", function(text) { + var found = []; + var parseError = function(err) { + var loc = err.lineNumber; + found.push({from: CodeMirror.Pos(loc-1, 0), + to: CodeMirror.Pos(loc, 0), + severity: err.level, + message: err.message}); + }; + try { + var res = coffeelint.lint(text); + for(var i = 0; i < res.length; i++) { + parseError(res[i]); + } + } catch(e) { + found.push({from: CodeMirror.Pos(e.location.first_line, 0), + to: CodeMirror.Pos(e.location.last_line, e.location.last_column), + severity: 'error', + message: e.message}); + } + return found; +}); +CodeMirror.coffeeValidator = CodeMirror.lint.coffeescript; // deprecated diff --git a/gulliver/js/codemirror/addon/lint/css-lint.js b/gulliver/js/codemirror/addon/lint/css-lint.js new file mode 100644 index 000000000..1de71fbcb --- /dev/null +++ b/gulliver/js/codemirror/addon/lint/css-lint.js @@ -0,0 +1,19 @@ +// Depends on csslint.js from https://github.com/stubbornella/csslint + +// declare global: CSSLint + +CodeMirror.registerHelper("lint", "css", function(text) { + var found = []; + var results = CSSLint.verify(text), messages = results.messages, message = null; + for ( var i = 0; i < messages.length; i++) { + message = messages[i]; + var startLine = message.line -1, endLine = message.line -1, startCol = message.col -1, endCol = message.col; + found.push({ + from: CodeMirror.Pos(startLine, startCol), + to: CodeMirror.Pos(endLine, endCol), + message: message.message, + severity : message.type + }); + } + return found; +}); diff --git a/gulliver/js/codemirror/addon/lint/javascript-lint.js b/gulliver/js/codemirror/addon/lint/javascript-lint.js index db9ff6b22..7123ab7e6 100644 --- a/gulliver/js/codemirror/addon/lint/javascript-lint.js +++ b/gulliver/js/codemirror/addon/lint/javascript-lint.js @@ -1,26 +1,25 @@ (function() { + "use strict"; + // declare global: JSHINT var bogus = [ "Dangerous comment" ]; var warnings = [ [ "Expected '{'", - "Statement body should be inside '{ }' braces." ] ]; + "Statement body should be inside '{ }' braces." ] ]; var errors = [ "Missing semicolon", "Extra comma", "Missing property name", - "Unmatched ", " and instead saw", " is not defined", - "Unclosed string", "Stopping, unable to continue" ]; + "Unmatched ", " and instead saw", " is not defined", + "Unclosed string", "Stopping, unable to continue" ]; - function validator(options, text) { + function validator(text, options) { JSHINT(text, options); var errors = JSHINT.data().errors, result = []; if (errors) parseErrors(errors, result); return result; } - CodeMirror.javascriptValidatorWithOptions = function(options) { - return function(text) { return validator(options, text); }; - }; - - CodeMirror.javascriptValidator = CodeMirror.javascriptValidatorWithOptions(null); + CodeMirror.registerHelper("lint", "javascript", validator); + CodeMirror.javascriptValidator = CodeMirror.lint.javascript; // deprecated function cleanup(error) { // All problems are warnings by default @@ -42,10 +41,10 @@ found = description.indexOf(find) !== -1; if (force || found) { - error.severity = severity; + error.severity = severity; } if (found && replace) { - error.description = replace; + error.description = replace; } } } @@ -54,7 +53,7 @@ var description = error.description; for ( var i = 0; i < bogus.length; i++) { if (description.indexOf(bogus[i]) !== -1) { - return true; + return true; } } return false; @@ -64,59 +63,59 @@ for ( var i = 0; i < errors.length; i++) { var error = errors[i]; if (error) { - var linetabpositions, index; + var linetabpositions, index; - linetabpositions = []; + linetabpositions = []; - // This next block is to fix a problem in jshint. Jshint - // replaces - // all tabs with spaces then performs some checks. The error - // positions (character/space) are then reported incorrectly, - // not taking the replacement step into account. Here we look - // at the evidence line and try to adjust the character position - // to the correct value. - if (error.evidence) { - // Tab positions are computed once per line and cached - var tabpositions = linetabpositions[error.line]; - if (!tabpositions) { - var evidence = error.evidence; - tabpositions = []; - // ugggh phantomjs does not like this - // forEachChar(evidence, function(item, index) { - Array.prototype.forEach.call(evidence, function(item, - index) { - if (item === '\t') { - // First col is 1 (not 0) to match error - // positions - tabpositions.push(index + 1); - } - }); - linetabpositions[error.line] = tabpositions; - } - if (tabpositions.length > 0) { - var pos = error.character; - tabpositions.forEach(function(tabposition) { - if (pos > tabposition) pos -= 1; - }); - error.character = pos; - } - } + // This next block is to fix a problem in jshint. Jshint + // replaces + // all tabs with spaces then performs some checks. The error + // positions (character/space) are then reported incorrectly, + // not taking the replacement step into account. Here we look + // at the evidence line and try to adjust the character position + // to the correct value. + if (error.evidence) { + // Tab positions are computed once per line and cached + var tabpositions = linetabpositions[error.line]; + if (!tabpositions) { + var evidence = error.evidence; + tabpositions = []; + // ugggh phantomjs does not like this + // forEachChar(evidence, function(item, index) { + Array.prototype.forEach.call(evidence, function(item, + index) { + if (item === '\t') { + // First col is 1 (not 0) to match error + // positions + tabpositions.push(index + 1); + } + }); + linetabpositions[error.line] = tabpositions; + } + if (tabpositions.length > 0) { + var pos = error.character; + tabpositions.forEach(function(tabposition) { + if (pos > tabposition) pos -= 1; + }); + error.character = pos; + } + } - var start = error.character - 1, end = start + 1; - if (error.evidence) { - index = error.evidence.substring(start).search(/.\b/); - if (index > -1) { - end += index; - } - } + var start = error.character - 1, end = start + 1; + if (error.evidence) { + index = error.evidence.substring(start).search(/.\b/); + if (index > -1) { + end += index; + } + } - // Convert to format expected by validation service - error.description = error.reason;// + "(jshint)"; - error.start = error.character; - error.end = end; - error = cleanup(error); + // Convert to format expected by validation service + error.description = error.reason;// + "(jshint)"; + error.start = error.character; + error.end = end; + error = cleanup(error); - if (error) + if (error) output.push({message: error.description, severity: error.severity, from: CodeMirror.Pos(error.line - 1, start), diff --git a/gulliver/js/codemirror/addon/lint/json-lint.js b/gulliver/js/codemirror/addon/lint/json-lint.js index 42b36abb3..e10e11bca 100644 --- a/gulliver/js/codemirror/addon/lint/json-lint.js +++ b/gulliver/js/codemirror/addon/lint/json-lint.js @@ -1,6 +1,8 @@ // Depends on jsonlint.js from https://github.com/zaach/jsonlint -CodeMirror.jsonValidator = function(text) { +// declare global: jsonlint + +CodeMirror.registerHelper("lint", "json", function(text) { var found = []; jsonlint.parseError = function(str, hash) { var loc = hash.loc; @@ -11,4 +13,5 @@ CodeMirror.jsonValidator = function(text) { try { jsonlint.parse(text); } catch(e) {} return found; -}; +}); +CodeMirror.jsonValidator = CodeMirror.lint.json; // deprecated diff --git a/gulliver/js/codemirror/addon/lint/lint.css b/gulliver/js/codemirror/addon/lint/lint.css index 4fd72e774..414a9a0e0 100644 --- a/gulliver/js/codemirror/addon/lint/lint.css +++ b/gulliver/js/codemirror/addon/lint/lint.css @@ -14,6 +14,7 @@ padding: 2px 5px; position: fixed; white-space: pre; + white-space: pre-wrap; z-index: 100; max-width: 600px; opacity: 0; @@ -24,18 +25,18 @@ -ms-transition: opacity .4s; } -.CodeMirror-lint-span-error, .CodeMirror-lint-span-warning { +.CodeMirror-lint-mark-error, .CodeMirror-lint-mark-warning { background-position: left bottom; background-repeat: repeat-x; } -.CodeMirror-lint-span-error { +.CodeMirror-lint-mark-error { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJDw4cOCW1/KIAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAHElEQVQI12NggIL/DAz/GdA5/xkY/qPKMDAwAADLZwf5rvm+LQAAAABJRU5ErkJggg==") ; } -.CodeMirror-lint-span-warning { +.CodeMirror-lint-mark-warning { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAYAAAC09K7GAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJFhQXEbhTg7YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAMklEQVQI12NkgIIvJ3QXMjAwdDN+OaEbysDA4MPAwNDNwMCwiOHLCd1zX07o6kBVGQEAKBANtobskNMAAAAASUVORK5CYII="); } @@ -57,40 +58,16 @@ } .CodeMirror-lint-marker-error, .CodeMirror-lint-message-error { - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAPVvcvWHiPVucvRuc+ttcfV6f91KVN5LU99PV/FZY/JhaM4oN84pONE4Rd1ATfJLWutVYPRgbdxpcsgWKMgZKs4lNfE/UvE/U+artcpdSc5uXveimslHPuBhW/eJhfV5efaCgO2CgP+/v+PExP///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACUALAAAAAAQABAAAAZ+wJJwSCwaScgkySgkjTQZTkYzWhadnE5oE+pwqkSshwQqkzxfa4kkQXxEpA9J9EFI1KQGQQBAigYCBA14ExEWF0gXihETeA0QD3AkD5QQg0NsDnAJmwkOd5gYFSQKpXAFDBhqaxgLBwQBBAapq00YEg0UDRKqTGtKSL7Cw8JBADs="); + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAHlBMVEW7AAC7AACxAAC7AAC7AAAAAAC4AAC5AAD///+7AAAUdclpAAAABnRSTlMXnORSiwCK0ZKSAAAATUlEQVR42mWPOQ7AQAgDuQLx/z8csYRmPRIFIwRGnosRrpamvkKi0FTIiMASR3hhKW+hAN6/tIWhu9PDWiTGNEkTtIOucA5Oyr9ckPgAWm0GPBog6v4AAAAASUVORK5CYII="); } .CodeMirror-lint-marker-warning, .CodeMirror-lint-message-warning { - background-image: url("data:image/gif;base64,R0lGODlhEAAQANUAAP7bc//egf/ij/7ijv/jl/7kl//mnv7lnv/uwf7CTP7DTf7DT/7IW//Na/7Na//NbP7QdP/dmbltAIJNAF03AMSAJMSCLKqASa2DS6uBSquCSrGHTq6ETbCHT7WKUrKIUcCVXL+UXMOYX8GWXsSZYMiib6+ETbOIUcOXX86uhd3Muf///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAACsALAAAAAAQABAAAAZowJVwSCwaj0ihikRSJYcoBEL0XKlGkcjImQQhJBREKFnyICoThKeE/AAW6AXgdPyUAgrLJBEo0YsbAQyDhAEdRRwDDw8OaA4NDQImRBgFEJdglxAEGEQZKQcHBqOkKRpFF6mqq1WtrUEAOw=="); + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAANlBMVEX/uwDvrwD/uwD/uwD/uwD/uwD/uwD/uwD/uwD6twD/uwAAAADurwD2tQD7uAD+ugAAAAD/uwDhmeTRAAAADHRSTlMJ8mN1EYcbmiixgACm7WbuAAAAVklEQVR42n3PUQqAIBBFUU1LLc3u/jdbOJoW1P08DA9Gba8+YWJ6gNJoNYIBzAA2chBth5kLmG9YUoG0NHAUwFXwO9LuBQL1giCQb8gC9Oro2vp5rncCIY8L8uEx5ZkAAAAASUVORK5CYII="); } .CodeMirror-lint-marker-multiple { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEAQvB2JVdrAAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAD1JREFUCNdtjkESADAEAzemf69f66HMqGlOIhYiFRFRtSQBWAY7mzx+EDTL6sSgb1jTk7Q87rxyqe37fXsAa78gLyZnRgEAAAAASUVORK5CYII="); + background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAAAAAC/v7914kyHAAAAAXRSTlMAQObYZgAAACNJREFUeNo1ioEJAAAIwmz/H90iFFSGJgFMe3gaLZ0od+9/AQZ0ADosbYraAAAAAElFTkSuQmCC"); background-repeat: no-repeat; background-position: right bottom; width: 100%; height: 100%; } - -/* Styles for the overview ruler -.annotationOverview { - cursor: pointer; - border-radius: 2px; - left: 2px; - width: 8px; -} -.annotationOverview.error { - background-color: lightcoral; - border: 1px solid darkred; -} -.annotationOverview.warning { - background-color: Gold; - border: 1px solid black; -} - -.annotationHTML.overlay { - background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9sJEAQvB2JVdrAAAAAdaVRYdENvbW1lbnQAAAAAAENyZWF0ZWQgd2l0aCBHSU1QZC5lBwAAAD1JREFUCNdtjkESADAEAzemf69f66HMqGlOIhYiFRFRtSQBWAY7mzx+EDTL6sSgb1jTk7Q87rxyqe37fXsAa78gLyZnRgEAAAAASUVORK5CYII="); - background-position: right bottom; - position: relative; - top: -16px; -} -*/ \ No newline at end of file diff --git a/gulliver/js/codemirror/addon/lint/lint.js b/gulliver/js/codemirror/addon/lint/lint.js index dd838e7ea..6121735d3 100644 --- a/gulliver/js/codemirror/addon/lint/lint.js +++ b/gulliver/js/codemirror/addon/lint/lint.js @@ -1,4 +1,5 @@ -CodeMirror.validate = (function() { +(function() { + "use strict"; var GUTTER_ID = "CodeMirror-lint-markers"; var SEVERITIES = /^(?:error|warning)$/; @@ -10,12 +11,12 @@ CodeMirror.validate = (function() { function position(e) { if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position); - tt.style.top = (e.clientY - tt.offsetHeight - 5) + "px"; + tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px"; tt.style.left = (e.clientX + 5) + "px"; } CodeMirror.on(document, "mousemove", position); position(e); - tt.style.opacity = 1; + if (tt.style.opacity != null) tt.style.opacity = 1; return tt; } function rm(elt) { @@ -28,6 +29,22 @@ CodeMirror.validate = (function() { setTimeout(function() { rm(tt); }, 600); } + function showTooltipFor(e, content, node) { + var tooltip = showTooltip(e, content); + function hide() { + CodeMirror.off(node, "mouseout", hide); + if (tooltip) { hideTooltip(tooltip); tooltip = null; } + } + var poll = setInterval(function() { + if (tooltip) for (var n = node;; n = n.parentNode) { + if (n == document.body) return; + if (!n) { hide(); break; } + } + if (!tooltip) return clearInterval(poll); + }, 400); + CodeMirror.on(node, "mouseout", hide); + } + function LintState(cm, options, hasGutter) { this.marked = []; this.options = options; @@ -36,21 +53,23 @@ CodeMirror.validate = (function() { this.onMouseOver = function(e) { onMouseOver(cm, e); }; } - function parseOptions(options) { + function parseOptions(cm, options) { if (options instanceof Function) return {getAnnotations: options}; - else if (!options || !options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); + if (!options || options === true) options = {}; + if (!options.getAnnotations) options.getAnnotations = cm.getHelper(CodeMirror.Pos(0, 0), "lint"); + if (!options.getAnnotations) throw new Error("Required option 'getAnnotations' missing (lint addon)"); return options; } function clearMarks(cm) { - var state = cm._lintState; + var state = cm.state.lint; if (state.hasGutter) cm.clearGutter(GUTTER_ID); for (var i = 0; i < state.marked.length; ++i) state.marked[i].clear(); state.marked.length = 0; } - function makeMarker(labels, severity, multiple) { + function makeMarker(labels, severity, multiple, tooltips) { var marker = document.createElement("div"), inner = marker; marker.className = "CodeMirror-lint-marker-" + severity; if (multiple) { @@ -58,9 +77,9 @@ CodeMirror.validate = (function() { inner.className = "CodeMirror-lint-marker-multiple"; } - var tooltip; - CodeMirror.on(inner, "mouseover", function(e) { tooltip = showTooltip(e, labels); }); - CodeMirror.on(inner, "mouseout", function() { if (tooltip) hideTooltip(tooltip); }); + if (tooltips != false) CodeMirror.on(inner, "mouseover", function(e) { + showTooltipFor(e, labels, inner); + }); return marker; } @@ -89,16 +108,16 @@ CodeMirror.validate = (function() { } function startLinting(cm) { - var state = cm._lintState, options = state.options; - if (options.async) - options.getAnnotations(cm, updateLinting, options); - else - updateLinting(cm, options.getAnnotations(cm.getValue())); + var state = cm.state.lint, options = state.options; + if (options.async) + options.getAnnotations(cm, updateLinting, options); + else + updateLinting(cm, options.getAnnotations(cm.getValue(), options.options)); } - + function updateLinting(cm, annotationsNotSorted) { clearMarks(cm); - var state = cm._lintState, options = state.options; + var state = cm.state.lint, options = state.options; var annotations = groupByLine(annotationsNotSorted); @@ -115,35 +134,31 @@ CodeMirror.validate = (function() { if (!SEVERITIES.test(severity)) severity = "error"; maxSeverity = getMaxSeverity(maxSeverity, severity); - if (options.formatAnnotation) ann = options.formatAnnotation(ann); + if (options.formatAnnotation) ann = options.formatAnnotation(ann); if (state.hasGutter) tipLabel.appendChild(annotationTooltip(ann)); if (ann.to) state.marked.push(cm.markText(ann.from, ann.to, { - className: "CodeMirror-lint-span-" + severity, + className: "CodeMirror-lint-mark-" + severity, __annotation: ann })); } if (state.hasGutter) - cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1)); + cm.setGutterMarker(line, GUTTER_ID, makeMarker(tipLabel, maxSeverity, anns.length > 1, + state.options.tooltips)); } if (options.onUpdateLinting) options.onUpdateLinting(annotationsNotSorted, annotations, cm); } function onChange(cm) { - var state = cm._lintState; + var state = cm.state.lint; clearTimeout(state.timeout); state.timeout = setTimeout(function(){startLinting(cm);}, state.options.delay || 500); } function popupSpanTooltip(ann, e) { - var tooltip = showTooltip(e, annotationTooltip(ann)); var target = e.target || e.srcElement; - CodeMirror.on(target, "mouseout", hide); - function hide() { - CodeMirror.off(target, "mouseout", hide); - hideTooltip(tooltip); - } + showTooltipFor(e, annotationTooltip(ann), target); } // When the mouseover fires, the cursor might not actually be over @@ -152,7 +167,7 @@ CodeMirror.validate = (function() { var nearby = [0, 0, 0, 5, 0, -5, 5, 0, -5, 0]; function onMouseOver(cm, e) { - if (!/\bCodeMirror-lint-span-/.test((e.target || e.srcElement).className)) return; + if (!/\bCodeMirror-lint-mark-/.test((e.target || e.srcElement).className)) return; for (var i = 0; i < nearby.length; i += 2) { var spans = cm.findMarksAt(cm.coordsChar({left: e.clientX + nearby[i], top: e.clientY + nearby[i + 1]})); @@ -163,21 +178,26 @@ CodeMirror.validate = (function() { } } - CodeMirror.defineOption("lintWith", false, function(cm, val, old) { + function optionHandler(cm, val, old) { if (old && old != CodeMirror.Init) { clearMarks(cm); cm.off("change", onChange); - CodeMirror.off(cm.getWrapperElement(), "mouseover", cm._lintState.onMouseOver); - delete cm._lintState; + CodeMirror.off(cm.getWrapperElement(), "mouseover", cm.state.lint.onMouseOver); + delete cm.state.lint; } - + if (val) { var gutters = cm.getOption("gutters"), hasLintGutter = false; for (var i = 0; i < gutters.length; ++i) if (gutters[i] == GUTTER_ID) hasLintGutter = true; - var state = cm._lintState = new LintState(cm, parseOptions(val), hasLintGutter); + var state = cm.state.lint = new LintState(cm, parseOptions(cm, val), hasLintGutter); cm.on("change", onChange); - CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); + if (state.options.tooltips != false) + CodeMirror.on(cm.getWrapperElement(), "mouseover", state.onMouseOver); + startLinting(cm); } - }); + } + + CodeMirror.defineOption("lintWith", false, optionHandler); // deprecated + CodeMirror.defineOption("lint", false, optionHandler); // deprecated })(); diff --git a/gulliver/js/codemirror/addon/merge/dep/diff_match_patch.js b/gulliver/js/codemirror/addon/merge/dep/diff_match_patch.js new file mode 100644 index 000000000..ac34105fc --- /dev/null +++ b/gulliver/js/codemirror/addon/merge/dep/diff_match_patch.js @@ -0,0 +1,50 @@ +// From https://code.google.com/p/google-diff-match-patch/ , licensed under the Apache License 2.0 +(function(){function diff_match_patch(){this.Diff_Timeout=1;this.Diff_EditCost=4;this.Match_Threshold=0.5;this.Match_Distance=1E3;this.Patch_DeleteThreshold=0.5;this.Patch_Margin=4;this.Match_MaxBits=32} +diff_match_patch.prototype.diff_main=function(a,b,c,d){"undefined"==typeof d&&(d=0>=this.Diff_Timeout?Number.MAX_VALUE:(new Date).getTime()+1E3*this.Diff_Timeout);if(null==a||null==b)throw Error("Null input. (diff_main)");if(a==b)return a?[[0,a]]:[];"undefined"==typeof c&&(c=!0);var e=c,f=this.diff_commonPrefix(a,b);c=a.substring(0,f);a=a.substring(f);b=b.substring(f);var f=this.diff_commonSuffix(a,b),g=a.substring(a.length-f);a=a.substring(0,a.length-f);b=b.substring(0,b.length-f);a=this.diff_compute_(a, +b,e,d);c&&a.unshift([0,c]);g&&a.push([0,g]);this.diff_cleanupMerge(a);return a}; +diff_match_patch.prototype.diff_compute_=function(a,b,c,d){if(!a)return[[1,b]];if(!b)return[[-1,a]];var e=a.length>b.length?a:b,f=a.length>b.length?b:a,g=e.indexOf(f);return-1!=g?(c=[[1,e.substring(0,g)],[0,f],[1,e.substring(g+f.length)]],a.length>b.length&&(c[0][0]=c[2][0]=-1),c):1==f.length?[[-1,a],[1,b]]:(e=this.diff_halfMatch_(a,b))?(f=e[0],a=e[1],g=e[2],b=e[3],e=e[4],f=this.diff_main(f,g,c,d),c=this.diff_main(a,b,c,d),f.concat([[0,e]],c)):c&&100c);v++){for(var n=-v+r;n<=v-t;n+=2){var l=g+n,m;m=n==-v||n!=v&&j[l-1]d)t+=2;else if(s>e)r+=2;else if(q&&(l=g+k-n,0<=l&&l= +u)return this.diff_bisectSplit_(a,b,m,s,c)}}for(n=-v+p;n<=v-w;n+=2){l=g+n;u=n==-v||n!=v&&i[l-1]d)w+=2;else if(m>e)p+=2;else if(!q&&(l=g+k-n,0<=l&&(l=u)))return this.diff_bisectSplit_(a,b,m,s,c)}}return[[-1,a],[1,b]]}; +diff_match_patch.prototype.diff_bisectSplit_=function(a,b,c,d,e){var f=a.substring(0,c),g=b.substring(0,d);a=a.substring(c);b=b.substring(d);f=this.diff_main(f,g,!1,e);e=this.diff_main(a,b,!1,e);return f.concat(e)}; +diff_match_patch.prototype.diff_linesToChars_=function(a,b){function c(a){for(var b="",c=0,f=-1,g=d.length;fd?a=a.substring(c-d):c=a.length?[h,j,n,l,g]:null}if(0>=this.Diff_Timeout)return null; +var d=a.length>b.length?a:b,e=a.length>b.length?b:a;if(4>d.length||2*e.lengthd[4].length?g:d:d:g;var j;a.length>b.length?(g=h[0],d=h[1],e=h[2],j=h[3]):(e=h[0],j=h[1],g=h[2],d=h[3]);h=h[4];return[g,d,e,j,h]}; +diff_match_patch.prototype.diff_cleanupSemantic=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=0,h=0,j=0,i=0;f=e){if(d>=b.length/2||d>=c.length/2)a.splice(f,0,[0,c.substring(0,d)]),a[f-1][1]=b.substring(0,b.length-d),a[f+1][1]=c.substring(d),f++}else if(e>=b.length/2||e>=c.length/2)a.splice(f,0,[0,b.substring(0,e)]),a[f-1][0]=1,a[f-1][1]=c.substring(0,c.length-e),a[f+1][0]=-1,a[f+1][1]=b.substring(e),f++;f++}f++}}; +diff_match_patch.prototype.diff_cleanupSemanticLossless=function(a){function b(a,b){if(!a||!b)return 6;var c=a.charAt(a.length-1),d=b.charAt(0),e=c.match(diff_match_patch.nonAlphaNumericRegex_),f=d.match(diff_match_patch.nonAlphaNumericRegex_),g=e&&c.match(diff_match_patch.whitespaceRegex_),h=f&&d.match(diff_match_patch.whitespaceRegex_),c=g&&c.match(diff_match_patch.linebreakRegex_),d=h&&d.match(diff_match_patch.linebreakRegex_),i=c&&a.match(diff_match_patch.blanklineEndRegex_),j=d&&b.match(diff_match_patch.blanklineStartRegex_); +return i||j?5:c||d?4:e&&!g&&h?3:g||h?2:e||f?1:0}for(var c=1;c=i&&(i=k,g=d,h=e,j=f)}a[c-1][1]!=g&&(g?a[c-1][1]=g:(a.splice(c-1,1),c--),a[c][1]= +h,j?a[c+1][1]=j:(a.splice(c+1,1),c--))}c++}};diff_match_patch.nonAlphaNumericRegex_=/[^a-zA-Z0-9]/;diff_match_patch.whitespaceRegex_=/\s/;diff_match_patch.linebreakRegex_=/[\r\n]/;diff_match_patch.blanklineEndRegex_=/\n\r?\n$/;diff_match_patch.blanklineStartRegex_=/^\r?\n\r?\n/; +diff_match_patch.prototype.diff_cleanupEfficiency=function(a){for(var b=!1,c=[],d=0,e=null,f=0,g=!1,h=!1,j=!1,i=!1;fb)break;e=c;f=d}return a.length!=g&&-1===a[g][0]?f:f+(b-e)}; +diff_match_patch.prototype.diff_prettyHtml=function(a){for(var b=[],c=/&/g,d=//g,f=/\n/g,g=0;g");switch(h){case 1:b[g]=''+j+"";break;case -1:b[g]=''+j+"";break;case 0:b[g]=""+j+""}}return b.join("")}; +diff_match_patch.prototype.diff_text1=function(a){for(var b=[],c=0;cthis.Match_MaxBits)throw Error("Pattern too long for this browser.");var e=this.match_alphabet_(b),f=this,g=this.Match_Threshold,h=a.indexOf(b,c);-1!=h&&(g=Math.min(d(0,h),g),h=a.lastIndexOf(b,c+b.length),-1!=h&&(g=Math.min(d(0,h),g)));for(var j=1<=i;p--){var w=e[a.charAt(p-1)];k[p]=0===t?(k[p+1]<<1|1)&w:(k[p+1]<<1|1)&w|((r[p+1]|r[p])<<1|1)|r[p+1];if(k[p]&j&&(w=d(t,p-1),w<=g))if(g=w,h=p-1,h>c)i=Math.max(1,2*c-h);else break}if(d(t+1,c)>g)break;r=k}return h}; +diff_match_patch.prototype.match_alphabet_=function(a){for(var b={},c=0;c=2*this.Patch_Margin&& +e&&(this.patch_addContext_(a,h),c.push(a),a=new diff_match_patch.patch_obj,e=0,h=d,f=g)}1!==i&&(f+=k.length);-1!==i&&(g+=k.length)}e&&(this.patch_addContext_(a,h),c.push(a));return c};diff_match_patch.prototype.patch_deepCopy=function(a){for(var b=[],c=0;cthis.Match_MaxBits){if(j=this.match_main(b,h.substring(0,this.Match_MaxBits),g),-1!=j&&(i=this.match_main(b,h.substring(h.length-this.Match_MaxBits),g+h.length-this.Match_MaxBits),-1==i||j>=i))j=-1}else j=this.match_main(b,h,g); +if(-1==j)e[f]=!1,d-=a[f].length2-a[f].length1;else if(e[f]=!0,d=j-g,g=-1==i?b.substring(j,j+h.length):b.substring(j,i+this.Match_MaxBits),h==g)b=b.substring(0,j)+this.diff_text2(a[f].diffs)+b.substring(j+h.length);else if(g=this.diff_main(h,g,!1),h.length>this.Match_MaxBits&&this.diff_levenshtein(g)/h.length>this.Patch_DeleteThreshold)e[f]=!1;else{this.diff_cleanupSemanticLossless(g);for(var h=0,k,i=0;ie[0][1].length){var f=b-e[0][1].length;e[0][1]=c.substring(e[0][1].length)+e[0][1];d.start1-=f;d.start2-=f;d.length1+=f;d.length2+=f}d=a[a.length-1];e=d.diffs;0==e.length||0!=e[e.length-1][0]?(e.push([0, +c]),d.length1+=b,d.length2+=b):b>e[e.length-1][1].length&&(f=b-e[e.length-1][1].length,e[e.length-1][1]+=c.substring(0,f),d.length1+=f,d.length2+=f);return c}; +diff_match_patch.prototype.patch_splitMax=function(a){for(var b=this.Match_MaxBits,c=0;c2*b?(h.length1+=i.length,e+=i.length,j=!1,h.diffs.push([g,i]),d.diffs.shift()):(i=i.substring(0,b-h.length1-this.Patch_Margin),h.length1+=i.length,e+=i.length,0===g?(h.length2+=i.length,f+=i.length):j=!1,h.diffs.push([g,i]),i==d.diffs[0][1]?d.diffs.shift():d.diffs[0][1]=d.diffs[0][1].substring(i.length))}g=this.diff_text2(h.diffs);g=g.substring(g.length-this.Patch_Margin);i=this.diff_text1(d.diffs).substring(0,this.Patch_Margin);""!==i&& +(h.length1+=i.length,h.length2+=i.length,0!==h.diffs.length&&0===h.diffs[h.diffs.length-1][0]?h.diffs[h.diffs.length-1][1]+=i:h.diffs.push([0,i]));j||a.splice(++c,0,h)}}};diff_match_patch.prototype.patch_toText=function(a){for(var b=[],c=0;c now) return false; + + var sInfo = editor.getScrollInfo(), halfScreen = .5 * sInfo.clientHeight, midY = sInfo.top + halfScreen; + var mid = editor.lineAtHeight(midY, "local"); + var around = chunkBoundariesAround(dv.diff, mid, type == DIFF_INSERT); + var off = getOffsets(editor, type == DIFF_INSERT ? around.edit : around.orig); + var offOther = getOffsets(other, type == DIFF_INSERT ? around.orig : around.edit); + var ratio = (midY - off.top) / (off.bot - off.top); + var targetPos = (offOther.top - halfScreen) + ratio * (offOther.bot - offOther.top); + + var botDist, mix; + // Some careful tweaking to make sure no space is left out of view + // when scrolling to top or bottom. + if (targetPos > sInfo.top && (mix = sInfo.top / halfScreen) < 1) { + targetPos = targetPos * mix + sInfo.top * (1 - mix); + } else if ((botDist = sInfo.height - sInfo.clientHeight - sInfo.top) < halfScreen) { + var otherInfo = other.getScrollInfo(); + var botDistOther = otherInfo.height - otherInfo.clientHeight - targetPos; + if (botDistOther > botDist && (mix = botDist / halfScreen) < 1) + targetPos = targetPos * mix + (otherInfo.height - otherInfo.clientHeight - botDist) * (1 - mix); + } + + other.scrollTo(sInfo.left, targetPos); + other.state.scrollSetAt = now; + other.state.scrollSetBy = dv; + return true; + } + + function getOffsets(editor, around) { + var bot = around.after; + if (bot == null) bot = editor.lastLine() + 1; + return {top: editor.heightAtLine(around.before || 0, "local"), + bot: editor.heightAtLine(bot, "local")}; + } + + function setScrollLock(dv, val, action) { + dv.lockScroll = val; + if (val && action != false) syncScroll(dv, DIFF_INSERT) && drawConnectors(dv); + dv.lockButton.innerHTML = val ? "\u21db\u21da" : "\u21db  \u21da"; + } + + // Updating the marks for editor content + + function clearMarks(editor, arr, classes) { + for (var i = 0; i < arr.length; ++i) { + var mark = arr[i]; + if (mark instanceof CodeMirror.TextMarker) { + mark.clear(); + } else { + editor.removeLineClass(mark, "background", classes.chunk); + editor.removeLineClass(mark, "background", classes.start); + editor.removeLineClass(mark, "background", classes.end); + } + } + arr.length = 0; + } + + // FIXME maybe add a margin around viewport to prevent too many updates + function updateMarks(editor, diff, state, type, classes) { + var vp = editor.getViewport(); + editor.operation(function() { + if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) { + clearMarks(editor, state.marked, classes); + markChanges(editor, diff, type, state.marked, vp.from, vp.to, classes); + state.from = vp.from; state.to = vp.to; + } else { + if (vp.from < state.from) { + markChanges(editor, diff, type, state.marked, vp.from, state.from, classes); + state.from = vp.from; + } + if (vp.to > state.to) { + markChanges(editor, diff, type, state.marked, state.to, vp.to, classes); + state.to = vp.to; + } + } + }); + } + + function markChanges(editor, diff, type, marks, from, to, classes) { + var pos = Pos(0, 0); + var top = Pos(from, 0), bot = editor.clipPos(Pos(to - 1)); + var cls = type == DIFF_DELETE ? classes.del : classes.insert; + function markChunk(start, end) { + var bfrom = Math.max(from, start), bto = Math.min(to, end); + for (var i = bfrom; i < bto; ++i) { + var line = editor.addLineClass(i, "background", classes.chunk); + if (i == start) editor.addLineClass(line, "background", classes.start); + if (i == end - 1) editor.addLineClass(line, "background", classes.end); + marks.push(line); + } + // When the chunk is empty, make sure a horizontal line shows up + if (start == end && bfrom == end && bto == end) { + if (bfrom) + marks.push(editor.addLineClass(bfrom - 1, "background", classes.end)); + else + marks.push(editor.addLineClass(bfrom, "background", classes.start)); + } + } + + var chunkStart = 0; + for (var i = 0; i < diff.length; ++i) { + var part = diff[i], tp = part[0], str = part[1]; + if (tp == DIFF_EQUAL) { + var cleanFrom = pos.line + (startOfLineClean(diff, i) ? 0 : 1); + moveOver(pos, str); + var cleanTo = pos.line + (endOfLineClean(diff, i) ? 1 : 0); + if (cleanTo > cleanFrom) { + if (i) markChunk(chunkStart, cleanFrom); + chunkStart = cleanTo; + } + } else { + if (tp == type) { + var end = moveOver(pos, str, true); + var a = posMax(top, pos), b = posMin(bot, end); + if (!posEq(a, b)) + marks.push(editor.markText(a, b, {className: cls})); + pos = end; + } + } + } + if (chunkStart <= pos.line) markChunk(chunkStart, pos.line + 1); + } + + // Updating the gap between editor and original + + function drawConnectors(dv) { + if (!dv.showDifferences) return; + + if (dv.svg) { + clear(dv.svg); + var w = dv.gap.offsetWidth; + attrs(dv.svg, "width", w, "height", dv.gap.offsetHeight); + } + clear(dv.copyButtons); + + var flip = dv.type == "left"; + var vpEdit = dv.edit.getViewport(), vpOrig = dv.orig.getViewport(); + var sTopEdit = dv.edit.getScrollInfo().top, sTopOrig = dv.orig.getScrollInfo().top; + iterateChunks(dv.diff, function(topOrig, botOrig, topEdit, botEdit) { + if (topEdit > vpEdit.to || botEdit < vpEdit.from || + topOrig > vpOrig.to || botOrig < vpOrig.from) + return; + var topLpx = dv.orig.heightAtLine(topOrig, "local") - sTopOrig, top = topLpx; + if (dv.svg) { + var topRpx = dv.edit.heightAtLine(topEdit, "local") - sTopEdit; + if (flip) { var tmp = topLpx; topLpx = topRpx; topRpx = tmp; } + var botLpx = dv.orig.heightAtLine(botOrig, "local") - sTopOrig; + var botRpx = dv.edit.heightAtLine(botEdit, "local") - sTopEdit; + if (flip) { var tmp = botLpx; botLpx = botRpx; botRpx = tmp; } + var curveTop = " C " + w/2 + " " + topRpx + " " + w/2 + " " + topLpx + " " + (w + 2) + " " + topLpx; + var curveBot = " C " + w/2 + " " + botLpx + " " + w/2 + " " + botRpx + " -1 " + botRpx; + attrs(dv.svg.appendChild(document.createElementNS(svgNS, "path")), + "d", "M -1 " + topRpx + curveTop + " L " + (w + 2) + " " + botLpx + curveBot + " z", + "class", dv.classes.connect); + } + var copy = dv.copyButtons.appendChild(elt("div", dv.type == "left" ? "\u21dd" : "\u21dc", + "CodeMirror-merge-copy")); + copy.title = "Revert chunk"; + copy.chunk = {topEdit: topEdit, botEdit: botEdit, topOrig: topOrig, botOrig: botOrig}; + copy.style.top = top + "px"; + }); + } + + function copyChunk(dv, chunk) { + if (dv.diffOutOfDate) return; + dv.edit.replaceRange(dv.orig.getRange(Pos(chunk.topOrig, 0), Pos(chunk.botOrig, 0)), + Pos(chunk.topEdit, 0), Pos(chunk.botEdit, 0)); + } + + // Merge view, containing 0, 1, or 2 diff views. + + var MergeView = CodeMirror.MergeView = function(node, options) { + if (!(this instanceof MergeView)) return new MergeView(node, options); + + var origLeft = options.origLeft, origRight = options.origRight == null ? options.orig : options.origRight; + var hasLeft = origLeft != null, hasRight = origRight != null; + var panes = 1 + (hasLeft ? 1 : 0) + (hasRight ? 1 : 0); + var wrap = [], left = this.left = null, right = this.right = null; + + if (hasLeft) { + left = this.left = new DiffView(this, "left"); + var leftPane = elt("div", null, "CodeMirror-merge-pane"); + wrap.push(leftPane); + wrap.push(buildGap(left)); + } + + var editPane = elt("div", null, "CodeMirror-merge-pane"); + wrap.push(editPane); + + if (hasRight) { + right = this.right = new DiffView(this, "right"); + wrap.push(buildGap(right)); + var rightPane = elt("div", null, "CodeMirror-merge-pane"); + wrap.push(rightPane); + } + + (hasRight ? rightPane : editPane).className += " CodeMirror-merge-pane-rightmost"; + + wrap.push(elt("div", null, null, "height: 0; clear: both;")); + var wrapElt = this.wrap = node.appendChild(elt("div", wrap, "CodeMirror-merge CodeMirror-merge-" + panes + "pane")); + this.edit = CodeMirror(editPane, copyObj(options)); + + if (left) left.init(leftPane, origLeft, options); + if (right) right.init(rightPane, origRight, options); + + var onResize = function() { + if (left) drawConnectors(left); + if (right) drawConnectors(right); + }; + CodeMirror.on(window, "resize", onResize); + var resizeInterval = setInterval(function() { + for (var p = wrapElt.parentNode; p && p != document.body; p = p.parentNode) {} + if (!p) { clearInterval(resizeInterval); CodeMirror.off(window, "resize", onResize); } + }, 5000); + }; + + function buildGap(dv) { + var lock = dv.lockButton = elt("div", null, "CodeMirror-merge-scrolllock"); + lock.title = "Toggle locked scrolling"; + var lockWrap = elt("div", [lock], "CodeMirror-merge-scrolllock-wrap"); + CodeMirror.on(lock, "click", function() { setScrollLock(dv, !dv.lockScroll); }); + dv.copyButtons = elt("div", null, "CodeMirror-merge-copybuttons-" + dv.type); + CodeMirror.on(dv.copyButtons, "click", function(e) { + var node = e.target || e.srcElement; + if (node.chunk) copyChunk(dv, node.chunk); + }); + var gapElts = [dv.copyButtons, lockWrap]; + var svg = document.createElementNS && document.createElementNS(svgNS, "svg"); + if (svg && !svg.createSVGRect) svg = null; + dv.svg = svg; + if (svg) gapElts.push(svg); + + return dv.gap = elt("div", gapElts, "CodeMirror-merge-gap"); + } + + MergeView.prototype = { + constuctor: MergeView, + editor: function() { return this.edit; }, + rightOriginal: function() { return this.right && this.right.orig; }, + leftOriginal: function() { return this.left && this.left.orig; }, + setShowDifferences: function(val) { + if (this.right) this.right.setShowDifferences(val); + if (this.left) this.left.setShowDifferences(val); + } + }; + + function asString(obj) { + if (typeof obj == "string") return obj; + else return obj.getValue(); + } + + // Operations on diffs + + var dmp = new diff_match_patch(); + function getDiff(a, b) { + var diff = dmp.diff_main(a, b); + dmp.diff_cleanupSemantic(diff); + // The library sometimes leaves in empty parts, which confuse the algorithm + for (var i = 0; i < diff.length; ++i) { + var part = diff[i]; + if (!part[1]) { + diff.splice(i--, 1); + } else if (i && diff[i - 1][0] == part[0]) { + diff.splice(i--, 1); + diff[i][1] += part[1]; + } + } + return diff; + } + + function iterateChunks(diff, f) { + var startEdit = 0, startOrig = 0; + var edit = Pos(0, 0), orig = Pos(0, 0); + for (var i = 0; i < diff.length; ++i) { + var part = diff[i], tp = part[0]; + if (tp == DIFF_EQUAL) { + var startOff = startOfLineClean(diff, i) ? 0 : 1; + var cleanFromEdit = edit.line + startOff, cleanFromOrig = orig.line + startOff; + moveOver(edit, part[1], null, orig); + var endOff = endOfLineClean(diff, i) ? 1 : 0; + var cleanToEdit = edit.line + endOff, cleanToOrig = orig.line + endOff; + if (cleanToEdit > cleanFromEdit) { + if (i) f(startOrig, cleanFromOrig, startEdit, cleanFromEdit); + startEdit = cleanToEdit; startOrig = cleanToOrig; + } + } else { + moveOver(tp == DIFF_INSERT ? edit : orig, part[1]); + } + } + if (startEdit <= edit.line || startOrig <= orig.line) + f(startOrig, orig.line + 1, startEdit, edit.line + 1); + } + + function endOfLineClean(diff, i) { + if (i == diff.length - 1) return true; + var next = diff[i + 1][1]; + if (next.length == 1 || next.charCodeAt(0) != 10) return false; + if (i == diff.length - 2) return true; + next = diff[i + 2][1]; + return next.length > 1 && next.charCodeAt(0) == 10; + } + + function startOfLineClean(diff, i) { + if (i == 0) return true; + var last = diff[i - 1][1]; + if (last.charCodeAt(last.length - 1) != 10) return false; + if (i == 1) return true; + last = diff[i - 2][1]; + return last.charCodeAt(last.length - 1) == 10; + } + + function chunkBoundariesAround(diff, n, nInEdit) { + var beforeE, afterE, beforeO, afterO; + iterateChunks(diff, function(fromOrig, toOrig, fromEdit, toEdit) { + var fromLocal = nInEdit ? fromEdit : fromOrig; + var toLocal = nInEdit ? toEdit : toOrig; + if (afterE == null) { + if (fromLocal > n) { afterE = fromEdit; afterO = fromOrig; } + else if (toLocal > n) { afterE = toEdit; afterO = toOrig; } + } + if (toLocal <= n) { beforeE = toEdit; beforeO = toOrig; } + else if (fromLocal <= n) { beforeE = fromEdit; beforeO = fromOrig; } + }); + return {edit: {before: beforeE, after: afterE}, orig: {before: beforeO, after: afterO}}; + } + + // General utilities + + function elt(tag, content, className, style) { + var e = document.createElement(tag); + if (className) e.className = className; + if (style) e.style.cssText = style; + if (typeof content == "string") e.appendChild(document.createTextNode(content)); + else if (content) for (var i = 0; i < content.length; ++i) e.appendChild(content[i]); + return e; + } + + function clear(node) { + for (var count = node.childNodes.length; count > 0; --count) + node.removeChild(node.firstChild); + } + + function attrs(elt) { + for (var i = 1; i < arguments.length; i += 2) + elt.setAttribute(arguments[i], arguments[i+1]); + } + + function copyObj(obj, target) { + if (!target) target = {}; + for (var prop in obj) if (obj.hasOwnProperty(prop)) target[prop] = obj[prop]; + return target; + } + + function moveOver(pos, str, copy, other) { + var out = copy ? Pos(pos.line, pos.ch) : pos, at = 0; + for (;;) { + var nl = str.indexOf("\n", at); + if (nl == -1) break; + ++out.line; + if (other) ++other.line; + at = nl + 1; + } + out.ch = (at ? 0 : out.ch) + (str.length - at); + if (other) other.ch = (at ? 0 : other.ch) + (str.length - at); + return out; + } + + function posMin(a, b) { return (a.line - b.line || a.ch - b.ch) < 0 ? a : b; } + function posMax(a, b) { return (a.line - b.line || a.ch - b.ch) > 0 ? a : b; } + function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } +})(); diff --git a/gulliver/js/codemirror/addon/mode/multiplex.js b/gulliver/js/codemirror/addon/mode/multiplex.js index e77ff2a9c..614ab1add 100644 --- a/gulliver/js/codemirror/addon/mode/multiplex.js +++ b/gulliver/js/codemirror/addon/mode/multiplex.js @@ -1,5 +1,5 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { - // Others should be {open, close, mode [, delimStyle]} objects + // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects var others = Array.prototype.slice.call(arguments, 1); var n_others = others.length; @@ -47,7 +47,11 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { return outerToken; } else { var curInner = state.innerActive, oldContent = stream.string; - var found = indexOf(oldContent, curInner.close, stream.pos); + if (!curInner.close && stream.sol()) { + state.innerActive = state.inner = null; + return this.token(stream, state); + } + var found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos) : -1; if (found == stream.pos) { stream.match(curInner.close); state.innerActive = state.inner = null; @@ -56,12 +60,16 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { if (found > -1) stream.string = oldContent.slice(0, found); var innerToken = curInner.mode.token(stream, state.inner); if (found > -1) stream.string = oldContent; - var cur = stream.current(), found = cur.indexOf(curInner.close); - if (found > -1) stream.backUp(cur.length - found); + + if (curInner.innerStyle) { + if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle; + else innerToken = curInner.innerStyle; + } + return innerToken; } }, - + indent: function(state, textAfter) { var mode = state.innerActive ? state.innerActive.mode : outer; if (!mode.indent) return CodeMirror.Pass; @@ -81,7 +89,7 @@ CodeMirror.multiplexingMode = function(outer /*, others */) { state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "") : 0); } } - } else if (mode.close === "\n") { + } else if (state.innerActive.close === "\n") { state.innerActive = state.inner = null; } }, diff --git a/gulliver/js/codemirror/addon/mode/multiplex_test.js b/gulliver/js/codemirror/addon/mode/multiplex_test.js new file mode 100644 index 000000000..c0656357c --- /dev/null +++ b/gulliver/js/codemirror/addon/mode/multiplex_test.js @@ -0,0 +1,30 @@ +(function() { + CodeMirror.defineMode("markdown_with_stex", function(){ + var inner = CodeMirror.getMode({}, "stex"); + var outer = CodeMirror.getMode({}, "markdown"); + + var innerOptions = { + open: '$', + close: '$', + mode: inner, + delimStyle: 'delim', + innerStyle: 'inner' + }; + + return CodeMirror.multiplexingMode(outer, innerOptions); + }); + + var mode = CodeMirror.getMode({}, "markdown_with_stex"); + + function MT(name) { + test.mode( + name, + mode, + Array.prototype.slice.call(arguments, 1), + 'multiplexing'); + } + + MT( + "stexInsideMarkdown", + "[strong **Equation:**] [delim $][inner&tag \\pi][delim $]"); +})(); diff --git a/gulliver/js/codemirror/addon/mode/overlay.js b/gulliver/js/codemirror/addon/mode/overlay.js index fba38987b..b7928a7bb 100644 --- a/gulliver/js/codemirror/addon/mode/overlay.js +++ b/gulliver/js/codemirror/addon/mode/overlay.js @@ -43,14 +43,14 @@ CodeMirror.overlayMode = CodeMirror.overlayParser = function(base, overlay, comb if (state.baseCur != null && combine) return state.baseCur + " " + state.overlayCur; else return state.overlayCur; }, - + indent: base.indent && function(state, textAfter) { return base.indent(state.base, textAfter); }, electricChars: base.electricChars, innerMode: function(state) { return {state: state.base, mode: base}; }, - + blankLine: function(state) { if (base.blankLine) base.blankLine(state.base); if (overlay.blankLine) overlay.blankLine(state.overlay); diff --git a/gulliver/js/codemirror/addon/runmode/runmode-standalone.js b/gulliver/js/codemirror/addon/runmode/runmode-standalone.js index 7a9b82ffa..ec06ba11c 100644 --- a/gulliver/js/codemirror/addon/runmode/runmode-standalone.js +++ b/gulliver/js/codemirror/addon/runmode/runmode-standalone.js @@ -2,11 +2,15 @@ window.CodeMirror = {}; +(function() { +"use strict"; + function splitLines(string){ return string.split(/\r?\n|\r/); }; function StringStream(string) { this.pos = this.start = 0; this.string = string; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, @@ -38,22 +42,29 @@ StringStream.prototype = { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return this.start;}, + column: function() {return this.start - this.lineStart;}, indentation: function() {return 0;}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; CodeMirror.StringStream = StringStream; @@ -64,17 +75,26 @@ CodeMirror.startState = function (mode, a1, a2) { var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function (name, mode) { modes[name] = mode; }; CodeMirror.defineMIME = function (mime, spec) { mimeModes[mime] = spec; }; -CodeMirror.getMode = function (options, spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) +CodeMirror.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, config || {}); + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + spec = mimeModes[spec.name]; + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; }; +CodeMirror.getMode = function (options, spec) { + spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) throw new Error("Unknown mode: " + spec); + return mfactory(options, spec); +}; +CodeMirror.registerHelper = CodeMirror.registerGlobalHelper = Math.min; +CodeMirror.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; +}); +CodeMirror.defineMIME("text/plain", "null"); CodeMirror.runMode = function (string, modespec, callback, options) { var mode = CodeMirror.getMode({ indentUnit: 2 }, modespec); @@ -117,14 +137,15 @@ CodeMirror.runMode = function (string, modespec, callback, options) { }; } - var lines = splitLines(string), state = CodeMirror.startState(mode); + var lines = splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new CodeMirror.StringStream(lines[i]); while (!stream.eol()) { var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); + callback(stream.current(), style, i, stream.start, state); stream.start = stream.pos; } } }; +})(); diff --git a/gulliver/js/codemirror/addon/runmode/runmode.js b/gulliver/js/codemirror/addon/runmode/runmode.js index 3e1bed736..2cafa811f 100644 --- a/gulliver/js/codemirror/addon/runmode/runmode.js +++ b/gulliver/js/codemirror/addon/runmode/runmode.js @@ -1,5 +1,7 @@ CodeMirror.runMode = function(string, modespec, callback, options) { var mode = CodeMirror.getMode(CodeMirror.defaults, modespec); + var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); if (callback.nodeType == 1) { var tabSize = (options && options.tabSize) || CodeMirror.defaults.tabSize; @@ -7,7 +9,9 @@ CodeMirror.runMode = function(string, modespec, callback, options) { node.innerHTML = ""; callback = function(text, style) { if (text == "\n") { - node.appendChild(document.createElement("br")); + // Emitting LF or CRLF on IE8 or earlier results in an incorrect display. + // Emitting a carriage return makes everything ok. + node.appendChild(document.createTextNode(ie_lt9 ? '\r' : text)); col = 0; return; } @@ -39,13 +43,13 @@ CodeMirror.runMode = function(string, modespec, callback, options) { }; } - var lines = CodeMirror.splitLines(string), state = CodeMirror.startState(mode); + var lines = CodeMirror.splitLines(string), state = (options && options.state) || CodeMirror.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new CodeMirror.StringStream(lines[i]); while (!stream.eol()) { var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); + callback(stream.current(), style, i, stream.start, state); stream.start = stream.pos; } } diff --git a/gulliver/js/codemirror/addon/runmode/runmode.node.js b/gulliver/js/codemirror/addon/runmode/runmode.node.js index 6449e77c8..e8bccb0cf 100644 --- a/gulliver/js/codemirror/addon/runmode/runmode.node.js +++ b/gulliver/js/codemirror/addon/runmode/runmode.node.js @@ -5,6 +5,7 @@ function splitLines(string){ return string.split(/\r?\n|\r/); }; function StringStream(string) { this.pos = this.start = 0; this.string = string; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, @@ -36,22 +37,29 @@ StringStream.prototype = { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return this.start;}, + column: function() {return this.start - this.lineStart;}, indentation: function() {return 0;}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; - if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) { + var substr = this.string.substr(this.pos, pattern.length); + if (cased(substr) == cased(pattern)) { if (consume !== false) this.pos += pattern.length; return true; } } else { var match = this.string.slice(this.pos).match(pattern); + if (match && match.index > 0) return null; if (match && consume !== false) this.pos += match[0].length; return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; exports.StringStream = StringStream; @@ -60,29 +68,46 @@ exports.startState = function(mode, a1, a2) { }; var modes = exports.modes = {}, mimeModes = exports.mimeModes = {}; -exports.defineMode = function(name, mode) { modes[name] = mode; }; -exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; -exports.getMode = function(options, spec) { - if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) - spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) throw new Error("Unknown mode: " + spec); - return mfactory(options, config || {}); +exports.defineMode = function(name, mode) { + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } + modes[name] = mode; }; +exports.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; + +exports.defineMode("null", function() { + return {token: function(stream) {stream.skipToEnd();}}; +}); +exports.defineMIME("text/plain", "null"); + +exports.resolveMode = function(spec) { + if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) { + spec = mimeModes[spec]; + } else if (spec && typeof spec.name == "string" && mimeModes.hasOwnProperty(spec.name)) { + spec = mimeModes[spec.name]; + } + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; +}; +exports.getMode = function(options, spec) { + spec = exports.resolveMode(mimeModes[spec]); + var mfactory = modes[spec.name]; + if (!mfactory) throw new Error("Unknown mode: " + spec); + return mfactory(options, spec); +}; +exports.registerHelper = exports.registerGlobalHelper = Math.min; exports.runMode = function(string, modespec, callback) { var mode = exports.getMode({indentUnit: 2}, modespec); - var lines = splitLines(string), state = exports.startState(mode); + var lines = splitLines(string), state = (options && options.state) || exports.startState(mode); for (var i = 0, e = lines.length; i < e; ++i) { if (i) callback("\n"); var stream = new exports.StringStream(lines[i]); while (!stream.eol()) { var style = mode.token(stream, state); - callback(stream.current(), style, i, stream.start); + callback(stream.current(), style, i, stream.start, state); stream.start = stream.pos; } } diff --git a/gulliver/js/codemirror/addon/scroll/scrollpastend.js b/gulliver/js/codemirror/addon/scroll/scrollpastend.js new file mode 100644 index 000000000..14d7d5aee --- /dev/null +++ b/gulliver/js/codemirror/addon/scroll/scrollpastend.js @@ -0,0 +1,34 @@ +(function() { + "use strict"; + + CodeMirror.defineOption("scrollPastEnd", false, function(cm, val, old) { + if (old && old != CodeMirror.Init) { + cm.off("change", onChange); + cm.display.lineSpace.parentNode.style.paddingBottom = ""; + cm.state.scrollPastEndPadding = null; + } + if (val) { + cm.on("change", onChange); + updateBottomMargin(cm); + } + }); + + function onChange(cm, change) { + if (CodeMirror.changeEnd(change).line == cm.lastLine()) + updateBottomMargin(cm); + } + + function updateBottomMargin(cm) { + var padding = ""; + if (cm.lineCount() > 1) { + var totalH = cm.display.scroller.clientHeight - 30, + lastLineH = cm.getLineHandle(cm.lastLine()).height; + padding = (totalH - lastLineH) + "px"; + } + if (cm.state.scrollPastEndPadding != padding) { + cm.state.scrollPastEndPadding = padding; + cm.display.lineSpace.parentNode.style.paddingBottom = padding; + cm.setSize(); + } + } +})(); diff --git a/gulliver/js/codemirror/addon/search/match-highlighter.js b/gulliver/js/codemirror/addon/search/match-highlighter.js index c6e35cd97..e5cbeacab 100644 --- a/gulliver/js/codemirror/addon/search/match-highlighter.js +++ b/gulliver/js/codemirror/addon/search/match-highlighter.js @@ -5,54 +5,85 @@ // document. // // The option can be set to true to simply enable it, or to a -// {minChars, style} object to explicitly configure it. minChars is -// the minimum amount of characters that should be selected for the -// behavior to occur, and style is the token style to apply to the -// matches. This will be prefixed by "cm-" to create an actual CSS -// class name. +// {minChars, style, showToken} object to explicitly configure it. +// minChars is the minimum amount of characters that should be +// selected for the behavior to occur, and style is the token style to +// apply to the matches. This will be prefixed by "cm-" to create an +// actual CSS class name. showToken, when enabled, will cause the +// current token to be highlighted when nothing is selected. (function() { var DEFAULT_MIN_CHARS = 2; var DEFAULT_TOKEN_STYLE = "matchhighlight"; - + var DEFAULT_DELAY = 100; + function State(options) { - this.minChars = typeof options == "object" && options.minChars || DEFAULT_MIN_CHARS; - this.style = typeof options == "object" && options.style || DEFAULT_TOKEN_STYLE; - this.overlay = null; + if (typeof options == "object") { + this.minChars = options.minChars; + this.style = options.style; + this.showToken = options.showToken; + this.delay = options.delay; + } + if (this.style == null) this.style = DEFAULT_TOKEN_STYLE; + if (this.minChars == null) this.minChars = DEFAULT_MIN_CHARS; + if (this.delay == null) this.delay = DEFAULT_DELAY; + this.overlay = this.timeout = null; } CodeMirror.defineOption("highlightSelectionMatches", false, function(cm, val, old) { - var prev = old && old != CodeMirror.Init; - if (val && !prev) { - cm._matchHighlightState = new State(val); - cm.on("cursorActivity", highlightMatches); - } else if (!val && prev) { - var over = cm._matchHighlightState.overlay; + if (old && old != CodeMirror.Init) { + var over = cm.state.matchHighlighter.overlay; if (over) cm.removeOverlay(over); - cm._matchHighlightState = null; - cm.off("cursorActivity", highlightMatches); + clearTimeout(cm.state.matchHighlighter.timeout); + cm.state.matchHighlighter = null; + cm.off("cursorActivity", cursorActivity); + } + if (val) { + cm.state.matchHighlighter = new State(val); + highlightMatches(cm); + cm.on("cursorActivity", cursorActivity); } }); + function cursorActivity(cm) { + var state = cm.state.matchHighlighter; + clearTimeout(state.timeout); + state.timeout = setTimeout(function() {highlightMatches(cm);}, state.delay); + } + function highlightMatches(cm) { cm.operation(function() { - var state = cm._matchHighlightState; + var state = cm.state.matchHighlighter; if (state.overlay) { cm.removeOverlay(state.overlay); state.overlay = null; } - - if (!cm.somethingSelected()) return; + if (!cm.somethingSelected() && state.showToken) { + var re = state.showToken === true ? /[\w$]/ : state.showToken; + var cur = cm.getCursor(), line = cm.getLine(cur.line), start = cur.ch, end = start; + while (start && re.test(line.charAt(start - 1))) --start; + while (end < line.length && re.test(line.charAt(end))) ++end; + if (start < end) + cm.addOverlay(state.overlay = makeOverlay(line.slice(start, end), re, state.style)); + return; + } + if (cm.getCursor("head").line != cm.getCursor("anchor").line) return; var selection = cm.getSelection().replace(/^\s+|\s+$/g, ""); - if (selection.length < state.minChars) return; - - cm.addOverlay(state.overlay = makeOverlay(selection, state.style)); + if (selection.length >= state.minChars) + cm.addOverlay(state.overlay = makeOverlay(selection, false, state.style)); }); } - function makeOverlay(query, style) { + function boundariesAround(stream, re) { + return (!stream.start || !re.test(stream.string.charAt(stream.start - 1))) && + (stream.pos == stream.string.length || !re.test(stream.string.charAt(stream.pos))); + } + + function makeOverlay(query, hasBoundary, style) { return {token: function(stream) { - if (stream.match(query)) return style; + if (stream.match(query) && + (!hasBoundary || boundariesAround(stream, hasBoundary))) + return style; stream.next(); stream.skipTo(query.charAt(0)) || stream.skipToEnd(); }}; diff --git a/gulliver/js/codemirror/addon/search/search.js b/gulliver/js/codemirror/addon/search/search.js index 6331b8655..049f72f3d 100644 --- a/gulliver/js/codemirror/addon/search/search.js +++ b/gulliver/js/codemirror/addon/search/search.js @@ -7,7 +7,15 @@ // Ctrl-G. (function() { - function searchOverlay(query) { + function searchOverlay(query, caseInsensitive) { + var startChar; + if (typeof query == "string") { + startChar = query.charAt(0); + query = new RegExp("^" + query.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"), + caseInsensitive ? "i" : ""); + } else { + query = new RegExp("^(?:" + query.source + ")", query.ignoreCase ? "i" : ""); + } if (typeof query == "string") return {token: function(stream) { if (stream.match(query)) return "searching"; stream.next(); @@ -17,6 +25,8 @@ if (stream.match(query)) return "searching"; while (!stream.eol()) { stream.next(); + if (startChar) + stream.skipTo(startChar) || stream.skipToEnd(); if (stream.match(query, false)) break; } }}; @@ -27,15 +37,18 @@ this.overlay = null; } function getSearchState(cm) { - return cm._searchState || (cm._searchState = new SearchState()); + return cm.state.search || (cm.state.search = new SearchState()); + } + function queryCaseInsensitive(query) { + return typeof query == "string" && query == query.toLowerCase(); } function getSearchCursor(cm, query, pos) { // Heuristic: if the query string is all lowercase, do a case insensitive search. - return cm.getSearchCursor(query, pos, typeof query == "string" && query == query.toLowerCase()); + return cm.getSearchCursor(query, pos, queryCaseInsensitive(query)); } - function dialog(cm, text, shortText, f) { - if (cm.openDialog) cm.openDialog(text, f); - else f(prompt(shortText, "")); + function dialog(cm, text, shortText, deflt, f) { + if (cm.openDialog) cm.openDialog(text, f, {value: deflt}); + else f(prompt(shortText, deflt)); } function confirmDialog(cm, text, shortText, fs) { if (cm.openConfirm) cm.openConfirm(text, fs); @@ -50,12 +63,12 @@ function doSearch(cm, rev) { var state = getSearchState(cm); if (state.query) return findNext(cm, rev); - dialog(cm, queryDialog, "Search for:", function(query) { + dialog(cm, queryDialog, "Search for:", cm.getSelection(), function(query) { cm.operation(function() { if (!query || state.query) return; state.query = parseQuery(query); - cm.removeOverlay(state.overlay); - state.overlay = searchOverlay(query); + cm.removeOverlay(state.overlay, queryCaseInsensitive(state.query)); + state.overlay = searchOverlay(state.query); cm.addOverlay(state.overlay); state.posFrom = state.posTo = cm.getCursor(); findNext(cm, rev); @@ -70,6 +83,7 @@ if (!cursor.find(rev)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); state.posFrom = cursor.from(); state.posTo = cursor.to(); });} function clearSearch(cm) {cm.operation(function() { @@ -84,10 +98,10 @@ var replacementQueryDialog = 'With: '; var doReplaceConfirm = "Replace? "; function replace(cm, all) { - dialog(cm, replaceQueryDialog, "Replace:", function(query) { + dialog(cm, replaceQueryDialog, "Replace:", cm.getSelection(), function(query) { if (!query) return; query = parseQuery(query); - dialog(cm, replacementQueryDialog, "Replace with:", function(text) { + dialog(cm, replacementQueryDialog, "Replace with:", "", function(text) { if (all) { cm.operation(function() { for (var cursor = getSearchCursor(cm, query); cursor.findNext();) { @@ -108,6 +122,7 @@ (start && cursor.from().line == start.line && cursor.from().ch == start.ch)) return; } cm.setSelection(cursor.from(), cursor.to()); + cm.scrollIntoView({from: cursor.from(), to: cursor.to()}); confirmDialog(cm, doReplaceConfirm, "Replace?", [function() {doReplace(match);}, advance]); }; diff --git a/gulliver/js/codemirror/addon/search/searchcursor.js b/gulliver/js/codemirror/addon/search/searchcursor.js index fd134636e..711cf4ce5 100644 --- a/gulliver/js/codemirror/addon/search/searchcursor.js +++ b/gulliver/js/codemirror/addon/search/searchcursor.js @@ -1,11 +1,11 @@ (function(){ var Pos = CodeMirror.Pos; - function SearchCursor(cm, query, pos, caseFold) { - this.atOccurrence = false; this.cm = cm; + function SearchCursor(doc, query, pos, caseFold) { + this.atOccurrence = false; this.doc = doc; if (caseFold == null && typeof query == "string") caseFold = false; - pos = pos ? cm.clipPos(pos) : Pos(0, 0); + pos = pos ? doc.clipPos(pos) : Pos(0, 0); this.pos = {from: pos, to: pos}; // The matches method is filled in based on the type of query. @@ -17,26 +17,37 @@ this.matches = function(reverse, pos) { if (reverse) { query.lastIndex = 0; - var line = cm.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; + var line = doc.getLine(pos.line).slice(0, pos.ch), cutOff = 0, match, start; for (;;) { query.lastIndex = cutOff; var newMatch = query.exec(line); if (!newMatch) break; match = newMatch; start = match.index; - cutOff = match.index + 1; + cutOff = match.index + (match[0].length || 1); + if (cutOff == line.length) break; + } + var matchLen = (match && match[0].length) || 0; + if (!matchLen) { + if (start == 0 && line.length == 0) {match = undefined;} + else if (start != doc.getLine(pos.line).length) { + matchLen++; + } } } else { query.lastIndex = pos.ch; - var line = cm.getLine(pos.line), match = query.exec(line), - start = match && match.index; + var line = doc.getLine(pos.line), match = query.exec(line); + var matchLen = (match && match[0].length) || 0; + var start = match && match.index; + if (start + matchLen != line.length && !matchLen) matchLen = 1; } - if (match && match[0]) + if (match && matchLen) return {from: Pos(pos.line, start), - to: Pos(pos.line, start + match[0].length), + to: Pos(pos.line, start + matchLen), match: match}; }; } else { // String query + var origQuery = query; if (caseFold) query = query.toLowerCase(); var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; var target = query.split("\n"); @@ -48,33 +59,45 @@ this.matches = function() {}; } else { this.matches = function(reverse, pos) { - var line = fold(cm.getLine(pos.line)), len = query.length, match; - if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) - : (match = line.indexOf(query, pos.ch)) != -1) - return {from: Pos(pos.line, match), - to: Pos(pos.line, match + len)}; + if (reverse) { + var orig = doc.getLine(pos.line).slice(0, pos.ch), line = fold(orig); + var match = line.lastIndexOf(query); + if (match > -1) { + match = adjustPos(orig, line, match); + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; + } + } else { + var orig = doc.getLine(pos.line).slice(pos.ch), line = fold(orig); + var match = line.indexOf(query); + if (match > -1) { + match = adjustPos(orig, line, match) + pos.ch; + return {from: Pos(pos.line, match), to: Pos(pos.line, match + origQuery.length)}; + } + } }; } } else { + var origTarget = origQuery.split("\n"); this.matches = function(reverse, pos) { - var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(cm.getLine(ln)); - var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); - if (reverse ? offsetA >= pos.ch || offsetA != match.length - : offsetA <= pos.ch || offsetA != line.length - match.length) - return; - for (;;) { - if (reverse ? !ln : ln == cm.lineCount() - 1) return; - line = fold(cm.getLine(ln += reverse ? -1 : 1)); - match = target[reverse ? --idx : ++idx]; - if (idx > 0 && idx < target.length - 1) { - if (line != match) return; - else continue; - } - var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); - if (reverse ? offsetB != line.length - match.length : offsetB != match.length) - return; - var start = Pos(pos.line, offsetA), end = Pos(ln, offsetB); - return {from: reverse ? end : start, to: reverse ? start : end}; + var last = target.length - 1; + if (reverse) { + if (pos.line - (target.length - 1) < doc.firstLine()) return; + if (fold(doc.getLine(pos.line).slice(0, origTarget[last].length)) != target[target.length - 1]) return; + var to = Pos(pos.line, origTarget[last].length); + for (var ln = pos.line - 1, i = last - 1; i >= 1; --i, --ln) + if (target[i] != fold(doc.getLine(ln))) return; + var line = doc.getLine(ln), cut = line.length - origTarget[0].length; + if (fold(line.slice(cut)) != target[0]) return; + return {from: Pos(ln, cut), to: to}; + } else { + if (pos.line + (target.length - 1) > doc.lastLine()) return; + var line = doc.getLine(pos.line), cut = line.length - origTarget[0].length; + if (fold(line.slice(cut)) != target[0]) return; + var from = Pos(pos.line, cut); + for (var ln = pos.line + 1, i = 1; i < last; ++i, ++ln) + if (target[i] != fold(doc.getLine(ln))) return; + if (doc.getLine(ln).slice(0, origTarget[last].length) != target[last]) return; + return {from: from, to: Pos(ln, origTarget[last].length)}; } }; } @@ -86,7 +109,7 @@ findPrevious: function() {return this.find(true);}, find: function(reverse) { - var self = this, pos = this.cm.clipPos(reverse ? this.pos.from : this.pos.to); + var self = this, pos = this.doc.clipPos(reverse ? this.pos.from : this.pos.to); function savePosAndFail(line) { var pos = Pos(line, 0); self.pos = {from: pos, to: pos}; @@ -96,16 +119,15 @@ for (;;) { if (this.pos = this.matches(reverse, pos)) { - if (!this.pos.from || !this.pos.to) { console.log(this.matches, this.pos); } this.atOccurrence = true; return this.pos.match || true; } if (reverse) { if (!pos.line) return savePosAndFail(0); - pos = Pos(pos.line-1, this.cm.getLine(pos.line-1).length); + pos = Pos(pos.line-1, this.doc.getLine(pos.line-1).length); } else { - var maxLine = this.cm.lineCount(); + var maxLine = this.doc.lineCount(); if (pos.line == maxLine - 1) return savePosAndFail(maxLine); pos = Pos(pos.line + 1, 0); } @@ -118,13 +140,28 @@ replace: function(newText) { if (!this.atOccurrence) return; var lines = CodeMirror.splitLines(newText); - this.cm.replaceRange(lines, this.pos.from, this.pos.to); + this.doc.replaceRange(lines, this.pos.from, this.pos.to); this.pos.to = Pos(this.pos.from.line + lines.length - 1, lines[lines.length - 1].length + (lines.length == 1 ? this.pos.from.ch : 0)); } }; + // Maps a position in a case-folded line back to a position in the original line + // (compensating for codepoints increasing in number during folding) + function adjustPos(orig, folded, pos) { + if (orig.length == folded.length) return pos; + for (var pos1 = Math.min(pos, orig.length);;) { + var len1 = orig.slice(0, pos1).toLowerCase().length; + if (len1 < pos) ++pos1; + else if (len1 > pos) --pos1; + else return pos1; + } + } + CodeMirror.defineExtension("getSearchCursor", function(query, pos, caseFold) { + return new SearchCursor(this.doc, query, pos, caseFold); + }); + CodeMirror.defineDocExtension("getSearchCursor", function(query, pos, caseFold) { return new SearchCursor(this, query, pos, caseFold); }); })(); diff --git a/gulliver/js/codemirror/addon/selection/active-line.js b/gulliver/js/codemirror/addon/selection/active-line.js index 988a0ffbf..7cf7793c0 100644 --- a/gulliver/js/codemirror/addon/selection/active-line.js +++ b/gulliver/js/codemirror/addon/selection/active-line.js @@ -12,28 +12,34 @@ CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) { var prev = old && old != CodeMirror.Init; if (val && !prev) { - updateActiveLine(cm); - cm.on("cursorActivity", updateActiveLine); + updateActiveLine(cm, cm.getCursor().line); + cm.on("beforeSelectionChange", selectionChange); } else if (!val && prev) { - cm.off("cursorActivity", updateActiveLine); + cm.off("beforeSelectionChange", selectionChange); clearActiveLine(cm); - delete cm._activeLine; + delete cm.state.activeLine; } }); - + function clearActiveLine(cm) { - if ("_activeLine" in cm) { - cm.removeLineClass(cm._activeLine, "wrap", WRAP_CLASS); - cm.removeLineClass(cm._activeLine, "background", BACK_CLASS); + if ("activeLine" in cm.state) { + cm.removeLineClass(cm.state.activeLine, "wrap", WRAP_CLASS); + cm.removeLineClass(cm.state.activeLine, "background", BACK_CLASS); } } - function updateActiveLine(cm) { - var line = cm.getLineHandle(cm.getCursor().line); - if (cm._activeLine == line) return; - clearActiveLine(cm); - cm.addLineClass(line, "wrap", WRAP_CLASS); - cm.addLineClass(line, "background", BACK_CLASS); - cm._activeLine = line; + function updateActiveLine(cm, selectedLine) { + var line = cm.getLineHandleVisualStart(selectedLine); + if (cm.state.activeLine == line) return; + cm.operation(function() { + clearActiveLine(cm); + cm.addLineClass(line, "wrap", WRAP_CLASS); + cm.addLineClass(line, "background", BACK_CLASS); + cm.state.activeLine = line; + }); + } + + function selectionChange(cm, sel) { + updateActiveLine(cm, sel.head.line); } })(); diff --git a/gulliver/js/codemirror/addon/selection/mark-selection.js b/gulliver/js/codemirror/addon/selection/mark-selection.js index d7ff30c9a..c97776e49 100644 --- a/gulliver/js/codemirror/addon/selection/mark-selection.js +++ b/gulliver/js/codemirror/addon/selection/mark-selection.js @@ -1,7 +1,8 @@ // Because sometimes you need to mark the selected *text*. // // Adds an option 'styleSelectedText' which, when enabled, gives -// selected text the CSS class "CodeMirror-selectedtext". +// selected text the CSS class given as option value, or +// "CodeMirror-selectedtext" when the value is not a string. (function() { "use strict"; @@ -9,26 +10,99 @@ CodeMirror.defineOption("styleSelectedText", false, function(cm, val, old) { var prev = old && old != CodeMirror.Init; if (val && !prev) { - updateSelectedText(cm); - cm.on("cursorActivity", updateSelectedText); + cm.state.markedSelection = []; + cm.state.markedSelectionStyle = typeof val == "string" ? val : "CodeMirror-selectedtext"; + reset(cm); + cm.on("cursorActivity", onCursorActivity); + cm.on("change", onChange); } else if (!val && prev) { - cm.off("cursorActivity", updateSelectedText); - clearSelectedText(cm); - delete cm._selectionMark; + cm.off("cursorActivity", onCursorActivity); + cm.off("change", onChange); + clear(cm); + cm.state.markedSelection = cm.state.markedSelectionStyle = null; } }); - function clearSelectedText(cm) { - if (cm._selectionMark) cm._selectionMark.clear(); + function onCursorActivity(cm) { + cm.operation(function() { update(cm); }); } - function updateSelectedText(cm) { - clearSelectedText(cm); + function onChange(cm) { + if (cm.state.markedSelection.length) + cm.operation(function() { clear(cm); }); + } - if (cm.somethingSelected()) - cm._selectionMark = cm.markText(cm.getCursor("start"), cm.getCursor("end"), - {className: "CodeMirror-selectedtext"}); - else - cm._selectionMark = null; + var CHUNK_SIZE = 8; + var Pos = CodeMirror.Pos; + + function cmp(pos1, pos2) { + return pos1.line - pos2.line || pos1.ch - pos2.ch; + } + + function coverRange(cm, from, to, addAt) { + if (cmp(from, to) == 0) return; + var array = cm.state.markedSelection; + var cls = cm.state.markedSelectionStyle; + for (var line = from.line;;) { + var start = line == from.line ? from : Pos(line, 0); + var endLine = line + CHUNK_SIZE, atEnd = endLine >= to.line; + var end = atEnd ? to : Pos(endLine, 0); + var mark = cm.markText(start, end, {className: cls}); + if (addAt == null) array.push(mark); + else array.splice(addAt++, 0, mark); + if (atEnd) break; + line = endLine; + } + } + + function clear(cm) { + var array = cm.state.markedSelection; + for (var i = 0; i < array.length; ++i) array[i].clear(); + array.length = 0; + } + + function reset(cm) { + clear(cm); + var from = cm.getCursor("start"), to = cm.getCursor("end"); + coverRange(cm, from, to); + } + + function update(cm) { + var from = cm.getCursor("start"), to = cm.getCursor("end"); + if (cmp(from, to) == 0) return clear(cm); + + var array = cm.state.markedSelection; + if (!array.length) return coverRange(cm, from, to); + + var coverStart = array[0].find(), coverEnd = array[array.length - 1].find(); + if (!coverStart || !coverEnd || to.line - from.line < CHUNK_SIZE || + cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0) + return reset(cm); + + while (cmp(from, coverStart.from) > 0) { + array.shift().clear(); + coverStart = array[0].find(); + } + if (cmp(from, coverStart.from) < 0) { + if (coverStart.to.line - from.line < CHUNK_SIZE) { + array.shift().clear(); + coverRange(cm, from, coverStart.to, 0); + } else { + coverRange(cm, from, coverStart.from, 0); + } + } + + while (cmp(to, coverEnd.to) < 0) { + array.pop().clear(); + coverEnd = array[array.length - 1].find(); + } + if (cmp(to, coverEnd.to) > 0) { + if (to.line - coverEnd.from.line < CHUNK_SIZE) { + array.pop().clear(); + coverRange(cm, coverEnd.from, to); + } else { + coverRange(cm, coverEnd.to, to); + } + } } })(); diff --git a/gulliver/js/codemirror/addon/tern/tern.css b/gulliver/js/codemirror/addon/tern/tern.css new file mode 100644 index 000000000..eacc2f053 --- /dev/null +++ b/gulliver/js/codemirror/addon/tern/tern.css @@ -0,0 +1,85 @@ +.CodeMirror-Tern-completion { + padding-left: 22px; + position: relative; +} +.CodeMirror-Tern-completion:before { + position: absolute; + left: 2px; + bottom: 2px; + border-radius: 50%; + font-size: 12px; + font-weight: bold; + height: 15px; + width: 15px; + line-height: 16px; + text-align: center; + color: white; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.CodeMirror-Tern-completion-unknown:before { + content: "?"; + background: #4bb; +} +.CodeMirror-Tern-completion-object:before { + content: "O"; + background: #77c; +} +.CodeMirror-Tern-completion-fn:before { + content: "F"; + background: #7c7; +} +.CodeMirror-Tern-completion-array:before { + content: "A"; + background: #c66; +} +.CodeMirror-Tern-completion-number:before { + content: "1"; + background: #999; +} +.CodeMirror-Tern-completion-string:before { + content: "S"; + background: #999; +} +.CodeMirror-Tern-completion-bool:before { + content: "B"; + background: #999; +} + +.CodeMirror-Tern-completion-guess { + color: #999; +} + +.CodeMirror-Tern-tooltip { + border: 1px solid silver; + border-radius: 3px; + color: #444; + padding: 2px 5px; + font-size: 90%; + font-family: monospace; + background-color: white; + white-space: pre-wrap; + + max-width: 40em; + position: absolute; + z-index: 10; + -webkit-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + -moz-box-shadow: 2px 3px 5px rgba(0,0,0,.2); + box-shadow: 2px 3px 5px rgba(0,0,0,.2); + + transition: opacity 1s; + -moz-transition: opacity 1s; + -webkit-transition: opacity 1s; + -o-transition: opacity 1s; + -ms-transition: opacity 1s; +} + +.CodeMirror-Tern-hint-doc { + max-width: 25em; +} + +.CodeMirror-Tern-fname { color: black; } +.CodeMirror-Tern-farg { color: #70a; } +.CodeMirror-Tern-farg-current { text-decoration: underline; } +.CodeMirror-Tern-type { color: #07c; } +.CodeMirror-Tern-fhint-guess { opacity: .7; } diff --git a/gulliver/js/codemirror/addon/tern/tern.js b/gulliver/js/codemirror/addon/tern/tern.js new file mode 100644 index 000000000..7f83c4e4c --- /dev/null +++ b/gulliver/js/codemirror/addon/tern/tern.js @@ -0,0 +1,632 @@ +// Glue code between CodeMirror and Tern. +// +// Create a CodeMirror.TernServer to wrap an actual Tern server, +// register open documents (CodeMirror.Doc instances) with it, and +// call its methods to activate the assisting functions that Tern +// provides. +// +// Options supported (all optional): +// * defs: An array of JSON definition data structures. +// * plugins: An object mapping plugin names to configuration +// options. +// * getFile: A function(name, c) that can be used to access files in +// the project that haven't been loaded yet. Simply do c(null) to +// indicate that a file is not available. +// * fileFilter: A function(value, docName, doc) that will be applied +// to documents before passing them on to Tern. +// * switchToDoc: A function(name) that should, when providing a +// multi-file view, switch the view or focus to the named file. +// * showError: A function(editor, message) that can be used to +// override the way errors are displayed. +// * completionTip: Customize the content in tooltips for completions. +// Is passed a single argument—the completion's data as returned by +// Tern—and may return a string, DOM node, or null to indicate that +// no tip should be shown. By default the docstring is shown. +// * typeTip: Like completionTip, but for the tooltips shown for type +// queries. +// * responseFilter: A function(doc, query, request, error, data) that +// will be applied to the Tern responses before treating them +// +// +// It is possible to run the Tern server in a web worker by specifying +// these additional options: +// * useWorker: Set to true to enable web worker mode. You'll probably +// want to feature detect the actual value you use here, for example +// !!window.Worker. +// * workerScript: The main script of the worker. Point this to +// wherever you are hosting worker.js from this directory. +// * workerDeps: An array of paths pointing (relative to workerScript) +// to the Acorn and Tern libraries and any Tern plugins you want to +// load. Or, if you minified those into a single script and included +// them in the workerScript, simply leave this undefined. + +(function() { + "use strict"; + // declare global: tern + + CodeMirror.TernServer = function(options) { + var self = this; + this.options = options || {}; + var plugins = this.options.plugins || (this.options.plugins = {}); + if (!plugins.doc_comment) plugins.doc_comment = true; + if (this.options.useWorker) { + this.server = new WorkerServer(this); + } else { + this.server = new tern.Server({ + getFile: function(name, c) { return getFile(self, name, c); }, + async: true, + defs: this.options.defs || [], + plugins: plugins + }); + } + this.docs = Object.create(null); + this.trackChange = function(doc, change) { trackChange(self, doc, change); }; + + this.cachedArgHints = null; + this.activeArgHints = null; + this.jumpStack = []; + }; + + CodeMirror.TernServer.prototype = { + addDoc: function(name, doc) { + var data = {doc: doc, name: name, changed: null}; + this.server.addFile(name, docValue(this, data)); + CodeMirror.on(doc, "change", this.trackChange); + return this.docs[name] = data; + }, + + delDoc: function(name) { + var found = this.docs[name]; + if (!found) return; + CodeMirror.off(found.doc, "change", this.trackChange); + delete this.docs[name]; + this.server.delFile(name); + }, + + hideDoc: function(name) { + closeArgHints(this); + var found = this.docs[name]; + if (found && found.changed) sendDoc(this, found); + }, + + complete: function(cm) { + var self = this; + CodeMirror.showHint(cm, function(cm, c) { return hint(self, cm, c); }, {async: true}); + }, + + getHint: function(cm, c) { return hint(this, cm, c); }, + + showType: function(cm, pos) { showType(this, cm, pos); }, + + updateArgHints: function(cm) { updateArgHints(this, cm); }, + + jumpToDef: function(cm) { jumpToDef(this, cm); }, + + jumpBack: function(cm) { jumpBack(this, cm); }, + + rename: function(cm) { rename(this, cm); }, + + request: function (cm, query, c, pos) { + var self = this; + var doc = findDoc(this, cm.getDoc()); + var request = buildRequest(this, doc, query, pos); + + this.server.request(request, function (error, data) { + if (!error && self.options.responseFilter) + data = self.options.responseFilter(doc, query, request, error, data); + c(error, data); + }); + } + }; + + var Pos = CodeMirror.Pos; + var cls = "CodeMirror-Tern-"; + var bigDoc = 250; + + function getFile(ts, name, c) { + var buf = ts.docs[name]; + if (buf) + c(docValue(ts, buf)); + else if (ts.options.getFile) + ts.options.getFile(name, c); + else + c(null); + } + + function findDoc(ts, doc, name) { + for (var n in ts.docs) { + var cur = ts.docs[n]; + if (cur.doc == doc) return cur; + } + if (!name) for (var i = 0;; ++i) { + n = "[doc" + (i || "") + "]"; + if (!ts.docs[n]) { name = n; break; } + } + return ts.addDoc(name, doc); + } + + function trackChange(ts, doc, change) { + var data = findDoc(ts, doc); + + var argHints = ts.cachedArgHints; + if (argHints && argHints.doc == doc && cmpPos(argHints.start, change.to) <= 0) + ts.cachedArgHints = null; + + var changed = data.changed; + if (changed == null) + data.changed = changed = {from: change.from.line, to: change.from.line}; + var end = change.from.line + (change.text.length - 1); + if (change.from.line < changed.to) changed.to = changed.to - (change.to.line - end); + if (end >= changed.to) changed.to = end + 1; + if (changed.from > change.from.line) changed.from = change.from.line; + + if (doc.lineCount() > bigDoc && change.to - changed.from > 100) setTimeout(function() { + if (data.changed && data.changed.to - data.changed.from > 100) sendDoc(ts, data); + }, 200); + } + + function sendDoc(ts, doc) { + ts.server.request({files: [{type: "full", name: doc.name, text: docValue(ts, doc)}]}, function(error) { + if (error) console.error(error); + else doc.changed = null; + }); + } + + // Completion + + function hint(ts, cm, c) { + ts.request(cm, {type: "completions", types: true, docs: true, urls: true}, function(error, data) { + if (error) return showError(ts, cm, error); + var completions = [], after = ""; + var from = data.start, to = data.end; + if (cm.getRange(Pos(from.line, from.ch - 2), from) == "[\"" && + cm.getRange(to, Pos(to.line, to.ch + 2)) != "\"]") + after = "\"]"; + + for (var i = 0; i < data.completions.length; ++i) { + var completion = data.completions[i], className = typeToIcon(completion.type); + if (data.guess) className += " " + cls + "guess"; + completions.push({text: completion.name + after, + displayText: completion.name, + className: className, + data: completion}); + } + + var obj = {from: from, to: to, list: completions}; + var tooltip = null; + CodeMirror.on(obj, "close", function() { remove(tooltip); }); + CodeMirror.on(obj, "update", function() { remove(tooltip); }); + CodeMirror.on(obj, "select", function(cur, node) { + remove(tooltip); + var content = ts.options.completionTip ? ts.options.completionTip(cur.data) : cur.data.doc; + if (content) { + tooltip = makeTooltip(node.parentNode.getBoundingClientRect().right + window.pageXOffset, + node.getBoundingClientRect().top + window.pageYOffset, content); + tooltip.className += " " + cls + "hint-doc"; + } + }); + c(obj); + }); + } + + function typeToIcon(type) { + var suffix; + if (type == "?") suffix = "unknown"; + else if (type == "number" || type == "string" || type == "bool") suffix = type; + else if (/^fn\(/.test(type)) suffix = "fn"; + else if (/^\[/.test(type)) suffix = "array"; + else suffix = "object"; + return cls + "completion " + cls + "completion-" + suffix; + } + + // Type queries + + function showType(ts, cm, pos) { + ts.request(cm, "type", function(error, data) { + if (error) return showError(ts, cm, error); + if (ts.options.typeTip) { + var tip = ts.options.typeTip(data); + } else { + var tip = elt("span", null, elt("strong", null, data.type || "not found")); + if (data.doc) + tip.appendChild(document.createTextNode(" — " + data.doc)); + if (data.url) { + tip.appendChild(document.createTextNode(" ")); + tip.appendChild(elt("a", null, "[docs]")).href = data.url; + } + } + tempTooltip(cm, tip); + }, pos); + } + + // Maintaining argument hints + + function updateArgHints(ts, cm) { + closeArgHints(ts); + + if (cm.somethingSelected()) return; + var state = cm.getTokenAt(cm.getCursor()).state; + var inner = CodeMirror.innerMode(cm.getMode(), state); + if (inner.mode.name != "javascript") return; + var lex = inner.state.lexical; + if (lex.info != "call") return; + + var ch, argPos = lex.pos || 0, tabSize = cm.getOption("tabSize"); + for (var line = cm.getCursor().line, e = Math.max(0, line - 9), found = false; line >= e; --line) { + var str = cm.getLine(line), extra = 0; + for (var pos = 0;;) { + var tab = str.indexOf("\t", pos); + if (tab == -1) break; + extra += tabSize - (tab + extra) % tabSize - 1; + pos = tab + 1; + } + ch = lex.column - extra; + if (str.charAt(ch) == "(") {found = true; break;} + } + if (!found) return; + + var start = Pos(line, ch); + var cache = ts.cachedArgHints; + if (cache && cache.doc == cm.getDoc() && cmpPos(start, cache.start) == 0) + return showArgHints(ts, cm, argPos); + + ts.request(cm, {type: "type", preferFunction: true, end: start}, function(error, data) { + if (error || !data.type || !(/^fn\(/).test(data.type)) return; + ts.cachedArgHints = { + start: pos, + type: parseFnType(data.type), + name: data.exprName || data.name || "fn", + guess: data.guess, + doc: cm.getDoc() + }; + showArgHints(ts, cm, argPos); + }); + } + + function showArgHints(ts, cm, pos) { + closeArgHints(ts); + + var cache = ts.cachedArgHints, tp = cache.type; + var tip = elt("span", cache.guess ? cls + "fhint-guess" : null, + elt("span", cls + "fname", cache.name), "("); + for (var i = 0; i < tp.args.length; ++i) { + if (i) tip.appendChild(document.createTextNode(", ")); + var arg = tp.args[i]; + tip.appendChild(elt("span", cls + "farg" + (i == pos ? " " + cls + "farg-current" : ""), arg.name || "?")); + if (arg.type != "?") { + tip.appendChild(document.createTextNode(":\u00a0")); + tip.appendChild(elt("span", cls + "type", arg.type)); + } + } + tip.appendChild(document.createTextNode(tp.rettype ? ") ->\u00a0" : ")")); + if (tp.rettype) tip.appendChild(elt("span", cls + "type", tp.rettype)); + var place = cm.cursorCoords(null, "page"); + ts.activeArgHints = makeTooltip(place.right + 1, place.bottom, tip); + } + + function parseFnType(text) { + var args = [], pos = 3; + + function skipMatching(upto) { + var depth = 0, start = pos; + for (;;) { + var next = text.charAt(pos); + if (upto.test(next) && !depth) return text.slice(start, pos); + if (/[{\[\(]/.test(next)) ++depth; + else if (/[}\]\)]/.test(next)) --depth; + ++pos; + } + } + + // Parse arguments + if (text.charAt(pos) != ")") for (;;) { + var name = text.slice(pos).match(/^([^, \(\[\{]+): /); + if (name) { + pos += name[0].length; + name = name[1]; + } + args.push({name: name, type: skipMatching(/[\),]/)}); + if (text.charAt(pos) == ")") break; + pos += 2; + } + + var rettype = text.slice(pos).match(/^\) -> (.*)$/); + + return {args: args, rettype: rettype && rettype[1]}; + } + + // Moving to the definition of something + + function jumpToDef(ts, cm) { + function inner(varName) { + var req = {type: "definition", variable: varName || null}; + var doc = findDoc(ts, cm.getDoc()); + ts.server.request(buildRequest(ts, doc, req), function(error, data) { + if (error) return showError(ts, cm, error); + if (!data.file && data.url) { window.open(data.url); return; } + + if (data.file) { + var localDoc = ts.docs[data.file], found; + if (localDoc && (found = findContext(localDoc.doc, data))) { + ts.jumpStack.push({file: doc.name, + start: cm.getCursor("from"), + end: cm.getCursor("to")}); + moveTo(ts, doc, localDoc, found.start, found.end); + return; + } + } + showError(ts, cm, "Could not find a definition."); + }); + } + + if (!atInterestingExpression(cm)) + dialog(cm, "Jump to variable", function(name) { if (name) inner(name); }); + else + inner(); + } + + function jumpBack(ts, cm) { + var pos = ts.jumpStack.pop(), doc = pos && ts.docs[pos.file]; + if (!doc) return; + moveTo(ts, findDoc(ts, cm.getDoc()), doc, pos.start, pos.end); + } + + function moveTo(ts, curDoc, doc, start, end) { + doc.doc.setSelection(end, start); + if (curDoc != doc && ts.options.switchToDoc) { + closeArgHints(ts); + ts.options.switchToDoc(doc.name); + } + } + + // The {line,ch} representation of positions makes this rather awkward. + function findContext(doc, data) { + var before = data.context.slice(0, data.contextOffset).split("\n"); + var startLine = data.start.line - (before.length - 1); + var start = Pos(startLine, (before.length == 1 ? data.start.ch : doc.getLine(startLine).length) - before[0].length); + + var text = doc.getLine(startLine).slice(start.ch); + for (var cur = startLine + 1; cur < doc.lineCount() && text.length < data.context.length; ++cur) + text += "\n" + doc.getLine(cur); + if (text.slice(0, data.context.length) == data.context) return data; + + var cursor = doc.getSearchCursor(data.context, 0, false); + var nearest, nearestDist = Infinity; + while (cursor.findNext()) { + var from = cursor.from(), dist = Math.abs(from.line - start.line) * 10000; + if (!dist) dist = Math.abs(from.ch - start.ch); + if (dist < nearestDist) { nearest = from; nearestDist = dist; } + } + if (!nearest) return null; + + if (before.length == 1) + nearest.ch += before[0].length; + else + nearest = Pos(nearest.line + (before.length - 1), before[before.length - 1].length); + if (data.start.line == data.end.line) + var end = Pos(nearest.line, nearest.ch + (data.end.ch - data.start.ch)); + else + var end = Pos(nearest.line + (data.end.line - data.start.line), data.end.ch); + return {start: nearest, end: end}; + } + + function atInterestingExpression(cm) { + var pos = cm.getCursor("end"), tok = cm.getTokenAt(pos); + if (tok.start < pos.ch && (tok.type == "comment" || tok.type == "string")) return false; + return /\w/.test(cm.getLine(pos.line).slice(Math.max(pos.ch - 1, 0), pos.ch + 1)); + } + + // Variable renaming + + function rename(ts, cm) { + var token = cm.getTokenAt(cm.getCursor()); + if (!/\w/.test(token.string)) showError(ts, cm, "Not at a variable"); + dialog(cm, "New name for " + token.string, function(newName) { + ts.request(cm, {type: "rename", newName: newName, fullDocs: true}, function(error, data) { + if (error) return showError(ts, cm, error); + applyChanges(ts, data.changes); + }); + }); + } + + var nextChangeOrig = 0; + function applyChanges(ts, changes) { + var perFile = Object.create(null); + for (var i = 0; i < changes.length; ++i) { + var ch = changes[i]; + (perFile[ch.file] || (perFile[ch.file] = [])).push(ch); + } + for (var file in perFile) { + var known = ts.docs[file], chs = perFile[file];; + if (!known) continue; + chs.sort(function(a, b) { return cmpPos(b.start, a.start); }); + var origin = "*rename" + (++nextChangeOrig); + for (var i = 0; i < chs.length; ++i) { + var ch = chs[i]; + known.doc.replaceRange(ch.text, ch.start, ch.end, origin); + } + } + } + + // Generic request-building helper + + function buildRequest(ts, doc, query, pos) { + var files = [], offsetLines = 0, allowFragments = !query.fullDocs; + if (!allowFragments) delete query.fullDocs; + if (typeof query == "string") query = {type: query}; + query.lineCharPositions = true; + if (query.end == null) { + query.end = pos || doc.doc.getCursor("end"); + if (doc.doc.somethingSelected()) + query.start = doc.doc.getCursor("start"); + } + var startPos = query.start || query.end; + + if (doc.changed) { + if (doc.doc.lineCount() > bigDoc && allowFragments !== false && + doc.changed.to - doc.changed.from < 100 && + doc.changed.from <= startPos.line && doc.changed.to > query.end.line) { + files.push(getFragmentAround(doc, startPos, query.end)); + query.file = "#0"; + var offsetLines = files[0].offsetLines; + if (query.start != null) query.start = Pos(query.start.line - -offsetLines, query.start.ch); + query.end = Pos(query.end.line - offsetLines, query.end.ch); + } else { + files.push({type: "full", + name: doc.name, + text: docValue(ts, doc)}); + query.file = doc.name; + doc.changed = null; + } + } else { + query.file = doc.name; + } + for (var name in ts.docs) { + var cur = ts.docs[name]; + if (cur.changed && cur != doc) { + files.push({type: "full", name: cur.name, text: docValue(ts, cur)}); + cur.changed = null; + } + } + + return {query: query, files: files}; + } + + function getFragmentAround(data, start, end) { + var doc = data.doc; + var minIndent = null, minLine = null, endLine, tabSize = 4; + for (var p = start.line - 1, min = Math.max(0, p - 50); p >= min; --p) { + var line = doc.getLine(p), fn = line.search(/\bfunction\b/); + if (fn < 0) continue; + var indent = CodeMirror.countColumn(line, null, tabSize); + if (minIndent != null && minIndent <= indent) continue; + minIndent = indent; + minLine = p; + } + if (minLine == null) minLine = min; + var max = Math.min(doc.lastLine(), end.line + 20); + if (minIndent == null || minIndent == CodeMirror.countColumn(doc.getLine(start.line), null, tabSize)) + endLine = max; + else for (endLine = end.line + 1; endLine < max; ++endLine) { + var indent = CodeMirror.countColumn(doc.getLine(endLine), null, tabSize); + if (indent <= minIndent) break; + } + var from = Pos(minLine, 0); + + return {type: "part", + name: data.name, + offsetLines: from.line, + text: doc.getRange(from, Pos(endLine, 0))}; + } + + // Generic utilities + + function cmpPos(a, b) { return a.line - b.line || a.ch - b.ch; } + + function elt(tagname, cls /*, ... elts*/) { + var e = document.createElement(tagname); + if (cls) e.className = cls; + for (var i = 2; i < arguments.length; ++i) { + var elt = arguments[i]; + if (typeof elt == "string") elt = document.createTextNode(elt); + e.appendChild(elt); + } + return e; + } + + function dialog(cm, text, f) { + if (cm.openDialog) + cm.openDialog(text + ": ", f); + else + f(prompt(text, "")); + } + + // Tooltips + + function tempTooltip(cm, content) { + var where = cm.cursorCoords(); + var tip = makeTooltip(where.right + 1, where.bottom, content); + function clear() { + if (!tip.parentNode) return; + cm.off("cursorActivity", clear); + fadeOut(tip); + } + setTimeout(clear, 1700); + cm.on("cursorActivity", clear); + } + + function makeTooltip(x, y, content) { + var node = elt("div", cls + "tooltip", content); + node.style.left = x + "px"; + node.style.top = y + "px"; + document.body.appendChild(node); + return node; + } + + function remove(node) { + var p = node && node.parentNode; + if (p) p.removeChild(node); + } + + function fadeOut(tooltip) { + tooltip.style.opacity = "0"; + setTimeout(function() { remove(tooltip); }, 1100); + } + + function showError(ts, cm, msg) { + if (ts.options.showError) + ts.options.showError(cm, msg); + else + tempTooltip(cm, String(msg)); + } + + function closeArgHints(ts) { + if (ts.activeArgHints) { remove(ts.activeArgHints); ts.activeArgHints = null; } + } + + function docValue(ts, doc) { + var val = doc.doc.getValue(); + if (ts.options.fileFilter) val = ts.options.fileFilter(val, doc.name, doc.doc); + return val; + } + + // Worker wrapper + + function WorkerServer(ts) { + var worker = new Worker(ts.options.workerScript); + worker.postMessage({type: "init", + defs: ts.options.defs, + plugins: ts.options.plugins, + scripts: ts.options.workerDeps}); + var msgId = 0, pending = {}; + + function send(data, c) { + if (c) { + data.id = ++msgId; + pending[msgId] = c; + } + worker.postMessage(data); + } + worker.onmessage = function(e) { + var data = e.data; + if (data.type == "getFile") { + getFile(ts, data.name, function(err, text) { + send({type: "getFile", err: String(err), text: text, id: data.id}); + }); + } else if (data.type == "debug") { + console.log(data.message); + } else if (data.id && pending[data.id]) { + pending[data.id](data.err, data.body); + delete pending[data.id]; + } + }; + worker.onerror = function(e) { + for (var id in pending) pending[id](e); + pending = {}; + }; + + this.addFile = function(name, text) { send({type: "add", name: name, text: text}); }; + this.delFile = function(name) { send({type: "del", name: name}); }; + this.request = function(body, c) { send({type: "req", body: body}, c); }; + } +})(); diff --git a/gulliver/js/codemirror/addon/tern/worker.js b/gulliver/js/codemirror/addon/tern/worker.js new file mode 100644 index 000000000..1ff63de41 --- /dev/null +++ b/gulliver/js/codemirror/addon/tern/worker.js @@ -0,0 +1,41 @@ +// declare global: tern, server + +var server; + +this.onmessage = function(e) { + var data = e.data; + switch (data.type) { + case "init": return startServer(data.defs, data.plugins, data.scripts); + case "add": return server.addFile(data.name, data.text); + case "del": return server.delFile(data.name); + case "req": return server.request(data.body, function(err, reqData) { + postMessage({id: data.id, body: reqData, err: err && String(err)}); + }); + case "getFile": + var c = pending[data.id]; + delete pending[data.id]; + return c(data.err, data.text); + default: throw new Error("Unknown message type: " + data.type); + } +}; + +var nextId = 0, pending = {}; +function getFile(file, c) { + postMessage({type: "getFile", name: file, id: ++nextId}); + pending[nextId] = c; +} + +function startServer(defs, plugins, scripts) { + if (scripts) importScripts.apply(null, scripts); + + server = new tern.Server({ + getFile: getFile, + async: true, + defs: defs, + plugins: plugins + }); +} + +var console = { + log: function(v) { postMessage({type: "debug", message: v}); } +}; diff --git a/gulliver/js/codemirror/addon/wrap/hardwrap.js b/gulliver/js/codemirror/addon/wrap/hardwrap.js new file mode 100644 index 000000000..f6d99212a --- /dev/null +++ b/gulliver/js/codemirror/addon/wrap/hardwrap.js @@ -0,0 +1,111 @@ +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + + function findParagraph(cm, pos, options) { + var startRE = options.paragraphStart || cm.getHelper(pos, "paragraphStart"); + for (var start = pos.line, first = cm.firstLine(); start > first; --start) { + var line = cm.getLine(start); + if (startRE && startRE.test(line)) break; + if (!/\S/.test(line)) { ++start; break; } + } + var endRE = options.paragraphEnd || cm.getHelper(pos, "paragraphEnd"); + for (var end = pos.line + 1, last = cm.lastLine(); end <= last; ++end) { + var line = cm.getLine(end); + if (endRE && endRE.test(line)) { ++end; break; } + if (!/\S/.test(line)) break; + } + return {from: start, to: end}; + } + + function findBreakPoint(text, column, wrapOn, killTrailingSpace) { + for (var at = column; at > 0; --at) + if (wrapOn.test(text.slice(at - 1, at + 1))) break; + if (at == 0) at = column; + var endOfText = at; + if (killTrailingSpace) + while (text.charAt(endOfText - 1) == " ") --endOfText; + return {from: endOfText, to: at}; + } + + function wrapRange(cm, from, to, options) { + from = cm.clipPos(from); to = cm.clipPos(to); + var column = options.column || 80; + var wrapOn = options.wrapOn || /\s\S|-[^\.\d]/; + var killTrailing = options.killTrailingSpace !== false; + var changes = [], curLine = "", curNo = from.line; + var lines = cm.getRange(from, to, false); + if (!lines.length) return null; + var leadingSpace = lines[0].match(/^[ \t]*/)[0]; + + for (var i = 0; i < lines.length; ++i) { + var text = lines[i], oldLen = curLine.length, spaceInserted = 0; + if (curLine && text && !wrapOn.test(curLine.charAt(curLine.length - 1) + text.charAt(0))) { + curLine += " "; + spaceInserted = 1; + } + var spaceTrimmed = ""; + if (i) { + spaceTrimmed = text.match(/^\s*/)[0]; + text = text.slice(spaceTrimmed.length); + } + curLine += text; + if (i) { + var firstBreak = curLine.length > column && leadingSpace == spaceTrimmed && + findBreakPoint(curLine, column, wrapOn, killTrailing); + // If this isn't broken, or is broken at a different point, remove old break + if (!firstBreak || firstBreak.from != oldLen || firstBreak.to != oldLen + spaceInserted) { + changes.push({text: [spaceInserted ? " " : ""], + from: Pos(curNo, oldLen), + to: Pos(curNo + 1, spaceTrimmed.length)}); + } else { + curLine = leadingSpace + text; + ++curNo; + } + } + while (curLine.length > column) { + var bp = findBreakPoint(curLine, column, wrapOn, killTrailing); + changes.push({text: ["", leadingSpace], + from: Pos(curNo, bp.from), + to: Pos(curNo, bp.to)}); + curLine = leadingSpace + curLine.slice(bp.to); + ++curNo; + } + } + if (changes.length) cm.operation(function() { + for (var i = 0; i < changes.length; ++i) { + var change = changes[i]; + cm.replaceRange(change.text, change.from, change.to); + } + }); + return changes.length ? {from: changes[0].from, to: CodeMirror.changeEnd(changes[changes.length - 1])} : null; + } + + CodeMirror.defineExtension("wrapParagraph", function(pos, options) { + options = options || {}; + if (!pos) pos = this.getCursor(); + var para = findParagraph(this, pos, options); + return wrapRange(this, Pos(para.from, 0), Pos(para.to - 1), options); + }); + + CodeMirror.defineExtension("wrapRange", function(from, to, options) { + return wrapRange(this, from, to, options || {}); + }); + + CodeMirror.defineExtension("wrapParagraphsInRange", function(from, to, options) { + options = options || {}; + var cm = this, paras = []; + for (var line = from.line; line <= to.line;) { + var para = findParagraph(cm, Pos(line, 0), options); + paras.push(para); + line = para.to; + } + var madeChange = false; + if (paras.length) cm.operation(function() { + for (var i = paras.length - 1; i >= 0; --i) + madeChange = madeChange || wrapRange(cm, Pos(paras[i].from, 0), Pos(paras[i].to - 1), options); + }); + return madeChange; + }); +})(); diff --git a/gulliver/js/codemirror/bin/authors.sh b/gulliver/js/codemirror/bin/authors.sh new file mode 100644 index 000000000..b3ee99c6d --- /dev/null +++ b/gulliver/js/codemirror/bin/authors.sh @@ -0,0 +1,6 @@ +# Combine existing list of authors with everyone known in git, sort, add header. +tail --lines=+3 AUTHORS > AUTHORS.tmp +git log --format='%aN' >> AUTHORS.tmp +echo -e "List of CodeMirror contributors. Updated before every release.\n" > AUTHORS +sort -u AUTHORS.tmp >> AUTHORS +rm -f AUTHORS.tmp diff --git a/gulliver/js/codemirror/bin/compress b/gulliver/js/codemirror/bin/compress index d059b618b..809fbe83d 100755 --- a/gulliver/js/codemirror/bin/compress +++ b/gulliver/js/codemirror/bin/compress @@ -29,14 +29,15 @@ function help(ok) { process.exit(ok ? 0 : 1); } -var local = null, args = null, files = [], blob = ""; +var local = null, args = [], extraArgs = null, files = [], blob = ""; for (var i = 2; i < process.argv.length; ++i) { var arg = process.argv[i]; if (arg == "--local" && i + 1 < process.argv.length) { var parts = process.argv[++i].split(/\s+/); local = parts[0]; - args = parts.slice(1); + extraArgs = parts.slice(1); + if (!extraArgs.length) extraArgs = ["-c", "-m"]; } else if (arg == "--help") { help(true); } else if (arg[0] != "-") { @@ -73,7 +74,7 @@ if (files.length) { } if (local) { - require("child_process").spawn(local, args, {stdio: ["ignore", process.stdout, process.stderr]}); + require("child_process").spawn(local, args.concat(extraArgs), {stdio: ["ignore", process.stdout, process.stderr]}); } else { var data = new Buffer("js_code=" + require("querystring").escape(blob), "utf8"); var req = require("http").request({ diff --git a/gulliver/js/codemirror/bin/lint b/gulliver/js/codemirror/bin/lint new file mode 100644 index 000000000..4f70994c5 --- /dev/null +++ b/gulliver/js/codemirror/bin/lint @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +var lint = require("../test/lint/lint"), + path = require("path"); + +if (process.argv.length > 2) { + lint.checkDir(process.argv[2]); +} else { + process.chdir(path.resolve(__dirname, "..")); + lint.checkDir("lib"); + lint.checkDir("mode"); + lint.checkDir("addon"); + lint.checkDir("keymap"); +} + +process.exit(lint.success() ? 0 : 1); diff --git a/gulliver/js/codemirror/bin/release b/gulliver/js/codemirror/bin/release new file mode 100644 index 000000000..f92ab006d --- /dev/null +++ b/gulliver/js/codemirror/bin/release @@ -0,0 +1,41 @@ +#!/usr/bin/env node + +var fs = require("fs"), child = require("child_process"); + +var number, bumpOnly; + +for (var i = 2; i < process.argv.length; i++) { + if (process.argv[i] == "-bump") bumpOnly = true; + else if (/^\d+\.\d+\.\d+$/.test(process.argv[i])) number = process.argv[i]; + else { console.log("Bogus command line arg: " + process.argv[i]); process.exit(1); } +} + +if (!number) { console.log("Must give a version"); process.exit(1); } + +function rewrite(file, f) { + fs.writeFileSync(file, f(fs.readFileSync(file, "utf8")), "utf8"); +} + +rewrite("lib/codemirror.js", function(lib) { + return lib.replace(/CodeMirror\.version = "\d+\.\d+\.\d+"/, + "CodeMirror.version = \"" + number + "\""); +}); +rewrite("package.json", function(pack) { + return pack.replace(/"version":"\d+\.\d+\.\d+"/, "\"version\":\"" + number + "\""); +}); + +if (bumpOnly) process.exit(0); + +child.exec("bash bin/authors.sh", function(){}); + +var simple = number.slice(0, number.lastIndexOf(".")); + +rewrite("doc/compress.html", function(cmp) { + return cmp.replace(/\n "); +}); + +rewrite("index.html", function(index) { + return index.replace(/version 3.20<\/strong>/, + "version " + simple + ""); +}); diff --git a/gulliver/js/codemirror/bin/source-highlight b/gulliver/js/codemirror/bin/source-highlight new file mode 100644 index 000000000..7596ed776 --- /dev/null +++ b/gulliver/js/codemirror/bin/source-highlight @@ -0,0 +1,61 @@ +#!/usr/bin/env node + +// Simple command-line code highlighting tool. Reads code from stdin, +// spits html to stdout. For example: +// +// echo 'function foo(a) { return a; }' | bin/source-highlight -s javascript +// bin/source-highlight -s + +var fs = require("fs"); + +CodeMirror = require("../addon/runmode/runmode.node.js"); +require("../mode/meta.js"); + +var sPos = process.argv.indexOf("-s"); +if (sPos == -1 || sPos == process.argv.length - 1) { + console.error("Usage: source-highlight -s language"); + process.exit(1); +} +var lang = process.argv[sPos + 1].toLowerCase(), modeName = lang; +CodeMirror.modeInfo.forEach(function(info) { + if (info.mime == lang) { + modeName = info.mode; + } else if (info.name.toLowerCase() == lang) { + modeName = info.mode; + lang = info.mime; + } +}); + +function ensureMode(name) { + if (CodeMirror.modes[name] || name == "null") return; + try { + require("../mode/" + name + "/" + name + ".js"); + } catch(e) { + console.error("Could not load mode " + name + "."); + process.exit(1); + } + var obj = CodeMirror.modes[name]; + if (obj.dependencies) obj.dependencies.forEach(ensureMode); +} +ensureMode(modeName); + +function esc(str) { + return str.replace(/[<&]/, function(ch) { return ch == "&" ? "&" : "<"; }); +} + +var code = fs.readFileSync("/dev/stdin", "utf8"); +var curStyle = null, accum = ""; +function flush() { + if (curStyle) process.stdout.write("" + esc(accum) + ""); + else process.stdout.write(esc(accum)); +} + +CodeMirror.runMode(code, lang, function(text, style) { + if (style != curStyle) { + flush(); + curStyle = style; accum = text; + } else { + accum += text; + } +}); +flush(); diff --git a/gulliver/js/codemirror/bower.json b/gulliver/js/codemirror/bower.json new file mode 100644 index 000000000..66e049dfb --- /dev/null +++ b/gulliver/js/codemirror/bower.json @@ -0,0 +1,15 @@ +{ + "name": "CodeMirror", + "main": ["lib/codemirror.js", "lib/codemirror.css"], + "ignore": [ + "**/.*", + "node_modules", + "components", + "bin", + "demo", + "doc", + "test", + "index.html", + "package.json" + ] +} diff --git a/gulliver/js/codemirror/demo/activeline.html b/gulliver/js/codemirror/demo/activeline.html deleted file mode 100644 index b0ea9b907..000000000 --- a/gulliver/js/codemirror/demo/activeline.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - CodeMirror: Active Line Demo - - - - - - - - - -

CodeMirror: Active Line Demo

- -
- - - -

Styling the current cursor line.

- - - diff --git a/gulliver/js/codemirror/demo/bidi.html b/gulliver/js/codemirror/demo/bidi.html deleted file mode 100644 index 47feb8c5a..000000000 --- a/gulliver/js/codemirror/demo/bidi.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - CodeMirror: Bi-directional Text Demo - - - - - - - - -

CodeMirror: Bi-directional Text Demo

- -
- - - -

Demonstration of bi-directional text support. See - the related - blog post for more background.

- - - diff --git a/gulliver/js/codemirror/demo/btree.html b/gulliver/js/codemirror/demo/btree.html deleted file mode 100644 index 5e5ce0abf..000000000 --- a/gulliver/js/codemirror/demo/btree.html +++ /dev/null @@ -1,87 +0,0 @@ - - - - - CodeMirror: B-Tree visualization - - - - - - - -

CodeMirror: B-Tree visualization

- -

Shows a visual representation of the b-tree that CodeMirror - uses to store its document. See - the corresponding - blog post for a description of this format. The gray blocks - under each leaf show the lines it holds (with their width - representing the line height). Add and remove content to see how - the nodes are split and merged to keep the tree balanced.

- -
-
-
-
-
-
- - - -

- - - diff --git a/gulliver/js/codemirror/demo/buffers.html b/gulliver/js/codemirror/demo/buffers.html deleted file mode 100644 index bfd8248e4..000000000 --- a/gulliver/js/codemirror/demo/buffers.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - CodeMirror: Multiple Buffer & Split View Demo - - - - - - - - - -

CodeMirror: Multiple Buffer & Split View Demo

- -
-
- Select buffer: -     -
-
-
- Select buffer: -     -
- - - -

Demonstration of - using linked documents - to provide a split view on a document, and - using swapDoc - to use a single editor to display multiple documents.

- - - diff --git a/gulliver/js/codemirror/demo/changemode.html b/gulliver/js/codemirror/demo/changemode.html deleted file mode 100644 index 364c5cdb0..000000000 --- a/gulliver/js/codemirror/demo/changemode.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CodeMirror: Mode-Changing Demo - - - - - - - - - -

CodeMirror: Mode-Changing demo

- -
- -

On changes to the content of the above editor, a (crude) script -tries to auto-detect the language used, and switches the editor to -either JavaScript or Scheme mode based on that.

- - - - diff --git a/gulliver/js/codemirror/demo/closebrackets.html b/gulliver/js/codemirror/demo/closebrackets.html deleted file mode 100644 index 47304ea88..000000000 --- a/gulliver/js/codemirror/demo/closebrackets.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - CodeMirror: Closebrackets Demo - - - - - - - - - -

CodeMirror: Closebrackets Demo

- -

Type a bracket like '[', '(', '{', '"', or ''' - and the addon - will auto-close it. Type the closing variant when directly in - front of a matching character and it will overwrite it.

- -

If you backspace over a starting bracket while inside empty brackets - (e.g. {|}), it will delete the closing bracket for you.

- - -
- - - - diff --git a/gulliver/js/codemirror/demo/closetag.html b/gulliver/js/codemirror/demo/closetag.html deleted file mode 100644 index 87f4f019f..000000000 --- a/gulliver/js/codemirror/demo/closetag.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - CodeMirror: Close-Tag Demo - - - - - - - - - - - - -

Close-Tag Demo

-
    -
  • Type an html tag. When you type '>' or '/', the tag will auto-close/complete. Block-level tags will indent.
  • -
  • There are options for disabling tag closing or customizing the list of tags to indent.
  • -
  • Works with "text/html" (based on htmlmixed.js or xml.js) mode.
  • -
  • View source for key binding details.
  • -
- -
- - - - diff --git a/gulliver/js/codemirror/demo/complete.html b/gulliver/js/codemirror/demo/complete.html deleted file mode 100644 index 02ce65888..000000000 --- a/gulliver/js/codemirror/demo/complete.html +++ /dev/null @@ -1,70 +0,0 @@ - - - - - CodeMirror: Autocomplete Demo - - - - - - - - - -

CodeMirror: Autocomplete demo

- -
- -

Press ctrl-space to activate autocompletion. See -the code (here -and here) to figure out -how it works.

- - - - diff --git a/gulliver/js/codemirror/demo/completePHP.html b/gulliver/js/codemirror/demo/completePHP.html deleted file mode 100644 index 9fcd486c0..000000000 --- a/gulliver/js/codemirror/demo/completePHP.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - CodeMirror: Autocomplete Demo - - - - - - - - - - - - - - - - -

CodeMirror: Autocomplete demo

- -
- - - -

Press ctrl-space to activate autocompletion. See -the code (here -and here) to figure out -how it works.

- - - - diff --git a/gulliver/js/codemirror/demo/emacs.html b/gulliver/js/codemirror/demo/emacs.html deleted file mode 100644 index b37a46b04..000000000 --- a/gulliver/js/codemirror/demo/emacs.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Emacs bindings demo - - - - - - - - - -

CodeMirror: Emacs bindings demo

- -
- -

The emacs keybindings are enabled by -including keymap/emacs.js and setting -the keyMap option to "emacs". Because -CodeMirror's internal API is quite different from Emacs, they are only -a loose approximation of actual emacs bindings, though.

- -

Also note that a lot of browsers disallow certain keys from being -captured. For example, Chrome blocks both Ctrl-W and Ctrl-N, with the -result that idiomatic use of Emacs keys will constantly close your tab -or open a new window.

- - - - - diff --git a/gulliver/js/codemirror/demo/folding.html b/gulliver/js/codemirror/demo/folding.html deleted file mode 100644 index cd0417aac..000000000 --- a/gulliver/js/codemirror/demo/folding.html +++ /dev/null @@ -1,69 +0,0 @@ - - - - - CodeMirror: Code Folding Demo - - - - - - - - - - - - -

CodeMirror: Code Folding Demo

- -

Demonstration of code folding using the code - in foldcode.js. - Press ctrl-q or click on the gutter to fold a block, again - to unfold.

-
-
JavaScript:
-
HTML:
-
- - - diff --git a/gulliver/js/codemirror/demo/fullscreen.html b/gulliver/js/codemirror/demo/fullscreen.html deleted file mode 100644 index 2709ebb4b..000000000 --- a/gulliver/js/codemirror/demo/fullscreen.html +++ /dev/null @@ -1,147 +0,0 @@ - - - - - CodeMirror: Full Screen Editing - - - - - - - - - -

CodeMirror: Full Screen Editing

- -
- - -

Press F11 when cursor is in the editor to toggle full screen editing. Esc can also be used to exit full screen editing.

- - diff --git a/gulliver/js/codemirror/demo/html5complete.html b/gulliver/js/codemirror/demo/html5complete.html deleted file mode 100644 index 5091354ae..000000000 --- a/gulliver/js/codemirror/demo/html5complete.html +++ /dev/null @@ -1,92 +0,0 @@ - - - - - CodeMirror: Close-Tag Demo - - - - - - - - - - - - - - - - -

HTML5 code completation demo

-
    -
  • Type an html tag. If you press Ctrl+Space a hint panel show the code suggest. You can type to autocomplete tags, attributes if your cursor are inner a tag or attribute values if your cursor are inner a attribute value.
  • -
- -
- - - diff --git a/gulliver/js/codemirror/demo/indentwrap.html b/gulliver/js/codemirror/demo/indentwrap.html deleted file mode 100644 index c367c945d..000000000 --- a/gulliver/js/codemirror/demo/indentwrap.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CodeMirror: Indented wrapped line demo - - - - - - - - -

CodeMirror: Indented wrapped line demo

- -
- -

This page uses a hack on top of the "renderLine" - event to make wrapped text line up with the base indentation of - the line.

- - - - - diff --git a/gulliver/js/codemirror/demo/lint.html b/gulliver/js/codemirror/demo/lint.html deleted file mode 100644 index ece8b1cef..000000000 --- a/gulliver/js/codemirror/demo/lint.html +++ /dev/null @@ -1,90 +0,0 @@ - - - - - CodeMirror: Linter Demo - - - - - - - - - - - - - - -

CodeMirror: Linter Demo

- -

- -

- - - - - diff --git a/gulliver/js/codemirror/demo/loadmode.html b/gulliver/js/codemirror/demo/loadmode.html deleted file mode 100644 index 1bd958f70..000000000 --- a/gulliver/js/codemirror/demo/loadmode.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - CodeMirror: Lazy Mode Loading Demo - - - - - - - - -

CodeMirror: Lazy Mode Loading

- -
-

- - - - diff --git a/gulliver/js/codemirror/demo/marker.html b/gulliver/js/codemirror/demo/marker.html deleted file mode 100644 index f0981e4d5..000000000 --- a/gulliver/js/codemirror/demo/marker.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CodeMirror: Breakpoint Demo - - - - - - - - -

CodeMirror: Breakpoint demo

- -
- -

Click the line-number gutter to add or remove 'breakpoints'.

- - - - - diff --git a/gulliver/js/codemirror/demo/markselection.html b/gulliver/js/codemirror/demo/markselection.html deleted file mode 100644 index e1c054869..000000000 --- a/gulliver/js/codemirror/demo/markselection.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CodeMirror: Match Highlighter Demo - - - - - - - - - -

CodeMirror: Mark Selection Demo

- -
- - - -

Simple addon to easily mark (and style) selected text.

- - - diff --git a/gulliver/js/codemirror/demo/matchhighlighter.html b/gulliver/js/codemirror/demo/matchhighlighter.html deleted file mode 100644 index c574fbae8..000000000 --- a/gulliver/js/codemirror/demo/matchhighlighter.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CodeMirror: Match Highlighter Demo - - - - - - - - - -

CodeMirror: Match Highlighter Demo

- -
- - - -

Search and highlight occurences of the selected text.

- - - diff --git a/gulliver/js/codemirror/demo/multiplex.html b/gulliver/js/codemirror/demo/multiplex.html deleted file mode 100644 index 9ebe8f357..000000000 --- a/gulliver/js/codemirror/demo/multiplex.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Multiplexing Parser Demo - - - - - - - - - -

CodeMirror: Multiplexing Parser Demo

- -
- - - -

Demonstration of a multiplexing mode, which, at certain - boundary strings, switches to one or more inner modes. The out - (HTML) mode does not get fed the content of the << - >> blocks. See - the manual and - the source for more - information.

- - - diff --git a/gulliver/js/codemirror/demo/mustache.html b/gulliver/js/codemirror/demo/mustache.html deleted file mode 100644 index 9d2081dc4..000000000 --- a/gulliver/js/codemirror/demo/mustache.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - CodeMirror: Overlay Parser Demo - - - - - - - - - -

CodeMirror: Overlay Parser Demo

- -
- - - -

Demonstration of a mode that parses HTML, highlighting - the Mustache templating - directives inside of it by using the code - in overlay.js. View - source to see the 15 lines of code needed to accomplish this.

- - - diff --git a/gulliver/js/codemirror/demo/placeholder.html b/gulliver/js/codemirror/demo/placeholder.html deleted file mode 100644 index b0f245695..000000000 --- a/gulliver/js/codemirror/demo/placeholder.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - CodeMirror: Placeholder demo - - - - - - - - -

CodeMirror: Placeholder demo

- -
- -

The placeholder - plug-in adds an option placeholder that can be set to - make text appear in the editor when it is empty and not focused. - If the source textarea has a placeholder attribute, - it will automatically be inherited.

- - - - - diff --git a/gulliver/js/codemirror/demo/preview.html b/gulliver/js/codemirror/demo/preview.html deleted file mode 100644 index f70cdb009..000000000 --- a/gulliver/js/codemirror/demo/preview.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - CodeMirror: HTML5 preview - - - - - - - - - - -

CodeMirror: HTML5 preview

- - - - - diff --git a/gulliver/js/codemirror/demo/resize.html b/gulliver/js/codemirror/demo/resize.html deleted file mode 100644 index 7badc2bfb..000000000 --- a/gulliver/js/codemirror/demo/resize.html +++ /dev/null @@ -1,49 +0,0 @@ - - - - - CodeMirror: Autoresize Demo - - - - - - - - -

CodeMirror: Autoresize demo

- -
- -

By setting a few CSS properties, and giving -the viewportMargin -a value of Infinity, CodeMirror can be made to -automatically resize to fit its content.

- - - - - diff --git a/gulliver/js/codemirror/demo/runmode.html b/gulliver/js/codemirror/demo/runmode.html deleted file mode 100644 index dba808ba7..000000000 --- a/gulliver/js/codemirror/demo/runmode.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - CodeMirror: Mode Runner Demo - - - - - - - -

CodeMirror: Mode Runner Demo

- -
- -

-
-    
-
-    

Running a CodeMirror mode outside of the editor. - The CodeMirror.runMode function, defined - in lib/runmode.js takes the following arguments:

- -
-
text (string)
-
The document to run through the highlighter.
-
mode (mode spec)
-
The mode to use (must be loaded as normal).
-
output (function or DOM node)
-
If this is a function, it will be called for each token with - two arguments, the token's text and the token's style class (may - be null for unstyled tokens). If it is a DOM node, - the tokens will be converted to span elements as in - an editor, and inserted into the node - (through innerHTML).
-
- - - diff --git a/gulliver/js/codemirror/demo/search.html b/gulliver/js/codemirror/demo/search.html deleted file mode 100644 index d72107156..000000000 --- a/gulliver/js/codemirror/demo/search.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - - CodeMirror: Search/Replace Demo - - - - - - - - - - - - -

CodeMirror: Search/Replace Demo

- -
- - - -

Demonstration of primitive search/replace functionality. The - keybindings (which can be overridden by custom keymaps) are:

-
-
Ctrl-F / Cmd-F
Start searching
-
Ctrl-G / Cmd-G
Find next
-
Shift-Ctrl-G / Shift-Cmd-G
Find previous
-
Shift-Ctrl-F / Cmd-Option-F
Replace
-
Shift-Ctrl-R / Shift-Cmd-Option-F
Replace all
-
-

Searching is enabled by - including addon/search/search.js - and addon/search/searchcursor.js. - For good-looking input dialogs, you also want to include - addon/dialog/dialog.js - and addon/dialog/dialog.css.

- - diff --git a/gulliver/js/codemirror/demo/spanaffectswrapping_shim.html b/gulliver/js/codemirror/demo/spanaffectswrapping_shim.html deleted file mode 100644 index 733db067f..000000000 --- a/gulliver/js/codemirror/demo/spanaffectswrapping_shim.html +++ /dev/null @@ -1,73 +0,0 @@ - - - - - CodeMirror: Automatically derive odd wrapping behavior for your browser - - - -

CodeMirror: odd wrapping shim

- -

This is a hack to automatically derive - a spanAffectsWrapping regexp for a browser. See the - comments above that variable - in lib/codemirror.js - for some more details.

- -
-

-
-    
-  
-
diff --git a/gulliver/js/codemirror/demo/theme.html b/gulliver/js/codemirror/demo/theme.html
deleted file mode 100644
index 046d1fc3e..000000000
--- a/gulliver/js/codemirror/demo/theme.html
+++ /dev/null
@@ -1,87 +0,0 @@
-
-
-  
-    
-    CodeMirror: Theme Demo
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-    
-
-    
-  
-  
-    

CodeMirror: Theme demo

- -
- -

Select a theme: -

- - - - diff --git a/gulliver/js/codemirror/demo/variableheight.html b/gulliver/js/codemirror/demo/variableheight.html deleted file mode 100644 index b00f7e454..000000000 --- a/gulliver/js/codemirror/demo/variableheight.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - CodeMirror: Variable Height Demo - - - - - - - - - -

CodeMirror: Variable Height Demo

- -
- - - diff --git a/gulliver/js/codemirror/demo/vim.html b/gulliver/js/codemirror/demo/vim.html deleted file mode 100644 index c5835d40d..000000000 --- a/gulliver/js/codemirror/demo/vim.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - CodeMirror: Vim bindings demo - - - - - - - - - - - - -

CodeMirror: Vim bindings demo

- -
- -
- -

The vim keybindings are enabled by -including keymap/vim.js and setting -the keyMap option to "vim". Because -CodeMirror's internal API is quite different from Vim, they are only -a loose approximation of actual vim bindings, though.

- - - - - diff --git a/gulliver/js/codemirror/demo/visibletabs.html b/gulliver/js/codemirror/demo/visibletabs.html deleted file mode 100644 index 109d1a6b0..000000000 --- a/gulliver/js/codemirror/demo/visibletabs.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - CodeMirror: Visible tabs demo - - - - - - - - -

CodeMirror: Visible tabs demo

- -
- -

Tabs inside the editor are spans with the -class cm-tab, and can be styled.

- - - - - diff --git a/gulliver/js/codemirror/demo/widget.html b/gulliver/js/codemirror/demo/widget.html deleted file mode 100644 index a3b27a9e4..000000000 --- a/gulliver/js/codemirror/demo/widget.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - CodeMirror: Inline Widget Demo - - - - - - - - - -

CodeMirror: Inline Widget Demo

- -
- -

This demo runs JSHint over the code -in the editor (which is the script used on this page), and -inserts line widgets to -display the warnings that JSHint comes up with.

- - diff --git a/gulliver/js/codemirror/demo/xmlcomplete.html b/gulliver/js/codemirror/demo/xmlcomplete.html deleted file mode 100644 index 28a50638f..000000000 --- a/gulliver/js/codemirror/demo/xmlcomplete.html +++ /dev/null @@ -1,81 +0,0 @@ - - - - - CodeMirror: XML Autocomplete Demo - - - - - - - - - - - -

CodeMirror: XML Autocomplete demo

- -
- -

Type '<' or space inside tag or - press ctrl-space to activate autocompletion. See - the code (here - and here) to figure out how - it works.

- - - - diff --git a/gulliver/js/codemirror/doc/baboon.png b/gulliver/js/codemirror/doc/baboon.png deleted file mode 100644 index 55d97f70b..000000000 Binary files a/gulliver/js/codemirror/doc/baboon.png and /dev/null differ diff --git a/gulliver/js/codemirror/doc/baboon_vector.svg b/gulliver/js/codemirror/doc/baboon_vector.svg deleted file mode 100644 index dc1667af9..000000000 --- a/gulliver/js/codemirror/doc/baboon_vector.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - -image/svg+xml \ No newline at end of file diff --git a/gulliver/js/codemirror/doc/compress.html b/gulliver/js/codemirror/doc/compress.html deleted file mode 100644 index 06ed4970a..000000000 --- a/gulliver/js/codemirror/doc/compress.html +++ /dev/null @@ -1,197 +0,0 @@ - - - - - CodeMirror: Compression Helper - - - - - -

{ } CodeMirror

- -
- -
-/* Script compression
-   helper */
-
-
- -

To optimize loading CodeMirror, especially when including a - bunch of different modes, it is recommended that you combine and - minify (and preferably also gzip) the scripts. This page makes - those first two steps very easy. Simply select the version and - scripts you need in the form below, and - click Compress to download the minified script - file.

- -
- -

Version:

- -

- -

- with UglifyJS -

- -

Custom code to add to the compressed file:

-
- - - - - diff --git a/gulliver/js/codemirror/doc/docs.css b/gulliver/js/codemirror/doc/docs.css deleted file mode 100644 index 170cd4124..000000000 --- a/gulliver/js/codemirror/doc/docs.css +++ /dev/null @@ -1,167 +0,0 @@ -body { - font-family: Droid Sans, Arial, sans-serif; - line-height: 1.5; - max-width: 64.3em; - margin: 3em auto; - padding: 0 1em; -} - -h1 { - letter-spacing: -3px; - font-size: 3.23em; - font-weight: bold; - margin: 0; -} - -h2 { - font-size: 1.23em; - font-weight: bold; - margin: .5em 0; - letter-spacing: -1px; -} - -h3 { - font-size: 1.1em; - font-weight: bold; - margin: .4em 0; -} - -pre { - background-color: #eee; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: 1em; -} - -pre.code { - margin: 0 1em; -} - -.grey { - background-color: #eee; - border-radius: 6px; - margin-bottom: 1.65em; - margin-top: 0.825em; - padding: 0.825em 1.65em; - position: relative; -} - -img.logo { - position: absolute; - right: -1em; - bottom: 4px; - max-width: 23.6875em; /* Scale image down with text to prevent clipping */ -} - -.grey > pre { - background:none; - border-radius:0; - padding:0; - margin:0; - font-size:2.2em; - line-height:1.2em; -} - -a:link, a:visited, .quasilink { - color: #df0019; - cursor: pointer; - text-decoration: none; -} - -a:hover, .quasilink:hover { - color: #800004; -} - -h1 a:link, h1 a:visited, h1 a:hover { - color: black; -} - -ul { - margin: 0; - padding-left: 1.2em; -} - -a.download { - color: white; - background-color: #df0019; - width: 100%; - display: block; - text-align: center; - font-size: 1.23em; - font-weight: bold; - text-decoration: none; - -moz-border-radius: 6px; - -webkit-border-radius: 6px; - border-radius: 6px; - padding: .5em 0; - margin-bottom: 1em; -} - -a.download:hover { - background-color: #bb0010; -} - -.rel { - margin-bottom: 0; -} - -.rel-note { - color: #777; - font-size: .9em; - margin-top: .1em; -} - -.logo-braces { - color: #df0019; - position: relative; - top: -4px; -} - -.blk { - float: left; -} - -.left { - margin-right: 20.68em; - max-width: 37em; - padding-right: 6.53em; - padding-bottom: 1em; -} - -.left1 { - width: 15.24em; - padding-right: 6.45em; -} - -.left2 { - max-width: 15.24em; -} - -.right { - width: 20.68em; - margin-left: -20.68em; -} - -.leftbig { - width: 42.44em; - padding-right: 6.53em; -} - -.rightsmall { - width: 15.24em; -} - -.clear:after { - visibility: hidden; - display: block; - font-size: 0; - content: " "; - clear: both; - height: 0; -} -.clear { display: inline-block; } -/* start commented backslash hack \*/ -* html .clear { height: 1%; } -.clear { display: block; } -/* close commented backslash hack */ diff --git a/gulliver/js/codemirror/doc/internals.html b/gulliver/js/codemirror/doc/internals.html deleted file mode 100644 index 9139528f3..000000000 --- a/gulliver/js/codemirror/doc/internals.html +++ /dev/null @@ -1,505 +0,0 @@ - - - - - CodeMirror: Internals - - - - - - -

{ } CodeMirror

- -
- -
-/* (Re-) Implementing A Syntax-
-   Highlighting Editor in JavaScript */
-
-
- -
- -

- Topic: JavaScript, code editor implementation
- Author: Marijn Haverbeke
- Date: March 2nd 2011 (updated November 13th 2011) -

- -

Caution: this text was written briefly after -version 2 was initially written. It no longer (even including the -update at the bottom) fully represents the current implementation. I'm -leaving it here as a historic document. For more up-to-date -information, look at the entries -tagged cm-internals -on my blog.

- -

This is a followup to -my Brutal Odyssey to the -Dark Side of the DOM Tree story. That one describes the -mind-bending process of implementing (what would become) CodeMirror 1. -This one describes the internals of CodeMirror 2, a complete rewrite -and rethink of the old code base. I wanted to give this piece another -Hunter Thompson copycat subtitle, but somehow that would be out of -place—the process this time around was one of straightforward -engineering, requiring no serious mind-bending whatsoever.

- -

So, what is wrong with CodeMirror 1? I'd estimate, by mailing list -activity and general search-engine presence, that it has been -integrated into about a thousand systems by now. The most prominent -one, since a few weeks, -being Google -code's project hosting. It works, and it's being used widely. - -

Still, I did not start replacing it because I was bored. CodeMirror -1 was heavily reliant on designMode -or contentEditable (depending on the browser). Neither of -these are well specified (HTML5 tries -to specify -their basics), and, more importantly, they tend to be one of the more -obscure and buggy areas of browser functionality—CodeMirror, by using -this functionality in a non-typical way, was constantly running up -against browser bugs. WebKit wouldn't show an empty line at the end of -the document, and in some releases would suddenly get unbearably slow. -Firefox would show the cursor in the wrong place. Internet Explorer -would insist on linkifying everything that looked like a URL or email -address, a behaviour that can't be turned off. Some bugs I managed to -work around (which was often a frustrating, painful process), others, -such as the Firefox cursor placement, I gave up on, and had to tell -user after user that they were known problems, but not something I -could help.

- -

Also, there is the fact that designMode (which seemed -to be less buggy than contentEditable in Webkit and -Firefox, and was thus used by CodeMirror 1 in those browsers) requires -a frame. Frames are another tricky area. It takes some effort to -prevent getting tripped up by domain restrictions, they don't -initialize synchronously, behave strangely in response to the back -button, and, on several browsers, can't be moved around the DOM -without having them re-initialize. They did provide a very nice way to -namespace the library, though—CodeMirror 1 could freely pollute the -namespace inside the frame.

- -

Finally, working with an editable document means working with -selection in arbitrary DOM structures. Internet Explorer (8 and -before) has an utterly different (and awkward) selection API than all -of the other browsers, and even among the different implementations of -document.selection, details about how exactly a selection -is represented vary quite a bit. Add to that the fact that Opera's -selection support tended to be very buggy until recently, and you can -imagine why CodeMirror 1 contains 700 lines of selection-handling -code.

- -

And that brings us to the main issue with the CodeMirror 1 -code base: The proportion of browser-bug-workarounds to real -application code was getting dangerously high. By building on top of a -few dodgy features, I put the system in a vulnerable position—any -incompatibility and bugginess in these features, I had to paper over -with my own code. Not only did I have to do some serious stunt-work to -get it to work on older browsers (as detailed in the -previous story), things -also kept breaking in newly released versions, requiring me to come up -with new scary hacks in order to keep up. This was starting -to lose its appeal.

- -

General Approach

- -

What CodeMirror 2 does is try to sidestep most of the hairy hacks -that came up in version 1. I owe a lot to the -ACE editor for inspiration on how to -approach this.

- -

I absolutely did not want to be completely reliant on key events to -generate my input. Every JavaScript programmer knows that key event -information is horrible and incomplete. Some people (most awesomely -Mihai Bazon with Ymacs) have been able -to build more or less functioning editors by directly reading key -events, but it takes a lot of work (the kind of never-ending, fragile -work I described earlier), and will never be able to properly support -things like multi-keystoke international character -input. [see below for caveat]

- -

So what I do is focus a hidden textarea, and let the browser -believe that the user is typing into that. What we show to the user is -a DOM structure we built to represent his document. If this is updated -quickly enough, and shows some kind of believable cursor, it feels -like a real text-input control.

- -

Another big win is that this DOM representation does not have to -span the whole document. Some CodeMirror 1 users insisted that they -needed to put a 30 thousand line XML document into CodeMirror. Putting -all that into the DOM takes a while, especially since, for some -reason, an editable DOM tree is slower than a normal one on most -browsers. If we have full control over what we show, we must only -ensure that the visible part of the document has been added, and can -do the rest only when needed. (Fortunately, the onscroll -event works almost the same on all browsers, and lends itself well to -displaying things only as they are scrolled into view.)

- -

Input

- -

ACE uses its hidden textarea only as a text input shim, and does -all cursor movement and things like text deletion itself by directly -handling key events. CodeMirror's way is to let the browser do its -thing as much as possible, and not, for example, define its own set of -key bindings. One way to do this would have been to have the whole -document inside the hidden textarea, and after each key event update -the display DOM to reflect what's in that textarea.

- -

That'd be simple, but it is not realistic. For even medium-sized -document the editor would be constantly munging huge strings, and get -terribly slow. What CodeMirror 2 does is put the current selection, -along with an extra line on the top and on the bottom, into the -textarea.

- -

This means that the arrow keys (and their ctrl-variations), home, -end, etcetera, do not have to be handled specially. We just read the -cursor position in the textarea, and update our cursor to match it. -Also, copy and paste work pretty much for free, and people get their -native key bindings, without any special work on my part. For example, -I have emacs key bindings configured for Chrome and Firefox. There is -no way for a script to detect this. [no longer the case]

- -

Of course, since only a small part of the document sits in the -textarea, keys like page up and ctrl-end won't do the right thing. -CodeMirror is catching those events and handling them itself.

- -

Selection

- -

Getting and setting the selection range of a textarea in modern -browsers is trivial—you just use the selectionStart -and selectionEnd properties. On IE you have to do some -insane stuff with temporary ranges and compensating for the fact that -moving the selection by a 'character' will treat \r\n as a single -character, but even there it is possible to build functions that -reliably set and get the selection range.

- -

But consider this typical case: When I'm somewhere in my document, -press shift, and press the up arrow, something gets selected. Then, if -I, still holding shift, press the up arrow again, the top of my -selection is adjusted. The selection remembers where its head -and its anchor are, and moves the head when we shift-move. -This is a generally accepted property of selections, and done right by -every editing component built in the past twenty years.

- -

But not something that the browser selection APIs expose.

- -

Great. So when someone creates an 'upside-down' selection, the next -time CodeMirror has to update the textarea, it'll re-create the -selection as an 'upside-up' selection, with the anchor at the top, and -the next cursor motion will behave in an unexpected way—our second -up-arrow press in the example above will not do anything, since it is -interpreted in exactly the same way as the first.

- -

No problem. We'll just, ehm, detect that the selection is -upside-down (you can tell by the way it was created), and then, when -an upside-down selection is present, and a cursor-moving key is -pressed in combination with shift, we quickly collapse the selection -in the textarea to its start, allow the key to take effect, and then -combine its new head with its old anchor to get the real -selection.

- -

In short, scary hacks could not be avoided entirely in CodeMirror -2.

- -

And, the observant reader might ask, how do you even know that a -key combo is a cursor-moving combo, if you claim you support any -native key bindings? Well, we don't, but we can learn. The editor -keeps a set known cursor-movement combos (initialized to the -predictable defaults), and updates this set when it observes that -pressing a certain key had (only) the effect of moving the cursor. -This, of course, doesn't work if the first time the key is used was -for extending an inverted selection, but it works most of the -time.

- -

Intelligent Updating

- -

One thing that always comes up when you have a complicated internal -state that's reflected in some user-visible external representation -(in this case, the displayed code and the textarea's content) is -keeping the two in sync. The naive way is to just update the display -every time you change your state, but this is not only error prone -(you'll forget), it also easily leads to duplicate work on big, -composite operations. Then you start passing around flags indicating -whether the display should be updated in an attempt to be efficient -again and, well, at that point you might as well give up completely.

- -

I did go down that road, but then switched to a much simpler model: -simply keep track of all the things that have been changed during an -action, and then, only at the end, use this information to update the -user-visible display.

- -

CodeMirror uses a concept of operations, which start by -calling a specific set-up function that clears the state and end by -calling another function that reads this state and does the required -updating. Most event handlers, and all the user-visible methods that -change state are wrapped like this. There's a method -called operation that accepts a function, and returns -another function that wraps the given function as an operation.

- -

It's trivial to extend this (as CodeMirror does) to detect nesting, -and, when an operation is started inside an operation, simply -increment the nesting count, and only do the updating when this count -reaches zero again.

- -

If we have a set of changed ranges and know the currently shown -range, we can (with some awkward code to deal with the fact that -changes can add and remove lines, so we're dealing with a changing -coordinate system) construct a map of the ranges that were left -intact. We can then compare this map with the part of the document -that's currently visible (based on scroll offset and editor height) to -determine whether something needs to be updated.

- -

CodeMirror uses two update algorithms—a full refresh, where it just -discards the whole part of the DOM that contains the edited text and -rebuilds it, and a patch algorithm, where it uses the information -about changed and intact ranges to update only the out-of-date parts -of the DOM. When more than 30 percent (which is the current heuristic, -might change) of the lines need to be updated, the full refresh is -chosen (since it's faster to do than painstakingly finding and -updating all the changed lines), in the other case it does the -patching (so that, if you scroll a line or select another character, -the whole screen doesn't have to be -re-rendered). [the full-refresh -algorithm was dropped, it wasn't really faster than the patching -one]

- -

All updating uses innerHTML rather than direct DOM -manipulation, since that still seems to be by far the fastest way to -build documents. There's a per-line function that combines the -highlighting, marking, and -selection info for that line into a snippet of HTML. The patch updater -uses this to reset individual lines, the refresh updater builds an -HTML chunk for the whole visible document at once, and then uses a -single innerHTML update to do the refresh.

- -

Parsers can be Simple

- -

When I wrote CodeMirror 1, I -thought interruptable -parsers were a hugely scary and complicated thing, and I used a -bunch of heavyweight abstractions to keep this supposed complexity -under control: parsers -were iterators -that consumed input from another iterator, and used funny -closure-resetting tricks to copy and resume themselves.

- -

This made for a rather nice system, in that parsers formed strictly -separate modules, and could be composed in predictable ways. -Unfortunately, it was quite slow (stacking three or four iterators on -top of each other), and extremely intimidating to people not used to a -functional programming style.

- -

With a few small changes, however, we can keep all those -advantages, but simplify the API and make the whole thing less -indirect and inefficient. CodeMirror -2's mode API uses explicit state -objects, and makes the parser/tokenizer a function that simply takes a -state and a character stream abstraction, advances the stream one -token, and returns the way the token should be styled. This state may -be copied, optionally in a mode-defined way, in order to be able to -continue a parse at a given point. Even someone who's never touched a -lambda in his life can understand this approach. Additionally, far -fewer objects are allocated in the course of parsing now.

- -

The biggest speedup comes from the fact that the parsing no longer -has to touch the DOM though. In CodeMirror 1, on an older browser, you -could see the parser work its way through the document, -managing some twenty lines in each 50-millisecond time slice it got. It -was reading its input from the DOM, and updating the DOM as it went -along, which any experienced JavaScript programmer will immediately -spot as a recipe for slowness. In CodeMirror 2, the parser usually -finishes the whole document in a single 100-millisecond time slice—it -manages some 1500 lines during that time on Chrome. All it has to do -is munge strings, so there is no real reason for it to be slow -anymore.

- -

What Gives?

- -

Given all this, what can you expect from CodeMirror 2?

- -
    - -
  • Small. the base library is -some 45k when minified -now, 17k when gzipped. It's smaller than -its own logo.
  • - -
  • Lightweight. CodeMirror 2 initializes very -quickly, and does almost no work when it is not focused. This means -you can treat it almost like a textarea, have multiple instances on a -page without trouble.
  • - -
  • Huge document support. Since highlighting is -really fast, and no DOM structure is being built for non-visible -content, you don't have to worry about locking up your browser when a -user enters a megabyte-sized document.
  • - -
  • Extended API. Some things kept coming up in the -mailing list, such as marking pieces of text or lines, which were -extremely hard to do with CodeMirror 1. The new version has proper -support for these built in.
  • - -
  • Tab support. Tabs inside editable documents were, -for some reason, a no-go. At least six different people announced they -were going to add tab support to CodeMirror 1, none survived (I mean, -none delivered a working version). CodeMirror 2 no longer removes tabs -from your document.
  • - -
  • Sane styling. iframe nodes aren't -really known for respecting document flow. Now that an editor instance -is a plain div element, it is much easier to size it to -fit the surrounding elements. You don't even have to make it scroll if -you do not want to.
  • - -
- -

On the downside, a CodeMirror 2 instance is not a native -editable component. Though it does its best to emulate such a -component as much as possible, there is functionality that browsers -just do not allow us to hook into. Doing select-all from the context -menu, for example, is not currently detected by CodeMirror.

- -

[Updates from November 13th 2011] Recently, I've made -some changes to the codebase that cause some of the text above to no -longer be current. I've left the text intact, but added markers at the -passages that are now inaccurate. The new situation is described -below.

- -

Content Representation

- -

The original implementation of CodeMirror 2 represented the -document as a flat array of line objects. This worked well—splicing -arrays will require the part of the array after the splice to be -moved, but this is basically just a simple memmove of a -bunch of pointers, so it is cheap even for huge documents.

- -

However, I recently added line wrapping and code folding (line -collapsing, basically). Once lines start taking up a non-constant -amount of vertical space, looking up a line by vertical position -(which is needed when someone clicks the document, and to determine -the visible part of the document during scrolling) can only be done -with a linear scan through the whole array, summing up line heights as -you go. Seeing how I've been going out of my way to make big documents -fast, this is not acceptable.

- -

The new representation is based on a B-tree. The leaves of the tree -contain arrays of line objects, with a fixed minimum and maximum size, -and the non-leaf nodes simply hold arrays of child nodes. Each node -stores both the amount of lines that live below them and the vertical -space taken up by these lines. This allows the tree to be indexed both -by line number and by vertical position, and all access has -logarithmic complexity in relation to the document size.

- -

I gave line objects and tree nodes parent pointers, to the node -above them. When a line has to update its height, it can simply walk -these pointers to the top of the tree, adding or subtracting the -difference in height from each node it encounters. The parent pointers -also make it cheaper (in complexity terms, the difference is probably -tiny in normal-sized documents) to find the current line number when -given a line object. In the old approach, the whole document array had -to be searched. Now, we can just walk up the tree and count the sizes -of the nodes coming before us at each level.

- -

I chose B-trees, not regular binary trees, mostly because they -allow for very fast bulk insertions and deletions. When there is a big -change to a document, it typically involves adding, deleting, or -replacing a chunk of subsequent lines. In a regular balanced tree, all -these inserts or deletes would have to be done separately, which could -be really expensive. In a B-tree, to insert a chunk, you just walk -down the tree once to find where it should go, insert them all in one -shot, and then break up the node if needed. This breaking up might -involve breaking up nodes further up, but only requires a single pass -back up the tree. For deletion, I'm somewhat lax in keeping things -balanced—I just collapse nodes into a leaf when their child count goes -below a given number. This means that there are some weird editing -patterns that may result in a seriously unbalanced tree, but even such -an unbalanced tree will perform well, unless you spend a day making -strangely repeating edits to a really big document.

- -

Keymaps

- -

Above, I claimed that directly catching key -events for things like cursor movement is impractical because it -requires some browser-specific kludges. I then proceeded to explain -some awful hacks that were needed to make it -possible for the selection changes to be detected through the -textarea. In fact, the second hack is about as bad as the first.

- -

On top of that, in the presence of user-configurable tab sizes and -collapsed and wrapped lines, lining up cursor movement in the textarea -with what's visible on the screen becomes a nightmare. Thus, I've -decided to move to a model where the textarea's selection is no longer -depended on.

- -

So I moved to a model where all cursor movement is handled by my -own code. This adds support for a goal column, proper interaction of -cursor movement with collapsed lines, and makes it possible for -vertical movement to move through wrapped lines properly, instead of -just treating them like non-wrapped lines.

- -

The key event handlers now translate the key event into a string, -something like Ctrl-Home or Shift-Cmd-R, and -use that string to look up an action to perform. To make keybinding -customizable, this lookup goes through -a table, using a scheme that -allows such tables to be chained together (for example, the default -Mac bindings fall through to a table named 'emacsy', which defines -basic Emacs-style bindings like Ctrl-F, and which is also -used by the custom Emacs bindings).

- -

A new -option extraKeys -allows ad-hoc keybindings to be defined in a much nicer way than what -was possible with the -old onKeyEvent -callback. You simply provide an object mapping key identifiers to -functions, instead of painstakingly looking at raw key events.

- -

Built-in commands map to strings, rather than functions, for -example "goLineUp" is the default action bound to the up -arrow key. This allows new keymaps to refer to them without -duplicating any code. New commands can be defined by assigning to -the CodeMirror.commands object, which maps such commands -to functions.

- -

The hidden textarea now only holds the current selection, with no -extra characters around it. This has a nice advantage: polling for -input becomes much, much faster. If there's a big selection, this text -does not have to be read from the textarea every time—when we poll, -just noticing that something is still selected is enough to tell us -that no new text was typed.

- -

The reason that cheap polling is important is that many browsers do -not fire useful events on IME (input method engine) input, which is -the thing where people inputting a language like Japanese or Chinese -use multiple keystrokes to create a character or sequence of -characters. Most modern browsers fire input when the -composing is finished, but many don't fire anything when the character -is updated during composition. So we poll, whenever the -editor is focused, to provide immediate updates of the display.

- -
- -
 
- - diff --git a/gulliver/js/codemirror/doc/manual.html b/gulliver/js/codemirror/doc/manual.html deleted file mode 100644 index f1a157cf1..000000000 --- a/gulliver/js/codemirror/doc/manual.html +++ /dev/null @@ -1,1841 +0,0 @@ - - - - - CodeMirror: User Manual - - - - - - - - - - - - - - -

{ } CodeMirror

- -
- -
-/* User manual and
-   reference guide */
-
-
- -
- -

Overview

- -

CodeMirror is a code-editor component that can be embedded in - Web pages. The core library provides only the editor - component, no accompanying buttons, auto-completion, or other IDE - functionality. It does provide a rich API on top of which such - functionality can be straightforwardly implemented. See - the add-ons included in the distribution, - and - the CodeMirror - UI project, for reusable implementations of extra features.

- -

CodeMirror works with language-specific modes. Modes are - JavaScript programs that help color (and optionally indent) text - written in a given language. The distribution comes with a number - of modes (see the mode/ - directory), and it isn't hard to write new - ones for other languages.

- -

Basic Usage

- -

The easiest way to use CodeMirror is to simply load the script - and style sheet found under lib/ in the distribution, - plus a mode script from one of the mode/ directories. - (See the compression helper for an - easy way to combine scripts.) For example:

- -
<script src="lib/codemirror.js"></script>
-<link rel="stylesheet" href="../lib/codemirror.css">
-<script src="mode/javascript/javascript.js"></script>
- -

Having done this, an editor instance can be created like - this:

- -
var myCodeMirror = CodeMirror(document.body);
- -

The editor will be appended to the document body, will start - empty, and will use the mode that we loaded. To have more control - over the new editor, a configuration object can be passed - to CodeMirror as a second argument:

- -
var myCodeMirror = CodeMirror(document.body, {
-  value: "function myScript(){return 100;}\n",
-  mode:  "javascript"
-});
- -

This will initialize the editor with a piece of code already in - it, and explicitly tell it to use the JavaScript mode (which is - useful when multiple modes are loaded). - See below for a full discussion of the - configuration options that CodeMirror accepts.

- -

In cases where you don't want to append the editor to an - element, and need more control over the way it is inserted, the - first argument to the CodeMirror function can also - be a function that, when given a DOM element, inserts it into the - document somewhere. This could be used to, for example, replace a - textarea with a real editor:

- -
var myCodeMirror = CodeMirror(function(elt) {
-  myTextArea.parentNode.replaceChild(elt, myTextArea);
-}, {value: myTextArea.value});
- -

However, for this use case, which is a common way to use - CodeMirror, the library provides a much more powerful - shortcut:

- -
var myCodeMirror = CodeMirror.fromTextArea(myTextArea);
- -

This will, among other things, ensure that the textarea's value - is updated with the editor's contents when the form (if it is part - of a form) is submitted. See the API - reference for a full description of this method.

- -

Configuration

- -

Both the CodeMirror function and - its fromTextArea method take as second (optional) - argument an object containing configuration options. Any option - not supplied like this will be taken - from CodeMirror.defaults, an object containing the - default options. You can update this object to change the defaults - on your page.

- -

Options are not checked in any way, so setting bogus option - values is bound to lead to odd errors.

- -

These are the supported options:

- -
-
value (string or Doc)
-
The starting value of the editor. Can be a string, or - a document object.
- -
mode (string or object)
-
The mode to use. When not given, this will default to the - first mode that was loaded. It may be a string, which either - simply names the mode or is - a MIME type - associated with the mode. Alternatively, it may be an object - containing configuration options for the mode, with - a name property that names the mode (for - example {name: "javascript", json: true}). The demo - pages for each mode contain information about what configuration - parameters the mode supports. You can ask CodeMirror which modes - and MIME types have been defined by inspecting - the CodeMirror.modes - and CodeMirror.mimeModes objects. The first maps - mode names to their constructors, and the second maps MIME types - to mode specs.
- -
theme (string)
-
The theme to style the editor with. You must make sure the - CSS file defining the corresponding .cm-s-[name] - styles is loaded (see - the theme directory in the - distribution). The default is "default", for which - colors are included in codemirror.css. It is - possible to use multiple theming classes at once—for - example "foo bar" will assign both - the cm-s-foo and the cm-s-bar classes - to the editor.
- -
indentUnit (integer)
-
How many spaces a block (whatever that means in the edited - language) should be indented. The default is 2.
- -
smartIndent (boolean)
-
Whether to use the context-sensitive indentation that the - mode provides (or just indent the same as the line before). - Defaults to true.
- -
tabSize (integer)
-
The width of a tab character. Defaults to 4.
- -
indentWithTabs (boolean)
-
Whether, when indenting, the first N*tabSize - spaces should be replaced by N tabs. Default is false.
- -
electricChars (boolean)
-
Configures whether the editor should re-indent the current - line when a character is typed that might change its proper - indentation (only works if the mode supports indentation). - Default is true.
- -
rtlMoveVisually (boolean)
-
Determines whether horizontal cursor movement through - right-to-left (Arabic, Hebrew) text is visual (pressing the left - arrow moves the cursor left) or logical (pressing the left arrow - moves to the next lower index in the string, which is visually - right in right-to-left text). The default is false - on Windows, and true on other platforms.
- -
keyMap (string)
-
Configures the keymap to use. The default - is "default", which is the only keymap defined - in codemirror.js itself. Extra keymaps are found in - the keymap directory. See - the section on keymaps for more - information.
- -
extraKeys (object)
-
Can be used to specify extra keybindings for the editor, - alongside the ones defined - by keyMap. Should be - either null, or a valid keymap value.
- -
lineWrapping (boolean)
-
Whether CodeMirror should scroll or wrap for long lines. - Defaults to false (scroll).
- -
lineNumbers (boolean)
-
Whether to show line numbers to the left of the editor.
- -
firstLineNumber (integer)
-
At which number to start counting lines. Default is 1.
- -
lineNumberFormatter (function)
-
A function used to format line numbers. The function is - passed the line number, and should return a string that will be - shown in the gutter.
- -
gutters (array)
-
Can be used to add extra gutters (beyond or instead of the - line number gutter). Should be an array of CSS class names, each - of which defines a width (and optionally a - background), and which will be used to draw the background of - the gutters. May include - the CodeMirror-linenumbers class, in order to - explicitly set the position of the line number gutter (it will - default to be to the right of all other gutters). These class - names are the keys passed - to setGutterMarker.
- -
fixedGutter (boolean)
-
Determines whether the gutter scrolls along with the content - horizontally (false) or whether it stays fixed during horizontal - scrolling (true, the default).
- -
readOnly (boolean)
-
This disables editing of the editor content by the user. If - the special value "nocursor" is given (instead of - simply true), focusing of the editor is also - disallowed.
- -
showCursorWhenSelecting (boolean)
-
Whether the cursor should be drawn when a selection is - active. Defaults to false.
- -
undoDepth (integer)
-
The maximum number of undo levels that the editor stores. - Defaults to 40.
- -
tabindex (integer)
-
The tab - index to assign to the editor. If not given, no tab index - will be assigned.
- -
autofocus (boolean)
-
Can be used to make CodeMirror focus itself on - initialization. Defaults to off. - When fromTextArea is - used, and no explicit value is given for this option, it will be - set to true when either the source textarea is focused, or it - has an autofocus attribute and no other element is - focused.
-
- -

Below this a few more specialized, low-level options are - listed. These are only useful in very specific situations, you - might want to skip them the first time you read this manual.

- -
-
dragDrop (boolean)
-
Controls whether drag-and-drop is enabled. On by default.
- -
onDragEvent (function)
-
When given, this will be called when the editor is handling - a dragenter, dragover, - or drop event. It will be passed the editor instance - and the event object as arguments. The callback can choose to - handle the event itself, in which case it should - return true to indicate that CodeMirror should not - do anything further.
- -
onKeyEvent (function)
-
This provides a rather low-level hook into CodeMirror's key - handling. If provided, this function will be called on - every keydown, keyup, - and keypress event that CodeMirror captures. It - will be passed two arguments, the editor instance and the key - event. This key event is pretty much the raw key event, except - that a stop() method is always added to it. You - could feed it to, for example, jQuery.Event to - further normalize it.
This function can inspect the key - event, and handle it if it wants to. It may return true to tell - CodeMirror to ignore the event. Be wary that, on some browsers, - stopping a keydown does not stop - the keypress from firing, whereas on others it - does. If you respond to an event, you should probably inspect - its type property and only do something when it - is keydown (or keypress for actions - that need character data).
- -
cursorBlinkRate (number)
-
Half-period in milliseconds used for cursor blinking. The default blink - rate is 530ms.
- -
cursorHeight (number)
-
Determines the height of the cursor. Default is 1, meaning - it spans the whole height of the line. For some fonts (and by - some tastes) a smaller height (for example 0.85), - which causes the cursor to not reach all the way to the bottom - of the line, looks better
- -
workTime, workDelay (number)
-
Highlighting is done by a pseudo background-thread that will - work for workTime milliseconds, and then use - timeout to sleep for workDelay milliseconds. The - defaults are 200 and 300, you can change these options to make - the highlighting more or less aggressive.
- -
pollInterval (number)
-
Indicates how quickly CodeMirror should poll its input - textarea for changes (when focused). Most input is captured by - events, but some things, like IME input on some browsers, don't - generate events that allow CodeMirror to properly detect it. - Thus, it polls. Default is 100 milliseconds.
- -
flattenSpans (boolean)
-
By default, CodeMirror will combine adjacent tokens into a - single span if they have the same class. This will result in a - simpler DOM tree, and thus perform better. With some kinds of - styling (such as rounded corners), this will change the way the - document looks. You can set this option to false to disable this - behavior.
- -
viewportMargin (integer)
-
Specifies the amount of lines that are rendered above and - below the part of the document that's currently scrolled into - view. This affects the amount of updates needed when scrolling, - and the amount of work that such an update does. You should - usually leave it at its default, 10. Can be set - to Infinity to make sure the whole document is - always rendered, and thus the browser's text search works on it. - This will have bad effects on performance of big - documents.
-
- -

Events

- -

A CodeMirror instance emits a number of events, which allow - client code to react to various situations. These are registered - with the on method (and - removed with the off - method). These are the events that fire on the instance object. - The name of the event is followed by the arguments that will be - passed to the handler. The instance argument always - refers to the editor instance.

- -
-
"change" (instance, changeObj)
-
Fires every time the content of the editor is changed. - The changeObj is a {from, to, text, removed, - next} object containing information about the changes - that occurred as second argument. from - and to are the positions (in the pre-change - coordinate system) where the change started and ended (for - example, it might be {ch:0, line:18} if the - position is at the beginning of line #19). text is - an array of strings representing the text that replaced the - changed range (split by line). removed is the text - that used to be between from and to, - which is overwritten by this change. If multiple changes - happened during a single operation, the object will have - a next property pointing to another change object - (which may point to another, etc).
- -
"beforeChange" (instance, change)
-
This event is fired before a change is applied, and its - handler may choose to modify or cancel the change. - The change object - has from, to, and text - properties, as with - the "change" event, but - never a next property, since this is fired for each - individual change, and not batched per operation. It also - has update(from, to, text) - and cancel() methods, which may be used to modify - or cancel the change. All three arguments to update - are optional, and can be left off to leave the existing value - for that field intact. Note: you may not do - anything from a "beforeChange" handler that would - cause changes to the document or its visualization. Doing so - will, since this handler is called directly from the bowels of - the CodeMirror implementation, probably cause the editor to - become corrupted.
- -
"cursorActivity" (instance)
-
Will be fired when the cursor or selection moves, or any - change is made to the editor content.
- -
"beforeSelectionChange" (instance, selection)
-
This event is fired before the selection is moved. Its - handler may modify the resulting selection head and anchor. - The selection parameter is an object - with head and anchor properties - holding {line, ch} objects, which the handler can - read and update. Handlers for this event have the same - restriction - as "beforeChange" - handlers — they should not do anything to directly update the - state of the editor.
- -
"viewportChange" (instance, from, to)
-
Fires whenever the view port of - the editor changes (due to scrolling, editing, or any other - factor). The from and to arguments - give the new start and end of the viewport.
- -
"gutterClick" (instance, line, gutter, clickEvent)
-
Fires when the editor gutter (the line-number area) is - clicked. Will pass the editor instance as first argument, the - (zero-based) number of the line that was clicked as second - argument, the CSS class of the gutter that was clicked as third - argument, and the raw mousedown event object as - fourth argument.
- -
"focus", "blur" (instance)
-
These fire whenever the editor is focused or unfocused.
- -
"scroll" (instance)
-
Fires when the editor is scrolled.
- -
"update" (instance)
-
Will be fired whenever CodeMirror updates its DOM display.
- -
"renderLine" (instance, line, element)
-
Fired whenever a line is (re-)rendered to the DOM. Fired - right after the DOM element is built, before it is - added to the document. The handler may mess with the style of - the resulting element, or add event handlers, but - should not try to change the state of the editor.
-
- -

It is also possible to register events on - other objects. Use CodeMirror.on(handle, "eventName", - func) to register handlers on objects that don't have their - own on method. Document objects (instances - of CodeMirror.Doc) emit the - following events:

- -
-
"change" (doc, changeObj)
-
Fired whenever a change occurs to the - document. changeObj has a similar type as the - object passed to the - editor's "change" - event, but it never has a next property, because - document change events are not batched (whereas editor change - events are).
- -
"beforeChange" (doc, change)
-
See the description of the - same event on editor instances.
- -
"cursorActivity" (doc)
-
Fired whenever the cursor or selection in this document - changes.
- -
"beforeSelectionChange" (doc, selection)
-
Equivalent to - the event by the same - name as fired on editor instances.
-
- -

Line handles (as returned by, for - example, getLineHandle) - support these events:

- -
-
"delete" ()
-
Will be fired when the line object is deleted. A line object - is associated with the start of the line. Mostly useful - when you need to find out when your gutter - markers on a given line are removed.
-
"change" (line, changeObj)
-
Fires when the line's text content is changed in any way - (but the line is not deleted outright). The change - object is similar to the one passed - to change event on the editor - object.
-
- -

Marked range handles, as returned - by markText - and setBookmark, emit the - following events:

- -
-
"beforeCursorEnter" ()
-
Fired when the cursor enters the marked range. From this - event handler, the editor state may be inspected - but not modified, with the exception that the range on - which the event fires may be cleared.
-
"clear" ()
-
Fired when the range is cleared, either through cursor - movement in combination - with clearOnEnter - or through a call to its clear() method. Will only - be fired once per handle. Note that deleting the range through - text editing does not fire this event, because an undo - action might bring the range back into existence.
-
"hide" ()
-
Fired when the last part of the marker is removed from the - document by editing operations.
-
"unhide" ()
-
Fired when, after the marker was removed by editing, a undo - operation brought the marker back.
-
- -

Line widgets, returned - by addLineWidget, fire - these events:

- -
-
"redraw" ()
-
Fired whenever the editor re-adds the widget to the DOM. - This will happen once right after the widget is added (if it is - scrolled into view), and then again whenever it is scrolled out - of view and back in again, or when changes to the editor options - or the line the widget is on require the widget to be - redrawn.
-
- -

Keymaps

- -

Keymaps are ways to associate keys with functionality. A keymap - is an object mapping strings that identify the keys to functions - that implement their functionality.

- -

Keys are identified either by name or by character. - The CodeMirror.keyNames object defines names for - common keys and associates them with their key codes. Examples of - names defined here are Enter, F5, - and Q. These can be prefixed - with Shift-, Cmd-, Ctrl-, - and Alt- (in that order!) to specify a modifier. So - for example, Shift-Ctrl-Space would be a valid key - identifier.

- -

Common example: map the Tab key to insert spaces instead of a tab - character.

- -
-{
-  Tab: function(cm) {
-    var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
-    cm.replaceSelection(spaces, "end", "+input");
-  }
-}
- -

Alternatively, a character can be specified directly by - surrounding it in single quotes, for example '$' - or 'q'. Due to limitations in the way browsers fire - key events, these may not be prefixed with modifiers.

- -

The CodeMirror.keyMap object associates keymaps - with names. User code and keymap definitions can assign extra - properties to this object. Anywhere where a keymap is expected, a - string can be given, which will be looked up in this object. It - also contains the "default" keymap holding the - default bindings.

- -

The values of properties in keymaps can be either functions of - a single argument (the CodeMirror instance), strings, or - false. Such strings refer to properties of the - CodeMirror.commands object, which defines a number of - common commands that are used by the default keybindings, and maps - them to functions. If the property is set to false, - CodeMirror leaves handling of the key up to the browser. A key - handler function may return CodeMirror.Pass to indicate - that it has decided not to handle the key, and other handlers (or - the default behavior) should be given a turn.

- -

Keys mapped to command names that start with the - characters "go" (which should be used for - cursor-movement actions) will be fired even when an - extra Shift modifier is present (i.e. "Up": - "goLineUp" matches both up and shift-up). This is used to - easily implement shift-selection.

- -

Keymaps can defer to each other by defining - a fallthrough property. This indicates that when a - key is not found in the map itself, one or more other maps should - be searched. It can hold either a single keymap or an array of - keymaps.

- -

When a keymap contains a nofallthrough property - set to true, keys matched against that map will be - ignored if they don't match any of the bindings in the map (no - further child maps will be tried, and the default effect of - inserting a character will not occur).

- -

Customized Styling

- -

Up to a certain extent, CodeMirror's look can be changed by - modifying style sheet files. The style sheets supplied by modes - simply provide the colors for that mode, and can be adapted in a - very straightforward way. To style the editor itself, it is - possible to alter or override the styles defined - in codemirror.css.

- -

Some care must be taken there, since a lot of the rules in this - file are necessary to have CodeMirror function properly. Adjusting - colors should be safe, of course, and with some care a lot of - other things can be changed as well. The CSS classes defined in - this file serve the following roles:

- -
-
CodeMirror
-
The outer element of the editor. This should be used for the - editor width, height, borders and positioning. Can also be used - to set styles that should hold for everything inside the editor - (such as font and font size), or to set a background.
- -
CodeMirror-scroll
-
Whether the editor scrolls (overflow: auto + - fixed height). By default, it does. Setting - the CodeMirror class to have height: - auto and giving this class overflow-x: auto; - overflow-y: hidden; will cause the editor - to resize to fit its - content.
- -
CodeMirror-focused
-
Whenever the editor is focused, the top element gets this - class. This is used to hide the cursor and give the selection a - different color when the editor is not focused.
- -
CodeMirror-gutters
-
This is the backdrop for all gutters. Use it to set the - default gutter background color, and optionally add a border on - the right of the gutters.
- -
CodeMirror-linenumbers
-
Use this for giving a background or width to the line number - gutter.
- -
CodeMirror-linenumber
-
Used to style the actual individual line numbers. These - won't be children of the CodeMirror-linenumbers - (plural) element, but rather will be absolutely positioned to - overlay it. Use this to set alignment and text properties for - the line numbers.
- -
CodeMirror-lines
-
The visible lines. This is where you specify vertical - padding for the editor content.
- -
CodeMirror-cursor
-
The cursor is a block element that is absolutely positioned. - You can make it look whichever way you want.
- -
CodeMirror-selected
-
The selection is represented by span elements - with this class.
- -
CodeMirror-matchingbracket, - CodeMirror-nonmatchingbracket
-
These are used to style matched (or unmatched) brackets.
-
- -

If your page's style sheets do funky things to - all div or pre elements (you probably - shouldn't do that), you'll have to define rules to cancel these - effects out again for elements under the CodeMirror - class.

- -

Themes are also simply CSS files, which define colors for - various syntactic elements. See the files in - the theme directory.

- -

Programming API

- -

A lot of CodeMirror features are only available through its - API. Thus, you need to write code (or - use add-ons) if you want to expose them to - your users.

- -

Whenever points in the document are represented, the API uses - objects with line and ch properties. - Both are zero-based. CodeMirror makes sure to 'clip' any positions - passed by client code so that they fit inside the document, so you - shouldn't worry too much about sanitizing your coordinates. If you - give ch a value of null, or don't - specify it, it will be replaced with the length of the specified - line.

- -

Methods prefixed with doc. can, unless otherwise - specified, be called both on CodeMirror (editor) - instances and CodeMirror.Doc instances. Methods - prefixed with cm. are only available - on CodeMirror instances.

- -

Content manipulation methods

- -
-
doc.getValue() → string
-
Get the current editor content. You can pass it an optional - argument to specify the string to be used to separate lines - (defaults to "\n").
-
doc.setValue(string)
-
Set the editor content.
- -
doc.getRange(from, to) → string
-
Get the text between the given points in the editor, which - should be {line, ch} objects. An optional third - argument can be given to indicate the line separator string to - use (defaults to "\n").
-
doc.replaceRange(string, from, to)
-
Replace the part of the document between from - and to with the given string. from - and to must be {line, ch} - objects. to can be left off to simply insert the - string at position from.
- -
doc.getLine(n) → string
-
Get the content of line n.
-
doc.setLine(n, text)
-
Set the content of line n.
-
doc.removeLine(n)
-
Remove the given line from the document.
- -
doc.lineCount() → number
-
Get the number of lines in the editor.
-
doc.firstLine() → number
-
doc.lastLine() → number
-
Get the first and last lines of the editor. This will - usually be zero and doc.lineCount() - 1 respectively, - but for linked sub-views, - or documents instantiated with a non-zero - first line, it might return other values.
- -
doc.getLineHandle(num) → lineHandle
-
Fetches the line handle for the given line number.
-
doc.getLineNumber(handle) → integer
-
Given a line handle, returns the current position of that - line (or null when it is no longer in the - document).
-
doc.eachLine(f) | doc.eachLine(start, end, f)
-
Iterate over the whole document, or if start - and end line numbers are given, the range - from start up to (not including) end, - and call f for each line, passing the line handle. - This is a faster way to visit a range of line handlers than - calling getLineHandle - for each of them. Note that line handles have - a text property containing the line's content (as a - string).
- -
doc.markClean()
-
Set the editor content as 'clean', a flag that it will - retain until it is edited, and which will be set again when such - an edit is undone again. Useful to track whether the content - needs to be saved.
-
doc.isClean() → boolean
-
Returns whether the document is currently clean (not - modified since initialization or the last call - to markClean).
-
- -

Cursor and selection methods

- -
-
doc.getSelection() → string
-
Get the currently selected code.
-
doc.replaceSelection(string)
-
Replace the selection with the given string.
- -
doc.getCursor(start) → object
-
start is a an optional string indicating which - end of the selection to return. It may - be "start", "end", "head" - (the side of the selection that moves when you press - shift+arrow), or "anchor" (the fixed side of the - selection). Omitting the argument is the same as - passing "head". A {line, ch} object - will be returned.
-
doc.somethingSelected() → boolean
-
Return true if any text is selected.
-
doc.setCursor(pos)
-
Set the cursor position. You can either pass a - single {line, ch} object, or the line and the - character as two separate parameters.
-
doc.setSelection(anchor, head)
-
Set the selection range. anchor - and head should be {line, ch} - objects. head defaults to anchor when - not given.
-
doc.extendSelection(pos, pos2)
-
Similar - to setSelection, but - will, if shift is held or - the extending flag is set, move the - head of the selection while leaving the anchor at its current - place. pos2 is optional, and can be passed to - ensure a region (for example a word or paragraph) will end up - selected (in addition to whatever lies between that region and - the current anchor).
-
doc.setExtending(bool)
-
Sets or clears the 'extending' flag, which acts similar to - the shift key, in that it will cause cursor movement and calls - to extendSelection - to leave the selection anchor in place.
- -
cm.hasFocus() → bool
-
Tells you whether the editor currently has focus.
- -
cm.findPosH(start, amount, unit, visually) → object
-
Used to find the target position for horizontal cursor - motion. start is a {line, ch} - object, amount an integer (may be negative), - and unit one of the - string "char", "column", - or "word". Will return a position that is produced - by moving amount times the distance specified - by unit. When visually is true, motion - in right-to-left text will be visual rather than logical. When - the motion was clipped by hitting the end or start of the - document, the returned value will have a hitSide - property set to true.
-
cm.findPosV(start, amount, unit) → object
-
Similar to findPosH, - but used for vertical motion. unit may - be "line" or "page". The other - arguments and the returned value have the same interpretation as - they have in findPosH.
-
- -

Configuration methods

- -
-
cm.setOption(option, value)
-
Change the configuration of the editor. option - should the name of an option, - and value should be a valid value for that - option.
-
cm.getOption(option) → value
-
Retrieves the current value of the given option for this - editor instance.
- -
cm.addKeyMap(map, bottom)
-
Attach an additional keymap to the - editor. This is mostly useful for add-ons that need to register - some key handlers without trampling on - the extraKeys - option. Maps added in this way have a higher precedence than - the extraKeys - and keyMap options, - and between them, the maps added earlier have a lower precedence - than those added later, unless the bottom argument - was passed, in which case they end up below other keymaps added - with this method.
-
cm.removeKeyMap(map)
-
Disable a keymap added - with addKeyMap. Either - pass in the keymap object itself, or a string, which will be - compared against the name property of the active - keymaps.
- -
cm.addOverlay(mode, options)
-
Enable a highlighting overlay. This is a stateless mini-mode - that can be used to add extra highlighting. For example, - the search add-on uses it to - highlight the term that's currently being - searched. mode can be a mode - spec or a mode object (an object with - a token method). - The options parameter is optional. If given, it - should be an object. Currently, only the opaque - option is recognized. This defaults to off, but can be given to - allow the overlay styling, when not null, to - override the styling of the base mode entirely, instead of the - two being applied together.
-
cm.removeOverlay(mode)
-
Pass this the exact argument passed for - the mode parameter - to addOverlay to remove - an overlay again.
- -
cm.on(type, func)
-
Register an event handler for the given event type (a - string) on the editor instance. There is also - a CodeMirror.on(object, type, func) version - that allows registering of events on any object.
-
cm.off(type, func)
-
Remove an event handler on the editor instance. An - equivalent CodeMirror.off(object, type, - func) also exists.
-
- -

Document management methods

- -

Each editor is associated with an instance - of CodeMirror.Doc, its document. A document - represents the editor content, plus a selection, an undo history, - and a mode. A document can only be - associated with a single editor at a time. You can create new - documents by calling the CodeMirror.Doc(text, mode, - firstLineNumber) constructor. The last two arguments are - optional and can be used to set a mode for the document and make - it start at a line number other than 0, respectively.

- -
-
cm.getDoc() → doc
-
Retrieve the currently active document from an editor.
-
doc.getEditor() → editor
-
Retrieve the editor associated with a document. May - return null.
- -
cm.swapDoc(doc) → doc
-
Attach a new document to the editor. Returns the old - document, which is now no longer associated with an editor.
- -
doc.copy(copyHistory) → doc
-
Create an identical copy of the given doc. - When copyHistory is true, the history will also be - copied. Can not be called directly on an editor.
- -
doc.linkedDoc(options) → doc
-
Create a new document that's linked to the target document. - Linked documents will stay in sync (changes to one are also - applied to the other) until unlinked. - These are the options that are supported: -
-
sharedHist (boolean)
-
When turned on, the linked copy will share an undo - history with the original. Thus, something done in one of - the two can be undone in the other, and vice versa.
-
from, to (integer)
-
Can be given to make the new document a subview of the - original. Subviews only show a given range of lines. Note - that line coordinates inside the subview will be consistent - with those of the parent, so that for example a subview - starting at line 10 will refer to its first line as line 10, - not 0.
-
mode (mode spec)
-
By default, the new document inherits the mode of the - parent. This option can be set to - a mode spec to give it a - different mode.
-
-
doc.unlinkDoc(doc)
-
Break the link between two documents. After calling this, - changes will no longer propagate between the documents, and, if - they had a shared history, the history will become - separate.
-
doc.iterLinkedDocs(function)
-
Will call the given function for all documents linked to the - target document. It will be passed two arguments, the linked document - and a boolean indicating whether that document shares history - with the target.
-
- -

History-related methods

- -
-
doc.undo()
-
Undo one edit (if any undo events are stored).
-
doc.redo()
-
Redo one undone edit.
- -
doc.historySize() → object
-
Returns an object with {undo, redo} properties, - both of which hold integers, indicating the amount of stored - undo and redo operations.
-
doc.clearHistory()
-
Clears the editor's undo history.
-
doc.getHistory() → object
-
Get a (JSON-serializeable) representation of the undo history.
-
doc.setHistory(object)
-
Replace the editor's undo history with the one provided, - which must be a value as returned - by getHistory. Note that - this will have entirely undefined results if the editor content - isn't also the same as it was when getHistory was - called.
-
- -

Text-marking methods

- -
-
doc.markText(from, to, options) → object
-
Can be used to mark a range of text with a specific CSS - class name. from and to should - be {line, ch} objects. The options - parameter is optional. When given, it should be an object that - may contain the following configuration options: -
-
className (string)
-
Assigns a CSS class to the marked stretch of text.
-
inclusiveLeft (boolean)
Determines whether - text inserted on the left of the marker will end up inside - or outside of it.
-
inclusiveRight (boolean)
Like inclusiveLeft, - but for the right side.
-
atomic (boolean)
-
Atomic ranges act as a single unit when cursor movement is - concerned—i.e. it is impossible to place the cursor inside of - them. In atomic ranges, inclusiveLeft - and inclusiveRight have a different meaning—they - will prevent the cursor from being placed respectively - directly before and directly after the range.
-
collapsed (boolean)
-
Collapsed ranges do not show up in the display. Setting a - range to be collapsed will automatically make it atomic.
-
clearOnEnter (boolean)
-
When enabled, will cause the mark to clear itself whenever - the cursor enters its range. This is mostly useful for - text-replacement widgets that need to 'snap open' when the - user tries to edit them. A - the "clear" event - fired on the range handle can be used to be notified when this - happens.
-
replacedWith (dom node)
-
Use a given node to display this range. Implies both - collapsed and atomic. The given DOM node must be an - inline element (as opposed to a block element).
-
readOnly
-
A read-only span can, as long as it is not cleared, not be - modified except by - calling setValue to reset - the whole document. Note: adding a read-only span - currently clears the undo history of the editor, because - existing undo events being partially nullified by read-only - spans would corrupt the history (in the current - implementation).
-
startStyle
Can be used to specify - an extra CSS class to be applied to the leftmost span that - is part of the marker.
-
endStyle
Equivalent - to startStyle, but for the rightmost span.
-
shared
When the - target document is linked to other - documents, you can set shared to true to make the - marker appear in all documents. By default, a marker appears - only in its target document.
-
- The method will return an object that represents the marker - (with constuctor CodeMirror.TextMarker), which - exposes three methods: - clear(), to remove the mark, - find(), which returns a {from, to} - object (both holding document positions), indicating the current - position of the marked range, or undefined if the - marker is no longer in the document, and - finally getOptions(copyWidget), which returns an - object representing the options for the marker. - If copyWidget is given an true, it will clone the - value of - the replacedWith - option, if any.
- -
doc.setBookmark(pos, options) → object
-
Inserts a bookmark, a handle that follows the text around it - as it is being edited, at the given position. A bookmark has two - methods find() and clear(). The first - returns the current position of the bookmark, if it is still in - the document, and the second explicitly removes the bookmark. - The options argument is optional. If given, the following - properties are recognized: -
-
widget
Can be used to display a DOM - node at the current location of the bookmark (analogous to - the replacedWith - option to markText).
-
insertLeft
By default, text typed - when the cursor is on top of the bookmark will end up to the - right of the bookmark. Set this option to true to make it go - to the left instead.
-
- -
doc.findMarksAt(pos) → array
-
Returns an array of all the bookmarks and marked ranges - present at the given position.
-
doc.getAllMarks() → array
-
Returns an array containing all marked ranges in the document.
-
- -

Widget, gutter, and decoration methods

- -
-
cm.setGutterMarker(line, gutterID, value) → lineHandle
-
Sets the gutter marker for the given gutter (identified by - its CSS class, see - the gutters option) - to the given value. Value can be either null, to - clear the marker, or a DOM element, to set it. The DOM element - will be shown in the specified gutter next to the specified - line.
- -
cm.clearGutter(gutterID)
-
Remove all gutter markers in - the gutter with the given ID.
- -
cm.addLineClass(line, where, class) → lineHandle
-
Set a CSS class name for the given line. line - can be a number or a line handle. where determines - to which element this class should be applied, can can be one - of "text" (the text element, which lies in front of - the selection), "background" (a background element - that will be behind the selection), or "wrap" (the - wrapper node that wraps all of the line's elements, including - gutter elements). class should be the name of the - class to apply.
- -
cm.removeLineClass(line, where, class) → lineHandle
-
Remove a CSS class from a line. line can be a - line handle or number. where should be one - of "text", "background", - or "wrap" - (see addLineClass). class - can be left off to remove all classes for the specified node, or - be a string to remove only a specific class.
- -
cm.lineInfo(line) → object
-
Returns the line number, text content, and marker status of - the given line, which can be either a number or a line handle. - The returned object has the structure {line, handle, text, - gutterMarkers, textClass, bgClass, wrapClass, widgets}, - where gutterMarkers is an object mapping gutter IDs - to marker elements, and widgets is an array - of line widgets attached to this - line, and the various class properties refer to classes added - with addLineClass.
- -
cm.addWidget(pos, node, scrollIntoView)
-
Puts node, which should be an absolutely - positioned DOM node, into the editor, positioned right below the - given {line, ch} position. - When scrollIntoView is true, the editor will ensure - that the entire node is visible (if possible). To remove the - widget again, simply use DOM methods (move it somewhere else, or - call removeChild on its parent).
- -
cm.addLineWidget(line, node, options) → object
-
Adds a line widget, an element shown below a line, spanning - the whole of the editor's width, and moving the lines below it - downwards. line should be either an integer or a - line handle, and node should be a DOM node, which - will be displayed below the given line. options, - when given, should be an object that configures the behavior of - the widget. The following options are supported (all default to - false): -
-
coverGutter (boolean)
-
Whether the widget should cover the gutter.
-
noHScroll (boolean)
-
Whether the widget should stay fixed in the face of - horizontal scrolling.
-
above (boolean)
-
Causes the widget to be placed above instead of below - the text of the line.
-
showIfHidden (boolean)
-
When true, will cause the widget to be rendered even if - the line it is associated with is hidden.
-
- Note that the widget node will become a descendant of nodes with - CodeMirror-specific CSS classes, and those classes might in some - cases affect it. This method returns an object that represents - the widget placement. It'll have a line property - pointing at the line handle that it is associated with, and the following methods: -
-
clear()
Removes the widget.
-
changed()
Call - this if you made some change to the widget's DOM node that - might affect its height. It'll force CodeMirror to update - the height of the line that contains the widget.
-
-
- -

Sizing, scrolling and positioning methods

- -
-
cm.setSize(width, height)
-
Programatically set the size of the editor (overriding the - applicable CSS - rules). width and height height - can be either numbers (interpreted as pixels) or CSS units - ("100%", for example). You can - pass null for either of them to indicate that that - dimension should not be changed.
- -
cm.scrollTo(x, y)
-
Scroll the editor to a given (pixel) position. Both - arguments may be left as null - or undefined to have no effect.
-
cm.getScrollInfo()
-
Get an {left, top, width, height, clientWidth, - clientHeight} object that represents the current scroll - position, the size of the scrollable area, and the size of the - visible area (minus scrollbars).
-
cm.scrollIntoView(pos, margin)
-
Scrolls the given element into view. pos may be - either a {line, ch} position, referring to a given - character, null, to refer to the cursor, or - a {left, top, right, bottom} object, in - editor-local coordinates. The margin parameter is - optional. When given, it indicates the amount of pixels around - the given area that should be made visible as well.
- -
cm.cursorCoords(where, mode) → object
-
Returns an {left, top, bottom} object - containing the coordinates of the cursor position. - If mode is "local", they will be - relative to the top-left corner of the editable document. If it - is "page" or not given, they are relative to the - top-left corner of the page. where can be a boolean - indicating whether you want the start (true) or the - end (false) of the selection, or, if a {line, - ch} object is given, it specifies the precise position at - which you want to measure.
-
cm.charCoords(pos, mode) → object
-
Returns the position and dimensions of an arbitrary - character. pos should be a {line, ch} - object. This differs from cursorCoords in that - it'll give the size of the whole character, rather than just the - position that the cursor would have when it would sit at that - position.
-
cm.coordsChar(object, mode) → pos
-
Given an {left, top} object, returns - the {line, ch} position that corresponds to it. The - optional mode parameter determines relative to what - the coordinates are interpreted. It may - be "window", "page" (the default), - or "local".
-
cm.defaultTextHeight() → number
-
Returns the line height of the default font for the editor.
-
cm.defaultCharWidth() → number
-
Returns the pixel width of an 'x' in the default font for - the editor. (Note that for non-monospace fonts, this is mostly - useless, and even for monospace fonts, non-ascii characters - might have a different width).
- -
cm.getViewport() → object
-
Returns a {from, to} object indicating the - start (inclusive) and end (exclusive) of the currently rendered - part of the document. In big documents, when most content is - scrolled out of view, CodeMirror will only render the visible - part, and a margin around it. See also - the viewportChange - event.
- -
cm.refresh()
-
If your code does something to change the size of the editor - element (window resizes are already listened for), or unhides - it, you should probably follow up by calling this method to - ensure CodeMirror is still looking as intended.
-
- -

Mode, state, and token-related methods

- -

When writing language-aware functionality, it can often be - useful to hook into the knowledge that the CodeMirror language - mode has. See the section on modes for a - more detailed description of how these work.

- -
-
doc.getMode() → object
-
Gets the mode object for the editor. Note that this is - distinct from getOption("mode"), which gives you - the mode specification, rather than the resolved, instantiated - mode object.
- -
cm.getTokenAt(pos) → object
-
Retrieves information about the token the current mode found - before the given position (a {line, ch} object). The - returned object has the following properties: -
-
start
The character (on the given line) at which the token starts.
-
end
The character at which the token ends.
-
string
The token's string.
-
type
The token type the mode assigned - to the token, such as "keyword" - or "comment" (may also be null).
-
state
The mode's state at the end of this token.
-
- -
cm.getStateAfter(line) → state
-
Returns the mode's parser state, if any, at the end of the - given line number. If no line number is given, the state at the - end of the document is returned. This can be useful for storing - parsing errors in the state, or getting other kinds of - contextual information for a line.
-
- -

Miscellaneous methods

- -
-
cm.operation(func) → result
-
CodeMirror internally buffers changes and only updates its - DOM structure after it has finished performing some operation. - If you need to perform a lot of operations on a CodeMirror - instance, you can call this method with a function argument. It - will call the function, buffering up all changes, and only doing - the expensive update after the function returns. This can be a - lot faster. The return value from this method will be the return - value of your function.
- -
cm.indentLine(line, dir)
-
Adjust the indentation of the given line. The second - argument (which defaults to "smart") may be one of: -
-
"prev"
-
Base indentation on the indentation of the previous line.
-
"smart"
-
Use the mode's smart indentation if available, behave - like "prev" otherwise.
-
"add"
-
Increase the indentation of the line by - one indent unit.
-
"subtract"
-
Reduce the indentation of the line.
-
- -
doc.posFromIndex(index) → object
-
Calculates and returns a {line, ch} object for a - zero-based index who's value is relative to the start of the - editor's text. If the index is out of range of the text then - the returned object is clipped to start or end of the text - respectively.
-
doc.indexFromPos(object) → number
-
The reverse of posFromIndex.
- -
cm.focus()
-
Give the editor focus.
- -
cm.getInputField() → textarea
-
Returns the hidden textarea used to read input.
-
cm.getWrapperElement() → node
-
Returns the DOM node that represents the editor, and - controls its size. Remove this from your tree to delete an - editor instance.
-
cm.getScrollerElement() → node
-
Returns the DOM node that is responsible for the scrolling - of the editor.
-
cm.getGutterElement() → node
-
Fetches the DOM node that contains the editor gutters.
-
- -

Static properties

- -

The CodeMirror object itself provides - several useful properties. Firstly, its version - property contains a string that indicates the version of the - library. For releases, this simply - contains "major.minor" (for - example "2.33". For beta versions, " B" - (space, capital B) is added at the end of the string, for - development snapshots, " +" (space, plus) is - added.

- -

The CodeMirror.fromTextArea - method provides another way to initialize an editor. It takes a - textarea DOM node as first argument and an optional configuration - object as second. It will replace the textarea with a CodeMirror - instance, and wire up the form of that textarea (if any) to make - sure the editor contents are put into the textarea when the form - is submitted. A CodeMirror instance created this way has three - additional methods:

- -
-
cm.save()
-
Copy the content of the editor into the textarea.
- -
cm.toTextArea()
-
Remove the editor, and restore the original textarea (with - the editor's current content).
- -
cm.getTextArea() → textarea
-
Returns the textarea that the instance was based on.
-
- -

If you want to define extra methods in terms - of the CodeMirror API, it is possible to - use CodeMirror.defineExtension(name, value). This - will cause the given value (usually a method) to be added to all - CodeMirror instances created from then on.

- -

Similarly, CodeMirror.defineOption(name, - default, updateFunc) can be used to define new options for - CodeMirror. The updateFunc will be called with the - editor instance and the new value when an editor is initialized, - and whenever the option is modified - through setOption.

- -

If your extention just needs to run some - code whenever a CodeMirror instance is initialized, - use CodeMirror.defineInitHook. Give it a function as - its only argument, and from then on, that function will be called - (with the instance as argument) whenever a new CodeMirror instance - is initialized.

- -

Add-ons

- -

The addon directory in the distribution contains a - number of reusable components that implement extra editor - functionality. In brief, they are:

- -
-
dialog/dialog.js
-
Provides a very simple way to query users for text input. - Adds an openDialog method to CodeMirror instances, - which can be called with an HTML fragment that provides the - prompt (should include an input tag), and a - callback function that is called when text has been entered. - Depends on addon/dialog/dialog.css.
-
search/searchcursor.js
-
Adds the getSearchCursor(query, start, caseFold) → - cursor method to CodeMirror instances, which can be used - to implement search/replace functionality. query - can be a regular expression or a string (only strings will match - across lines—if they contain newlines). start - provides the starting position of the search. It can be - a {line, ch} object, or can be left off to default - to the start of the document. caseFold is only - relevant when matching a string. It will cause the search to be - case-insensitive. A search cursor has the following methods: -
-
findNext(), findPrevious() → boolean
-
Search forward or backward from the current position. - The return value indicates whether a match was found. If - matching a regular expression, the return value will be the - array returned by the match method, in case you - want to extract matched groups.
-
from(), to() → object
-
These are only valid when the last call - to findNext or findPrevious did - not return false. They will return {line, ch} - objects pointing at the start and end of the match.
-
replace(text)
-
Replaces the currently found match with the given text - and adjusts the cursor position to reflect the - replacement.
-
- - -
Implements the search commands. CodeMirror has keys bound to - these by default, but will not do anything with them unless an - implementation is provided. Depends - on searchcursor.js, and will make use - of openDialog when - available to make prompting for search queries less ugly.
-
edit/matchbrackets.js
-
Defines an option matchBrackets which, when set - to true, causes matching brackets to be highlighted whenever the - cursor is next to them. It also adds a - method matchBrackets that forces this to happen - once, and a method findMatchingBracket that can be - used to run the bracket-finding algorithm that this uses - internally.
-
edit/closebrackets.js
-
Defines an option autoCloseBrackets that will - auto-close brackets and quotes when typed. By default, it'll - auto-close ()[]{}''"", but you can pass it a - string similar to that (containing pairs of matching characters) - to customize it. Demo - here.
-
fold/foldcode.js
-
Helps with code folding. - See the demo for an example. - Call CodeMirror.newFoldFunction with a range-finder - helper function to create a function that will, when applied to - a CodeMirror instance and a line number, attempt to fold or - unfold the block starting at the given line. A range-finder is a - language-specific function that also takes an instance and a - line number, and returns an range to be folded, or null if no - block is started on that line. There are files in - the addon/fold/ - directory providing CodeMirror.braceRangeFinder, - which finds blocks in brace languages (JavaScript, C, Java, - etc), CodeMirror.indentRangeFinder, for languages - where indentation determines block structure (Python, Haskell), - and CodeMirror.tagRangeFinder, for XML-style - languages.
-
runmode/runmode.js
-
Can be used to run a CodeMirror mode over text without - actually opening an editor instance. - See the demo for an example. - There are alternate versions of the file avaible for - running stand-alone - (without including all of CodeMirror) and - for running under - node.js.
-
mode/overlay.js
-
Mode combinator that can be used to extend a mode with an - 'overlay' — a secondary mode is run over the stream, along with - the base mode, and can color specific pieces of text without - interfering with the base mode. - Defines CodeMirror.overlayMode, which is used to - create such a mode. See this - demo for a detailed example.
-
mode/multiplex.js
-
Mode combinator that can be used to easily 'multiplex' - between several modes. - Defines CodeMirror.multiplexingMode which, when - given as first argument a mode object, and as other arguments - any number of {open, close, mode [, delimStyle]} - objects, will return a mode object that starts parsing using the - mode passed as first argument, but will switch to another mode - as soon as it encounters a string that occurs in one of - the open fields of the passed objects. When in a - sub-mode, it will go back to the top mode again when - the close string is encountered. - Pass "\n" for open or close - if you want to switch on a blank line. - When delimStyle is specified, it will be the token - style returned for the delimiter tokens. The outer mode will not - see the content between the delimiters. - See this demo for an - example.
-
hint/show-hint.js
-
Provides a framework for showing autocompletion hints. - Defines CodeMirror.showHint, which takes a - CodeMirror instance and a hinting function, and pops up a widget - that allows the user to select a completion. Hinting functions - are function that take an editor instance, and return - a {list, from, to} object, where list - is an array of strings (the completions), and from - and to give the start and end of the token that is - being completed. Depends - on addon/hint/show-hint.css. See the other files in - the addon/hint for - hint sources for various languages. Check - out the demo for an - example.
-
match-highlighter.js
-
Adds a highlightSelectionMatches option that - can be enabled to highlight all instances of a currently - selected word. - Demo here.
-
lint/lint.js
-
Defines an interface component for showing linting warnings, - with pluggable warning sources - (see json-lint.js - and javascript-lint.js - in the same directory). Defines a lintWith option - that can be set to a warning source (for - example CodeMirror.javascriptValidator). Depends - on addon/lint/lint.css. A demo can be - found here.
-
selection/mark-selection.js
-
Causes the selected text to be marked with the CSS class - CodeMirror-selectedtext when the styleSelectedText option - is enabled. Useful to change the colour of the selection (in addition to the background), - like in this demo.
-
selection/active-line.js
-
Defines a styleActiveLine option that, when enabled, - gives the wrapper of the active line the class CodeMirror-activeline, - and adds a background with the class CodeMirror-activeline-background. - is enabled. See the demo.
-
edit/closetag.js
-
Provides utility functions for adding automatic tag closing - to XML modes. See - the demo.
-
mode/loadmode.js
-
Defines a CodeMirror.requireMode(modename, - callback) function that will try to load a given mode and - call the callback when it succeeded. You'll have to - set CodeMirror.modeURL to a string that mode paths - can be constructed from, for - example "mode/%N/%N.js"—the %N's will - be replaced with the mode name. Also - defines CodeMirror.autoLoadMode(instance, mode), - which will ensure the given mode is loaded and cause the given - editor instance to refresh its mode when the loading - succeeded. See the demo.
-
edit/continuecomment.js
-
Adds an continueComments option, which can be - set to true to have the editor prefix new lines inside C-like - block comments with an asterisk when Enter is pressed. It can - also be set to a string in order to bind this functionality to a - specific key..
-
display/placeholder.js
-
Adds a placeholder option that can be used to - make text appear in the editor when it is empty and not focused. - Also gives the editor a CodeMirror-empty CSS class - whenever it doesn't contain any text. - See the demo.
-
- -

Writing CodeMirror Modes

- -

Modes typically consist of a single JavaScript file. This file - defines, in the simplest case, a lexer (tokenizer) for your - language—a function that takes a character stream as input, - advances it past a token, and returns a style for that token. More - advanced modes can also handle indentation for the language.

- -

The mode script should - call CodeMirror.defineMode to register itself with - CodeMirror. This function takes two arguments. The first should be - the name of the mode, for which you should use a lowercase string, - preferably one that is also the name of the files that define the - mode (i.e. "xml" is defined in xml.js). The - second argument should be a function that, given a CodeMirror - configuration object (the thing passed to - the CodeMirror function) and an optional mode - configuration object (as in - the mode option), returns - a mode object.

- -

Typically, you should use this second argument - to defineMode as your module scope function (modes - should not leak anything into the global scope!), i.e. write your - whole mode inside this function.

- -

The main responsibility of a mode script is parsing - the content of the editor. Depending on the language and the - amount of functionality desired, this can be done in really easy - or extremely complicated ways. Some parsers can be stateless, - meaning that they look at one element (token) of the code - at a time, with no memory of what came before. Most, however, will - need to remember something. This is done by using a state - object, which is an object that is always passed when - reading a token, and which can be mutated by the tokenizer.

- -

Modes that use a state must define - a startState method on their mode object. This is a - function of no arguments that produces a state object to be used - at the start of a document.

- -

The most important part of a mode object is - its token(stream, state) method. All modes must - define this method. It should read one token from the stream it is - given as an argument, optionally update its state, and return a - style string, or null for tokens that do not have to - be styled. For your styles, you are encouraged to use the - 'standard' names defined in the themes (without - the cm- prefix). If that fails, it is also possible - to come up with your own and write your own CSS theme file.

- -

The stream object that's passed - to token encapsulates a line of code (tokens may - never span lines) and our current position in that line. It has - the following API:

- -
-
eol() → boolean
-
Returns true only if the stream is at the end of the - line.
-
sol() → boolean
-
Returns true only if the stream is at the start of the - line.
- -
peek() → character
-
Returns the next character in the stream without advancing - it. Will return an null at the end of the - line.
-
next() → character
-
Returns the next character in the stream and advances it. - Also returns null when no more characters are - available.
- -
eat(match) → character
-
match can be a character, a regular expression, - or a function that takes a character and returns a boolean. If - the next character in the stream 'matches' the given argument, - it is consumed and returned. Otherwise, undefined - is returned.
-
eatWhile(match) → boolean
-
Repeatedly calls eat with the given argument, - until it fails. Returns true if any characters were eaten.
-
eatSpace() → boolean
-
Shortcut for eatWhile when matching - white-space.
-
skipToEnd()
-
Moves the position to the end of the line.
-
skipTo(ch) → boolean
-
Skips to the next occurrence of the given character, if - found on the current line (doesn't advance the stream if the - character does not occur on the line). Returns true if the - character was found.
-
match(pattern, consume, caseFold) → boolean
-
Act like a - multi-character eat—if consume is true - or not given—or a look-ahead that doesn't update the stream - position—if it is false. pattern can be either a - string or a regular expression starting with ^. - When it is a string, caseFold can be set to true to - make the match case-insensitive. When successfully matching a - regular expression, the returned value will be the array - returned by match, in case you need to extract - matched groups.
- -
backUp(n)
-
Backs up the stream n characters. Backing it up - further than the start of the current token will cause things to - break, so be careful.
-
column() → integer
-
Returns the column (taking into account tabs) at which the - current token starts.
-
indentation() → integer
-
Tells you how far the current line has been indented, in - spaces. Corrects for tab characters.
- -
current() → string
-
Get the string between the start of the current token and - the current stream position.
-
- -

By default, blank lines are simply skipped when - tokenizing a document. For languages that have significant blank - lines, you can define a blankLine(state) method on - your mode that will get called whenever a blank line is passed - over, so that it can update the parser state.

- -

Because state object are mutated, and CodeMirror - needs to keep valid versions of a state around so that it can - restart a parse at any line, copies must be made of state objects. - The default algorithm used is that a new state object is created, - which gets all the properties of the old object. Any properties - which hold arrays get a copy of these arrays (since arrays tend to - be used as mutable stacks). When this is not correct, for example - because a mode mutates non-array properties of its state object, a - mode object should define a copyState method, - which is given a state and should return a safe copy of that - state.

- -

If you want your mode to provide smart indentation - (through the indentLine - method and the indentAuto - and newlineAndIndent commands, to which keys can be - bound), you must define - an indent(state, textAfter) method on your mode - object.

- -

The indentation method should inspect the given state object, - and optionally the textAfter string, which contains - the text on the line that is being indented, and return an - integer, the amount of spaces to indent. It should usually take - the indentUnit - option into account. An indentation method may - return CodeMirror.Pass to indicate that it - could not come up with a precise indentation.

- -

Finally, a mode may define - an electricChars property, which should hold a string - containing all the characters that should trigger the behaviour - described for - the electricChars - option.

- -

So, to summarize, a mode must provide - a token method, and it may - provide startState, copyState, - and indent methods. For an example of a trivial mode, - see the diff mode, for a more - involved example, see the C-like - mode.

- -

Sometimes, it is useful for modes to nest—to have one - mode delegate work to another mode. An example of this kind of - mode is the mixed-mode HTML - mode. To implement such nesting, it is usually necessary to - create mode objects and copy states yourself. To create a mode - object, there are CodeMirror.getMode(options, - parserConfig), where the first argument is a configuration - object as passed to the mode constructor function, and the second - argument is a mode specification as in - the mode option. To copy a - state object, call CodeMirror.copyState(mode, state), - where mode is the mode that created the given - state.

- -

In a nested mode, it is recommended to add an - extra methods, innerMode which, given a state object, - returns a {state, mode} object with the inner mode - and its state for the current position. These are used by utility - scripts such as the tag closer to - get context information. Use the CodeMirror.innerMode - helper function to, starting from a mode and a state, recursively - walk down to the innermost mode and state.

- -

To make indentation work properly in a nested parser, it is - advisable to give the startState method of modes that - are intended to be nested an optional argument that provides the - base indentation for the block of code. The JavaScript and CSS - parser do this, for example, to allow JavaScript and CSS code - inside the mixed-mode HTML mode to be properly indented.

- -

It is possible, and encouraged, to associate your mode, or a - certain configuration of your mode, with - a MIME type. For - example, the JavaScript mode associates itself - with text/javascript, and its JSON variant - with application/json. To do this, - call CodeMirror.defineMIME(mime, modeSpec), - where modeSpec can be a string or object specifying a - mode, as in the mode - option.

- -

Sometimes, it is useful to add or override mode - object properties from external code. - The CodeMirror.extendMode can be used to add - properties to mode objects produced for a specific mode. Its first - argument is the name of the mode, its second an object that - specifies the properties that should be added. This is mostly - useful to add utilities that can later be looked - up through getMode.

- -
- -
 
- - - - - diff --git a/gulliver/js/codemirror/doc/modes.html b/gulliver/js/codemirror/doc/modes.html deleted file mode 100644 index f8080a46e..000000000 --- a/gulliver/js/codemirror/doc/modes.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - CodeMirror: Mode list - - - - - -

{ } CodeMirror

- -
- -
-/* Full list of
-   modes */
-
-
- -

Every mode in the distribution. The list on the front-page leaves -out some of the more obscure ones.

- - - - - diff --git a/gulliver/js/codemirror/doc/oldrelease.html b/gulliver/js/codemirror/doc/oldrelease.html deleted file mode 100644 index f97a65a8c..000000000 --- a/gulliver/js/codemirror/doc/oldrelease.html +++ /dev/null @@ -1,492 +0,0 @@ - - - - - CodeMirror - - - - - - -

{ } CodeMirror

- -
- -
-/* Old release
-   history */
-
-
- -

22-10-2012: Version 3.0, beta 2:

- -
    -
  • Fix page-based coordinate computation.
  • -
  • Fix firing of gutterClick event.
  • -
  • Add cursorHeight option.
  • -
  • Fix bi-directional text regression.
  • -
  • Add viewportMargin option.
  • -
  • Directly handle mousewheel events (again, hopefully better).
  • -
  • Make vertical cursor movement more robust (through widgets, big line gaps).
  • -
  • Add flattenSpans option.
  • -
  • Many optimizations. Poor responsiveness should be fixed.
  • -
  • Initialization in hidden state works again.
  • -
  • Full list of patches.
  • -
- -

19-09-2012: Version 2.34:

- -
    -
  • New mode: Common Lisp.
  • -
  • Fix right-click select-all on most browsers.
  • -
  • Change the way highlighting happens:
      Saves memory and CPU cycles.
      compareStates is no longer needed.
      onHighlightComplete no longer works.
  • -
  • Integrate mode (Markdown, XQuery, CSS, sTex) tests in central testsuite.
  • -
  • Add a CodeMirror.version property.
  • -
  • More robust handling of nested modes in formatting and closetag plug-ins.
  • -
  • Un/redo now preserves marked text and bookmarks.
  • -
  • Full list of patches.
  • -
- -

19-09-2012: Version 3.0, beta 1:

- -
    -
  • Bi-directional text support.
  • -
  • More powerful gutter model.
  • -
  • Support for arbitrary text/widget height.
  • -
  • In-line widgets.
  • -
  • Generalized event handling.
  • -
- -

23-08-2012: Version 2.33:

- -
    -
  • New mode: Sieve.
  • -
  • New getViewPort and onViewportChange API.
  • -
  • Configurable cursor blink rate.
  • -
  • Make binding a key to false disabling handling (again).
  • -
  • Show non-printing characters as red dots.
  • -
  • More tweaks to the scrolling model.
  • -
  • Expanded testsuite. Basic linter added.
  • -
  • Remove most uses of innerHTML. Remove CodeMirror.htmlEscape.
  • -
  • Full list of patches.
  • -
- -

23-07-2012: Version 2.32:

- -

Emergency fix for a bug where an editor with - line wrapping on IE will break when there is no - scrollbar.

- -

20-07-2012: Version 2.31:

- - - -

22-06-2012: Version 2.3:

- -
    -
  • New scrollbar implementation. Should flicker less. Changes DOM structure of the editor.
  • -
  • New theme: vibrant-ink.
  • -
  • Many extensions to the VIM keymap (including text objects).
  • -
  • Add mode-multiplexing utility script.
  • -
  • Fix bug where right-click paste works in read-only mode.
  • -
  • Add a getScrollInfo method.
  • -
  • Lots of other fixes.
  • -
- -

23-05-2012: Version 2.25:

- -
    -
  • New mode: Erlang.
  • -
  • Remove xmlpure mode (use xml.js).
  • -
  • Fix line-wrapping in Opera.
  • -
  • Fix X Windows middle-click paste in Chrome.
  • -
  • Fix bug that broke pasting of huge documents.
  • -
  • Fix backspace and tab key repeat in Opera.
  • -
- -

23-04-2012: Version 2.24:

- -
    -
  • Drop support for Internet Explorer 6.
  • -
  • New - modes: Shell, Tiki - wiki, Pig Latin.
  • -
  • New themes: Ambiance, Blackboard.
  • -
  • More control over drag/drop - with dragDrop - and onDragEvent - options.
  • -
  • Make HTML mode a bit less pedantic.
  • -
  • Add compoundChange API method.
  • -
  • Several fixes in undo history and line hiding.
  • -
  • Remove (broken) support for catchall in key maps, - add nofallthrough boolean field instead.
  • -
- -

26-03-2012: Version 2.23:

- -
    -
  • Change default binding for tab [more] - -
  • -
  • New modes: XQuery and VBScript.
  • -
  • Two new themes: lesser-dark and xq-dark.
  • -
  • Differentiate between background and text styles in setLineClass.
  • -
  • Fix drag-and-drop in IE9+.
  • -
  • Extend charCoords - and cursorCoords with a mode argument.
  • -
  • Add autofocus option.
  • -
  • Add findMarksAt method.
  • -
- -

27-02-2012: Version 2.22:

- - - -

27-01-2012: Version 2.21:

- -
    -
  • Added LESS, MySQL, - Go, and Verilog modes.
  • -
  • Add smartIndent - option.
  • -
  • Support a cursor in readOnly-mode.
  • -
  • Support assigning multiple styles to a token.
  • -
  • Use a new approach to drawing the selection.
  • -
  • Add scrollTo method.
  • -
  • Allow undo/redo events to span non-adjacent lines.
  • -
  • Lots and lots of bugfixes.
  • -
- -

20-12-2011: Version 2.2:

- - - -

21-11-2011: Version 2.18:

-

Fixes TextMarker.clear, which is broken in 2.17.

- -

21-11-2011: Version 2.17:

-
    -
  • Add support for line - wrapping and code - folding.
  • -
  • Add Github-style Markdown mode.
  • -
  • Add Monokai - and Rubyblue themes.
  • -
  • Add setBookmark method.
  • -
  • Move some of the demo code into reusable components - under lib/util.
  • -
  • Make screen-coord-finding code faster and more reliable.
  • -
  • Fix drag-and-drop in Firefox.
  • -
  • Improve support for IME.
  • -
  • Speed up content rendering.
  • -
  • Fix browser's built-in search in Webkit.
  • -
  • Make double- and triple-click work in IE.
  • -
  • Various fixes to modes.
  • -
- -

27-10-2011: Version 2.16:

-
    -
  • Add Perl, Rust, TiddlyWiki, and Groovy modes.
  • -
  • Dragging text inside the editor now moves, rather than copies.
  • -
  • Add a coordsFromIndex method.
  • -
  • API change: setValue now no longer clears history. Use clearHistory for that.
  • -
  • API change: markText now - returns an object with clear and find - methods. Marked text is now more robust when edited.
  • -
  • Fix editing code with tabs in Internet Explorer.
  • -
- -

26-09-2011: Version 2.15:

-

Fix bug that snuck into 2.14: Clicking the - character that currently has the cursor didn't re-focus the - editor.

- -

26-09-2011: Version 2.14:

- - - -

23-08-2011: Version 2.13:

- - -

25-07-2011: Version 2.12:

-
    -
  • Add a SPARQL mode.
  • -
  • Fix bug with cursor jumping around in an unfocused editor in IE.
  • -
  • Allow key and mouse events to bubble out of the editor. Ignore widget clicks.
  • -
  • Solve cursor flakiness after undo/redo.
  • -
  • Fix block-reindent ignoring the last few lines.
  • -
  • Fix parsing of multi-line attrs in XML mode.
  • -
  • Use innerHTML for HTML-escaping.
  • -
  • Some fixes to indentation in C-like mode.
  • -
  • Shrink horiz scrollbars when long lines removed.
  • -
  • Fix width feedback loop bug that caused the width of an inner DIV to shrink.
  • -
- -

04-07-2011: Version 2.11:

-
    -
  • Add a Scheme mode.
  • -
  • Add a replace method to search cursors, for cursor-preserving replacements.
  • -
  • Make the C-like mode mode more customizable.
  • -
  • Update XML mode to spot mismatched tags.
  • -
  • Add getStateAfter API and compareState mode API methods for finer-grained mode magic.
  • -
  • Add a getScrollerElement API method to manipulate the scrolling DIV.
  • -
  • Fix drag-and-drop for Firefox.
  • -
  • Add a C# configuration for the C-like mode.
  • -
  • Add full-screen editing and mode-changing demos.
  • -
- -

07-06-2011: Version 2.1:

-

Add - a theme system - (demo). Note that this is not - backwards-compatible—you'll have to update your styles and - modes!

- -

07-06-2011: Version 2.02:

-
    -
  • Add a Lua mode.
  • -
  • Fix reverse-searching for a regexp.
  • -
  • Empty lines can no longer break highlighting.
  • -
  • Rework scrolling model (the outer wrapper no longer does the scrolling).
  • -
  • Solve horizontal jittering on long lines.
  • -
  • Add runmode.js.
  • -
  • Immediately re-highlight text when typing.
  • -
  • Fix problem with 'sticking' horizontal scrollbar.
  • -
- -

26-05-2011: Version 2.01:

-
    -
  • Add a Smalltalk mode.
  • -
  • Add a reStructuredText mode.
  • -
  • Add a Python mode.
  • -
  • Add a PL/SQL mode.
  • -
  • coordsChar now works
  • -
  • Fix a problem where onCursorActivity interfered with onChange.
  • -
  • Fix a number of scrolling and mouse-click-position glitches.
  • -
  • Pass information about the changed lines to onChange.
  • -
  • Support cmd-up/down on OS X.
  • -
  • Add triple-click line selection.
  • -
  • Don't handle shift when changing the selection through the API.
  • -
  • Support "nocursor" mode for readOnly option.
  • -
  • Add an onHighlightComplete option.
  • -
  • Fix the context menu for Firefox.
  • -
- -

28-03-2011: Version 2.0:

-

CodeMirror 2 is a complete rewrite that's - faster, smaller, simpler to use, and less dependent on browser - quirks. See this - and this - for more information. - -

28-03-2011: Version 1.0:

-
    -
  • Fix error when debug history overflows.
  • -
  • Refine handling of C# verbatim strings.
  • -
  • Fix some issues with JavaScript indentation.
  • -
- -

22-02-2011: Version 2.0 beta 2:

-

Somewhat more mature API, lots of bugs shaken out. - -

17-02-2011: Version 0.94:

-
    -
  • tabMode: "spaces" was modified slightly (now indents when something is selected).
  • -
  • Fixes a bug that would cause the selection code to break on some IE versions.
  • -
  • Disabling spell-check on WebKit browsers now works.
  • -
- -

08-02-2011: Version 2.0 beta 1:

-

CodeMirror 2 is a complete rewrite of - CodeMirror, no longer depending on an editable frame.

- -

19-01-2011: Version 0.93:

-
    -
  • Added a Regular Expression parser.
  • -
  • Fixes to the PHP parser.
  • -
  • Support for regular expression in search/replace.
  • -
  • Add save method to instances created with fromTextArea.
  • -
  • Add support for MS T-SQL in the SQL parser.
  • -
  • Support use of CSS classes for highlighting brackets.
  • -
  • Fix yet another hang with line-numbering in hidden editors.
  • -
- -

17-12-2010: Version 0.92:

-
    -
  • Make CodeMirror work in XHTML documents.
  • -
  • Fix bug in handling of backslashes in Python strings.
  • -
  • The styleNumbers option is now officially - supported and documented.
  • -
  • onLineNumberClick option added.
  • -
  • More consistent names onLoad and - onCursorActivity callbacks. Old names still work, but - are deprecated.
  • -
  • Add a Freemarker mode.
  • -
- -

11-11-2010: Version 0.91:

-
    -
  • Adds support for Java.
  • -
  • Small additions to the PHP and SQL parsers.
  • -
  • Work around various Webkit issues.
  • -
  • Fix toTextArea to update the code in the textarea.
  • -
  • Add a noScriptCaching option (hack to ease development).
  • -
  • Make sub-modes of HTML mixed mode configurable.
  • -
- -

02-10-2010: Version 0.9:

-
    -
  • Add support for searching backwards.
  • -
  • There are now parsers for Scheme, XQuery, and OmetaJS.
  • -
  • Makes height: "dynamic" more robust.
  • -
  • Fixes bug where paste did not work on OS X.
  • -
  • Add a enterMode and electricChars options to make indentation even more customizable.
  • -
  • Add firstLineNumber option.
  • -
  • Fix bad handling of @media rules by the CSS parser.
  • -
  • Take a new, more robust approach to working around the invisible-last-line bug in WebKit.
  • -
- -

22-07-2010: Version 0.8:

-
    -
  • Add a cursorCoords method to find the screen - coordinates of the cursor.
  • -
  • A number of fixes and support for more syntax in the PHP parser.
  • -
  • Fix indentation problem with JSON-mode JS parser in Webkit.
  • -
  • Add a minification UI.
  • -
  • Support a height: dynamic mode, where the editor's - height will adjust to the size of its content.
  • -
  • Better support for IME input mode.
  • -
  • Fix JavaScript parser getting confused when seeing a no-argument - function call.
  • -
  • Have CSS parser see the difference between selectors and other - identifiers.
  • -
  • Fix scrolling bug when pasting in a horizontally-scrolled - editor.
  • -
  • Support toTextArea method in instances created with - fromTextArea.
  • -
  • Work around new Opera cursor bug that causes the cursor to jump - when pressing backspace at the end of a line.
  • -
- -

27-04-2010: Version - 0.67:

-

More consistent page-up/page-down behaviour - across browsers. Fix some issues with hidden editors looping forever - when line-numbers were enabled. Make PHP parser parse - "\\" correctly. Have jumpToLine work on - line handles, and add cursorLine function to fetch the - line handle where the cursor currently is. Add new - setStylesheet function to switch style-sheets in a - running editor.

- -

01-03-2010: Version - 0.66:

-

Adds removeLine method to API. - Introduces the PLSQL parser. - Marks XML errors by adding (rather than replacing) a CSS class, so - that they can be disabled by modifying their style. Fixes several - selection bugs, and a number of small glitches.

- -

12-11-2009: Version - 0.65:

-

Add support for having both line-wrapping and - line-numbers turned on, make paren-highlighting style customisable - (markParen and unmarkParen config - options), work around a selection bug that Opera - reintroduced in version 10.

- -

23-10-2009: Version - 0.64:

-

Solves some issues introduced by the - paste-handling changes from the previous release. Adds - setSpellcheck, setTextWrapping, - setIndentUnit, setUndoDepth, - setTabMode, and setLineNumbers to - customise a running editor. Introduces an SQL parser. Fixes a few small - problems in the Python - parser. And, as usual, add workarounds for various newly discovered - browser incompatibilities.

- -

31-08-2009: Version -0.63:

-

Overhaul of paste-handling (less fragile), fixes for several -serious IE8 issues (cursor jumping, end-of-document bugs) and a number -of small problems.

- -

30-05-2009: Version -0.62:

-

Introduces Python -and Lua parsers. Add -setParser (on-the-fly mode changing) and -clearHistory methods. Make parsing passes time-based -instead of lines-based (see the passTime option).

- - diff --git a/gulliver/js/codemirror/doc/realworld.html b/gulliver/js/codemirror/doc/realworld.html deleted file mode 100644 index 4f0dec346..000000000 --- a/gulliver/js/codemirror/doc/realworld.html +++ /dev/null @@ -1,96 +0,0 @@ - - - - - CodeMirror: Real-world uses - - - - - -

{ } CodeMirror

- -
- -
-/* Real world uses,
-   full list */
-
-
- -

Contact me if you'd like - your project to be added to this list.

- - - - - diff --git a/gulliver/js/codemirror/doc/reporting.html b/gulliver/js/codemirror/doc/reporting.html deleted file mode 100644 index a61651253..000000000 --- a/gulliver/js/codemirror/doc/reporting.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - CodeMirror: Reporting Bugs - - - - - - -

{ } CodeMirror

- -
- -
-/* Reporting bugs
-   effectively */
-
-
- -
- -

So you found a problem in CodeMirror. By all means, report it! Bug -reports from users are the main drive behind improvements to -CodeMirror. But first, please read over these points:

- -
    -
  1. CodeMirror is maintained by volunteers. They don't owe you - anything, so be polite. Reports with an indignant or belligerent - tone tend to be moved to the bottom of the pile.
  2. - -
  3. Include information about the browser in which the - problem occurred. Even if you tested several browsers, and - the problem occurred in all of them, mention this fact in the bug - report. Also include browser version numbers and the operating - system that you're on.
  4. - -
  5. Mention which release of CodeMirror you're using. Preferably, - try also with the current development snapshot, to ensure the - problem has not already been fixed.
  6. - -
  7. Mention very precisely what went wrong. "X is broken" is not a - good bug report. What did you expect to happen? What happened - instead? Describe the exact steps a maintainer has to take to make - the problem occur. We can not fix something that we can not - observe.
  8. - -
  9. If the problem can not be reproduced in any of the demos - included in the CodeMirror distribution, please provide an HTML - document that demonstrates the problem. The best way to do this is - to go to jsbin.com, enter - it there, press save, and include the resulting link in your bug - report.
  10. -
- -
- - - diff --git a/gulliver/js/codemirror/doc/upgrade_v2.2.html b/gulliver/js/codemirror/doc/upgrade_v2.2.html deleted file mode 100644 index 7e4d84004..000000000 --- a/gulliver/js/codemirror/doc/upgrade_v2.2.html +++ /dev/null @@ -1,98 +0,0 @@ - - - - - CodeMirror: Upgrading to v2.2 - - - - - -

{ } CodeMirror

- -
- -
-/* Upgrading to
-   v2.2 */
-
-
- -
- -

There are a few things in the 2.2 release that require some care -when upgrading.

- -

No more default.css

- -

The default theme is now included -in codemirror.css, so -you do not have to included it separately anymore. (It was tiny, so -even if you're not using it, the extra data overhead is negligible.) - -

Different key customization

- -

CodeMirror has moved to a system -where keymaps are used to -bind behavior to keys. This means custom -bindings are now possible.

- -

Three options that influenced key -behavior, tabMode, enterMode, -and smartHome, are no longer supported. Instead, you can -provide custom bindings to influence the way these keys act. This is -done through the -new extraKeys -option, which can hold an object mapping key names to functionality. A -simple example would be:

- -
  extraKeys: {
-    "Ctrl-S": function(instance) { saveText(instance.getValue()); },
-    "Ctrl-/": "undo"
-  }
- -

Keys can be mapped either to functions, which will be given the -editor instance as argument, or to strings, which are mapped through -functions through the CodeMirror.commands table, which -contains all the built-in editing commands, and can be inspected and -extended by external code.

- -

By default, the Home key is bound to -the "goLineStartSmart" command, which moves the cursor to -the first non-whitespace character on the line. You can set do this to -make it always go to the very start instead:

- -
  extraKeys: {"Home": "goLineStart"}
- -

Similarly, Enter is bound -to "newlineAndIndent" by default. You can bind it to -something else to get different behavior. To disable special handling -completely and only get a newline character inserted, you can bind it -to false:

- -
  extraKeys: {"Enter": false}
- -

The same works for Tab. If you don't want CodeMirror -to handle it, bind it to false. The default behaviour is -to indent the current line more ("indentMore" command), -and indent it less when shift is held ("indentLess"). -There are also "indentAuto" (smart indent) -and "insertTab" commands provided for alternate -behaviors. Or you can write your own handler function to do something -different altogether.

- -

Tabs

- -

Handling of tabs changed completely. The display width of tabs can -now be set with the tabSize option, and tabs can -be styled by setting CSS rules -for the cm-tab class.

- -

The default width for tabs is now 4, as opposed to the 8 that is -hard-wired into browsers. If you are relying on 8-space tabs, make -sure you explicitly set tabSize: 8 in your options.

- -
- - - diff --git a/gulliver/js/codemirror/doc/upgrade_v3.html b/gulliver/js/codemirror/doc/upgrade_v3.html deleted file mode 100644 index 7e8a6b61a..000000000 --- a/gulliver/js/codemirror/doc/upgrade_v3.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - CodeMirror: Upgrading to v3 - - - - - - - - - - - - - -

{ } CodeMirror

- -
- -
-/* Upgrading to
-   version 3 */
-
-
- -
- -

Version 3 does not depart too much from 2.x API, and sites that use -CodeMirror in a very simple way might be able to upgrade without -trouble. But it does introduce a number of incompatibilities. Please -at least skim this text before upgrading.

- -

Note that version 3 drops full support for Internet -Explorer 7. The editor will mostly work on that browser, but -it'll be significantly glitchy.

- -

DOM structure

- -

This one is the most likely to cause problems. The internal -structure of the editor has changed quite a lot, mostly to implement a -new scrolling model.

- -

Editor height is now set on the outer wrapper element (CSS -class CodeMirror), not on the scroller element -(CodeMirror-scroll).

- -

Other nodes were moved, dropped, and added. If you have any code -that makes assumptions about the internal DOM structure of the editor, -you'll have to re-test it and probably update it to work with v3.

- -

See the styling section of the -manual for more information.

- -

Gutter model

- -

In CodeMirror 2.x, there was a single gutter, and line markers -created with setMarker would have to somehow coexist with -the line numbers (if present). Version 3 allows you to specify an -array of gutters, by class -name, -use setGutterMarker -to add or remove markers in individual gutters, and clear whole -gutters -with clearGutter. -Gutter markers are now specified as DOM nodes, rather than HTML -snippets.

- -

The gutters no longer horizontally scrolls along with the content. -The fixedGutter option was removed (since it is now the -only behavior).

- -
-<style>
-  /* Define a gutter style */
-  .note-gutter { width: 3em; background: cyan; }
-</style>
-<script>
-  // Create an instance with two gutters -- line numbers and notes
-  var cm = new CodeMirror(document.body, {
-    gutters: ["note-gutter", "CodeMirror-linenumbers"],
-    lineNumbers: true
-  });
-  // Add a note to line 0
-  cm.setGutterMarker(0, "note-gutter", document.createTextNode("hi"));
-</script>
-
- -

Event handling

- -

Most of the onXYZ options have been removed. The same -effect is now obtained by calling -the on method with a string -identifying the event type. Multiple handlers can now be registered -(and individually unregistered) for an event, and objects such as line -handlers now also expose events. See the -full list here.

- -

(The onKeyEvent and onDragEvent options, -which act more as hooks than as event handlers, are still there in -their old form.)

- -
-cm.on("change", function(cm, change) {
-  console.log("something changed! (" + change.origin + ")");
-});
-
- -

markText method arguments

- -

The markText method -(which has gained some interesting new features, such as creating -atomic and read-only spans, or replacing spans with widgets) no longer -takes the CSS class name as a separate argument, but makes it an -optional field in the options object instead.

- -
-// Style first ten lines, and forbid the cursor from entering them
-cm.markText({line: 0, ch: 0}, {line: 10, ch: 0}, {
-  className: "magic-text",
-  inclusiveLeft: true,
-  atomic: true
-});
-
- -

Line folding

- -

The interface for hiding lines has been -removed. markText can -now be used to do the same in a more flexible and powerful way.

- -

The folding script has been -updated to use the new interface, and should now be more robust.

- -
-// Fold a range, replacing it with the text "??"
-var range = cm.markText({line: 4, ch: 2}, {line: 8, ch: 1}, {
-  replacedWith: document.createTextNode("??"),
-  // Auto-unfold when cursor moves into the range
-  clearOnEnter: true
-});
-// Get notified when auto-unfolding
-CodeMirror.on(range, "clear", function() {
-  console.log("boom");
-});
-
- -

Line CSS classes

- -

The setLineClass method has been replaced -by addLineClass -and removeLineClass, -which allow more modular control over the classes attached to a line.

- -
-var marked = cm.addLineClass(10, "background", "highlighted-line");
-setTimeout(function() {
-  cm.removeLineClass(marked, "background", "highlighted-line");
-});
-
- -

Position properties

- -

All methods that take or return objects that represent screen -positions now use {left, top, bottom, right} properties -(not always all of them) instead of the {x, y, yBot} used -by some methods in v2.x.

- -

Affected methods -are cursorCoords, charCoords, coordsChar, -and getScrollInfo.

- -

Bracket matching no longer in core

- -

The matchBrackets -option is no longer defined in the core editor. -Load addon/edit/matchbrackets.js to enable it.

- -

Mode management

- -

The CodeMirror.listModes -and CodeMirror.listMIMEs functions, used for listing -defined modes, are gone. You are now encouraged to simply -inspect CodeMirror.modes (mapping mode names to mode -constructors) and CodeMirror.mimeModes (mapping MIME -strings to mode specs).

- -

New features

- -

Some more reasons to upgrade to version 3.

- -
    -
  • Bi-directional text support. CodeMirror will now mostly do the - right thing when editing Arabic or Hebrew text.
  • -
  • Arbitrary line heights. Using fonts with different heights - inside the editor (whether off by one pixel or fifty) is now - supported and handled gracefully.
  • -
  • In-line widgets. See the demo - and the docs.
  • -
  • Defining custom options - with CodeMirror.defineOption.
  • -
- -
- - - - diff --git a/gulliver/js/codemirror/index.html b/gulliver/js/codemirror/index.html index 9b250b63d..7097608d8 100644 --- a/gulliver/js/codemirror/index.html +++ b/gulliver/js/codemirror/index.html @@ -1,472 +1,192 @@ - - - - CodeMirror - - - - - -

{ } CodeMirror

+CodeMirror + -
- -
-/* In-browser code editing
-   made bearable */
-
+ + + + + + + + + + + + + + -
+
-

CodeMirror is a JavaScript component that - provides a code editor in the browser. When a mode is available for - the language you are coding in, it will color your code, and - optionally help with indentation.

+
+

CodeMirror is a versatile text editor + implemented in JavaScript for the browser. It is specialized for + editing code, and comes with a number of language modes and addons + that implement more advanced editing functionaly.

-

A rich programming API and a CSS - theming system are available for customizing CodeMirror to fit your - application, and extending it with new functionality.

+

A rich programming API and a + CSS theming system are + available for customizing CodeMirror to fit your application, and + extending it with new functionality.

+
-
+
+

This is CodeMirror

+
+
+ +
+ DOWNLOAD LATEST RELEASE +
version 3.21 (Release notes)
+ +
+ DONATE WITH PAYPAL +
+ or Bank, + Gittip, + Flattr
+
+ × + Bank: Rabobank
+ Country: Netherlands
+ SWIFT: RABONL2U
+ Account: 147850770
+ Name: Marijn Haverbeke
+ IBAN: NL26 RABO 0147 8507 70 +
+
+ + +
+
+
+ Purchase commercial support +
+
+
+
-

Supported modes:

+
+

Features

+ +
- +
+

Community

-
+

CodeMirror is an open-source project shared under + an MIT license. It is the editor used in + Light + Table, Adobe + Brackets, Google Apps + Script, Bitbucket, + and many other projects.

-

Usage demos:

+

Development and bug tracking happens + on github + (alternate git + repository). + Please read these + pointers before submitting a bug. Use pull requests to submit + patches. All contributions must be released under the same MIT + license that CodeMirror uses.

- - -

Real-world uses:

- - - -
- -

Getting the code

- -

All of CodeMirror is released under a MIT-style license. To get it, you can download - the latest - release or the current development - snapshot as zip files. To create a custom minified script file, - you can use the compression API.

- -

We use git for version control. - The main repository can be fetched in this way:

- -
git clone http://marijnhaverbeke.nl/git/codemirror
- -

CodeMirror can also be found on GitHub at marijnh/CodeMirror. - If you plan to hack on the code and contribute patches, the best way - to do it is to create a GitHub fork, and send pull requests.

- -

Documentation

- -

The manual is your first stop for - learning how to use this library. It starts with a quick explanation - of how to use the editor, and then describes the API in detail.

- -

For those who want to learn more about the code, there is - a series of - posts on CodeMirror on my blog, and the - old overview of the editor - internals. - The source code - itself is, for the most part, also very readable.

- -

Support and bug reports

- -

Community discussion, questions, and informal bug reporting is - done on - the CodeMirror - Google group. There is a separate - group, CodeMirror-announce, - which is lower-volume, and is only used for major announcements—new - versions and such. These will be cross-posted to both groups, so you - don't need to subscribe to both.

- -

Though bug reports through e-mail are responded to, the preferred - way to report bugs is to use - the GitHub - issue tracker. Before reporting a - bug, read these pointers. Also, - the issue tracker is for bugs, not requests for help.

- -

When none of these seem fitting, you can - simply e-mail the maintainer +

Discussion around the project is done on + a mailing list. + There is also + the codemirror-announce + list, which is only used for major announcements (such as new + versions). If needed, you can + contact the maintainer directly.

-

Supported browsers

+

A list of CodeMirror-related software that is not part of the + main distribution is maintained + on our + wiki. Feel free to add your project.

+ -

The following desktop browsers are able to run CodeMirror:

+
+

Browser support

+

The desktop versions of the following browsers, + in standards mode (HTML5 <!doctype html> + recommended) are supported:

+ + + + + + +
Firefoxversion 3 and up
Chromeany version
Safariversion 5.2 and up
Internet Explorerversion 8 and up
Operaversion 9 and up
+

Modern mobile browsers tend to partly work. Bug reports and + patches for mobile support are welcome, but the maintainer does not + have the time or budget to actually work on it himself.

+
-
    -
  • Firefox 3 or higher
  • -
  • Chrome, any version
  • -
  • Safari 5.2 or higher
  • -
  • Opera 9 or higher (with some key-handling problems on OS X)
  • -
  • Internet Explorer 8 or higher in standards mode
    - (Not quirks mode. But quasi-standards mode with a - transitional doctype is also flaky. <!doctype - html> is recommended.)
  • -
  • Internet Explorer 7 (standards mode) is usable, but buggy. It - has a z-index - bug that prevents CodeMirror from working properly.
  • -
- -

I am not actively testing against every new browser release, and - vendors have a habit of introducing bugs all the time, so I am - relying on the community to tell me when something breaks. - See here for information on how to contact - me.

- -

Mobile browsers mostly kind of work, but, because of limitations - and their fundamentally different UI assumptions, show a lot of - quirks that are hard to work around.

- -

Commercial support

- -

CodeMirror is developed and maintained by me, Marijn Haverbeke, - in my own time. If your company is getting value out of CodeMirror, - please consider purchasing a support contract.

- -
    -
  • You'll be funding further work on CodeMirror.
  • -
  • You ensure that you get a quick response when you have a - problem, even when I am otherwise busy.
  • -
- -

CodeMirror support contracts exist in two - forms—basic at €100 per month, - and premium at €500 per - month. Contact me for further - information.

- -
- -
- - Download the latest release - -

Support CodeMirror

- - - - - -

Reading material

- - - -

Releases

- -

20-03-2013: Version 3.11:

- - - -

21-02-2013: Version 3.1:

- - - - -

25-01-2013: Version 3.02:

- -

Single-bugfix release. Fixes a problem that - prevents CodeMirror instances from being garbage-collected after - they become unused.

- -

21-01-2013: Version 3.01:

- - - -

21-01-2013: Version 2.38:

- -

Integrate some bugfixes, enhancements to the vim keymap, and new - modes - (D, Sass, APL) - from the v3 branch.

- -

20-12-2012: Version 2.37:

- -
    -
  • New mode: SQL (will replace plsql and mysql modes).
  • -
  • Further work on the new VIM mode.
  • -
  • Fix Cmd/Ctrl keys on recent Operas on OS X.
  • -
  • Full list of patches.
  • -
- -

10-12-2012: Version 3.0:

- -

New major version. Only - partially backwards-compatible. See - the upgrading guide for more - information. Changes since release candidate 2:

- -
    -
  • Rewritten VIM mode.
  • -
  • Fix a few minor scrolling and sizing issues.
  • -
  • Work around Safari segfault when dragging.
  • -
  • Full list of patches.
  • -
- - -

20-11-2012: Version 3.0, release candidate 2:

- -
    -
  • New mode: HTTP.
  • -
  • Improved handling of selection anchor position.
  • -
  • Improve IE performance on longer lines.
  • -
  • Reduce gutter glitches during horiz. scrolling.
  • -
  • Add addKeyMap and removeKeyMap methods.
  • -
  • Rewrite formatting and closetag add-ons.
  • -
  • Full list of patches.
  • -
- -

20-11-2012: Version 2.36:

- - - -

20-11-2012: Version 3.0, release candidate 1:

- - - -

22-10-2012: Version 2.35:

- -
    -
  • New (sub) mode: TypeScript.
  • -
  • Don't overwrite (insert key) when pasting.
  • -
  • Fix several bugs in markText/undo interaction.
  • -
  • Better indentation of JavaScript code without semicolons.
  • -
  • Add defineInitHook function.
  • -
  • Full list of patches.
  • -
- -

Older releases...

- -
- -
 
- -
- - -
- - - + diff --git a/gulliver/js/codemirror/keymap/emacs.js b/gulliver/js/codemirror/keymap/emacs.js index fab3ab9fe..592238bc9 100644 --- a/gulliver/js/codemirror/keymap/emacs.js +++ b/gulliver/js/codemirror/keymap/emacs.js @@ -1,30 +1,398 @@ -// TODO number prefixes (function() { - // Really primitive kill-ring implementation. + "use strict"; + + var Pos = CodeMirror.Pos; + function posEq(a, b) { return a.line == b.line && a.ch == b.ch; } + + // Kill 'ring' + var killRing = []; function addToRing(str) { killRing.push(str); if (killRing.length > 50) killRing.shift(); } - function getFromRing() { return killRing[killRing.length - 1] || ""; } + function growRingTop(str) { + if (!killRing.length) return addToRing(str); + killRing[killRing.length - 1] += str; + } + function getFromRing(n) { return killRing[killRing.length - (n ? Math.min(n, 1) : 1)] || ""; } function popFromRing() { if (killRing.length > 1) killRing.pop(); return getFromRing(); } - CodeMirror.keyMap.emacs = { - "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, - "Ctrl-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, - "Ctrl-Alt-W": function(cm) {addToRing(cm.getSelection()); cm.replaceSelection("");}, - "Alt-W": function(cm) {addToRing(cm.getSelection());}, - "Ctrl-Y": function(cm) {cm.replaceSelection(getFromRing());}, + var lastKill = null; + + function kill(cm, from, to, mayGrow, text) { + if (text == null) text = cm.getRange(from, to); + + if (mayGrow && lastKill && lastKill.cm == cm && posEq(from, lastKill.pos) && cm.isClean(lastKill.gen)) + growRingTop(text); + else + addToRing(text); + cm.replaceRange("", from, to, "+delete"); + + if (mayGrow) lastKill = {cm: cm, pos: from, gen: cm.changeGeneration()}; + else lastKill = null; + } + + // Boundaries of various units + + function byChar(cm, pos, dir) { + return cm.findPosH(pos, dir, "char", true); + } + + function byWord(cm, pos, dir) { + return cm.findPosH(pos, dir, "word", true); + } + + function byLine(cm, pos, dir) { + return cm.findPosV(pos, dir, "line", cm.doc.sel.goalColumn); + } + + function byPage(cm, pos, dir) { + return cm.findPosV(pos, dir, "page", cm.doc.sel.goalColumn); + } + + function byParagraph(cm, pos, dir) { + var no = pos.line, line = cm.getLine(no); + var sawText = /\S/.test(dir < 0 ? line.slice(0, pos.ch) : line.slice(pos.ch)); + var fst = cm.firstLine(), lst = cm.lastLine(); + for (;;) { + no += dir; + if (no < fst || no > lst) + return cm.clipPos(Pos(no - dir, dir < 0 ? 0 : null)); + line = cm.getLine(no); + var hasText = /\S/.test(line); + if (hasText) sawText = true; + else if (sawText) return Pos(no, 0); + } + } + + function bySentence(cm, pos, dir) { + var line = pos.line, ch = pos.ch; + var text = cm.getLine(pos.line), sawWord = false; + for (;;) { + var next = text.charAt(ch + (dir < 0 ? -1 : 0)); + if (!next) { // End/beginning of line reached + if (line == (dir < 0 ? cm.firstLine() : cm.lastLine())) return Pos(line, ch); + text = cm.getLine(line + dir); + if (!/\S/.test(text)) return Pos(line, ch); + line += dir; + ch = dir < 0 ? text.length : 0; + continue; + } + if (sawWord && /[!?.]/.test(next)) return Pos(line, ch + (dir > 0 ? 1 : 0)); + if (!sawWord) sawWord = /\w/.test(next); + ch += dir; + } + } + + function byExpr(cm, pos, dir) { + var wrap; + if (cm.findMatchingBracket && (wrap = cm.findMatchingBracket(pos, true)) + && wrap.match && (wrap.forward ? 1 : -1) == dir) + return dir > 0 ? Pos(wrap.to.line, wrap.to.ch + 1) : wrap.to; + + for (var first = true;; first = false) { + var token = cm.getTokenAt(pos); + var after = Pos(pos.line, dir < 0 ? token.start : token.end); + if (first && dir > 0 && token.end == pos.ch || !/\w/.test(token.string)) { + var newPos = cm.findPosH(after, dir, "char"); + if (posEq(after, newPos)) return pos; + else pos = newPos; + } else { + return after; + } + } + } + + // Prefixes (only crudely supported) + + function getPrefix(cm, precise) { + var digits = cm.state.emacsPrefix; + if (!digits) return precise ? null : 1; + clearPrefix(cm); + return digits == "-" ? -1 : Number(digits); + } + + function repeated(cmd) { + var f = typeof cmd == "string" ? function(cm) { cm.execCommand(cmd); } : cmd; + return function(cm) { + var prefix = getPrefix(cm); + f(cm); + for (var i = 1; i < prefix; ++i) f(cm); + }; + } + + function findEnd(cm, by, dir) { + var pos = cm.getCursor(), prefix = getPrefix(cm); + if (prefix < 0) { dir = -dir; prefix = -prefix; } + for (var i = 0; i < prefix; ++i) { + var newPos = by(cm, pos, dir); + if (posEq(newPos, pos)) break; + pos = newPos; + } + return pos; + } + + function move(by, dir) { + var f = function(cm) { + cm.extendSelection(findEnd(cm, by, dir)); + }; + f.motion = true; + return f; + } + + function killTo(cm, by, dir) { + kill(cm, cm.getCursor(), findEnd(cm, by, dir), true); + } + + function addPrefix(cm, digit) { + if (cm.state.emacsPrefix) { + if (digit != "-") cm.state.emacsPrefix += digit; + return; + } + // Not active yet + cm.state.emacsPrefix = digit; + cm.on("keyHandled", maybeClearPrefix); + cm.on("inputRead", maybeDuplicateInput); + } + + var prefixPreservingKeys = {"Alt-G": true, "Ctrl-X": true, "Ctrl-Q": true, "Ctrl-U": true}; + + function maybeClearPrefix(cm, arg) { + if (!cm.state.emacsPrefixMap && !prefixPreservingKeys.hasOwnProperty(arg)) + clearPrefix(cm); + } + + function clearPrefix(cm) { + cm.state.emacsPrefix = null; + cm.off("keyHandled", maybeClearPrefix); + cm.off("inputRead", maybeDuplicateInput); + } + + function maybeDuplicateInput(cm, event) { + var dup = getPrefix(cm); + if (dup > 1 && event.origin == "+input") { + var one = event.text.join("\n"), txt = ""; + for (var i = 1; i < dup; ++i) txt += one; + cm.replaceSelection(txt, "end", "+input"); + } + } + + function addPrefixMap(cm) { + cm.state.emacsPrefixMap = true; + cm.addKeyMap(prefixMap); + cm.on("keyHandled", maybeRemovePrefixMap); + cm.on("inputRead", maybeRemovePrefixMap); + } + + function maybeRemovePrefixMap(cm, arg) { + if (typeof arg == "string" && (/^\d$/.test(arg) || arg == "Ctrl-U")) return; + cm.removeKeyMap(prefixMap); + cm.state.emacsPrefixMap = false; + cm.off("keyHandled", maybeRemovePrefixMap); + cm.off("inputRead", maybeRemovePrefixMap); + } + + // Utilities + + function setMark(cm) { + cm.setCursor(cm.getCursor()); + cm.setExtending(true); + cm.on("change", function() { cm.setExtending(false); }); + } + + function clearMark(cm) { + cm.setExtending(false); + cm.setCursor(cm.getCursor()); + } + + function getInput(cm, msg, f) { + if (cm.openDialog) + cm.openDialog(msg + ": ", f, {bottom: true}); + else + f(prompt(msg, "")); + } + + function operateOnWord(cm, op) { + var start = cm.getCursor(), end = cm.findPosH(start, 1, "word"); + cm.replaceRange(op(cm.getRange(start, end)), start, end); + cm.setCursor(end); + } + + function toEnclosingExpr(cm) { + var pos = cm.getCursor(), line = pos.line, ch = pos.ch; + var stack = []; + while (line >= cm.firstLine()) { + var text = cm.getLine(line); + for (var i = ch == null ? text.length : ch; i > 0;) { + var ch = text.charAt(--i); + if (ch == ")") + stack.push("("); + else if (ch == "]") + stack.push("["); + else if (ch == "}") + stack.push("{"); + else if (/[\(\{\[]/.test(ch) && (!stack.length || stack.pop() != ch)) + return cm.extendSelection(Pos(line, i)); + } + --line; ch = null; + } + } + + function quit(cm) { + cm.execCommand("clearSearch"); + clearMark(cm); + } + + // Actual keymap + + var keyMap = CodeMirror.keyMap.emacs = { + "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));}, + "Ctrl-K": repeated(function(cm) { + var start = cm.getCursor(), end = cm.clipPos(Pos(start.line)); + var text = cm.getRange(start, end); + if (!/\S/.test(text)) { + text += "\n"; + end = Pos(start.line + 1, 0); + } + kill(cm, start, end, true, text); + }), + "Alt-W": function(cm) { + addToRing(cm.getSelection()); + clearMark(cm); + }, + "Ctrl-Y": function(cm) { + var start = cm.getCursor(); + cm.replaceRange(getFromRing(getPrefix(cm)), start, start, "paste"); + cm.setSelection(start, cm.getCursor()); + }, "Alt-Y": function(cm) {cm.replaceSelection(popFromRing());}, - "Ctrl-/": "undo", "Shift-Ctrl--": "undo", "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", - "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": "clearSearch", "Shift-Alt-5": "replace", - "Ctrl-Z": "undo", "Cmd-Z": "undo", "Alt-/": "autocomplete", "Alt-V": "goPageUp", + + "Ctrl-Space": setMark, "Ctrl-Shift-2": setMark, + + "Ctrl-F": move(byChar, 1), "Ctrl-B": move(byChar, -1), + "Right": move(byChar, 1), "Left": move(byChar, -1), + "Ctrl-D": function(cm) { killTo(cm, byChar, 1); }, + "Delete": function(cm) { killTo(cm, byChar, 1); }, + "Ctrl-H": function(cm) { killTo(cm, byChar, -1); }, + "Backspace": function(cm) { killTo(cm, byChar, -1); }, + + "Alt-F": move(byWord, 1), "Alt-B": move(byWord, -1), + "Alt-D": function(cm) { killTo(cm, byWord, 1); }, + "Alt-Backspace": function(cm) { killTo(cm, byWord, -1); }, + + "Ctrl-N": move(byLine, 1), "Ctrl-P": move(byLine, -1), + "Down": move(byLine, 1), "Up": move(byLine, -1), + "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "End": "goLineEnd", "Home": "goLineStart", + + "Alt-V": move(byPage, -1), "Ctrl-V": move(byPage, 1), + "PageUp": move(byPage, -1), "PageDown": move(byPage, 1), + + "Ctrl-Up": move(byParagraph, -1), "Ctrl-Down": move(byParagraph, 1), + + "Alt-A": move(bySentence, -1), "Alt-E": move(bySentence, 1), + "Alt-K": function(cm) { killTo(cm, bySentence, 1); }, + + "Ctrl-Alt-K": function(cm) { killTo(cm, byExpr, 1); }, + "Ctrl-Alt-Backspace": function(cm) { killTo(cm, byExpr, -1); }, + "Ctrl-Alt-F": move(byExpr, 1), "Ctrl-Alt-B": move(byExpr, -1), + + "Shift-Ctrl-Alt-2": function(cm) { + cm.setSelection(findEnd(cm, byExpr, 1), cm.getCursor()); + }, + "Ctrl-Alt-T": function(cm) { + var leftStart = byExpr(cm, cm.getCursor(), -1), leftEnd = byExpr(cm, leftStart, 1); + var rightEnd = byExpr(cm, leftEnd, 1), rightStart = byExpr(cm, rightEnd, -1); + cm.replaceRange(cm.getRange(rightStart, rightEnd) + cm.getRange(leftEnd, rightStart) + + cm.getRange(leftStart, leftEnd), leftStart, rightEnd); + }, + "Ctrl-Alt-U": repeated(toEnclosingExpr), + + "Alt-Space": function(cm) { + var pos = cm.getCursor(), from = pos.ch, to = pos.ch, text = cm.getLine(pos.line); + while (from && /\s/.test(text.charAt(from - 1))) --from; + while (to < text.length && /\s/.test(text.charAt(to))) ++to; + cm.replaceRange(" ", Pos(pos.line, from), Pos(pos.line, to)); + }, + "Ctrl-O": repeated(function(cm) { cm.replaceSelection("\n", "start"); }), + "Ctrl-T": repeated(function(cm) { + var pos = cm.getCursor(); + if (pos.ch < cm.getLine(pos.line).length) pos = Pos(pos.line, pos.ch + 1); + var from = cm.findPosH(pos, -2, "char"); + var range = cm.getRange(from, pos); + if (range.length != 2) return; + cm.setSelection(from, pos); + cm.replaceSelection(range.charAt(1) + range.charAt(0), "end"); + }), + + "Alt-C": repeated(function(cm) { + operateOnWord(cm, function(w) { + var letter = w.search(/\w/); + if (letter == -1) return w; + return w.slice(0, letter) + w.charAt(letter).toUpperCase() + w.slice(letter + 1).toLowerCase(); + }); + }), + "Alt-U": repeated(function(cm) { + operateOnWord(cm, function(w) { return w.toUpperCase(); }); + }), + "Alt-L": repeated(function(cm) { + operateOnWord(cm, function(w) { return w.toLowerCase(); }); + }), + + "Alt-;": "toggleComment", + + "Ctrl-/": repeated("undo"), "Shift-Ctrl--": repeated("undo"), + "Ctrl-Z": repeated("undo"), "Cmd-Z": repeated("undo"), + "Shift-Alt-,": "goDocStart", "Shift-Alt-.": "goDocEnd", + "Ctrl-S": "findNext", "Ctrl-R": "findPrev", "Ctrl-G": quit, "Shift-Alt-5": "replace", + "Alt-/": "autocomplete", "Ctrl-J": "newlineAndIndent", "Enter": false, "Tab": "indentAuto", - fallthrough: ["basic", "emacsy"] + + "Alt-G": function(cm) {cm.setOption("keyMap", "emacs-Alt-G");}, + "Ctrl-X": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-X");}, + "Ctrl-Q": function(cm) {cm.setOption("keyMap", "emacs-Ctrl-Q");}, + "Ctrl-U": addPrefixMap }; CodeMirror.keyMap["emacs-Ctrl-X"] = { - "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": "undo", "K": "close", + "Tab": function(cm) { + cm.indentSelection(getPrefix(cm, true) || cm.getOption("indentUnit")); + }, + "Ctrl-X": function(cm) { + cm.setSelection(cm.getCursor("head"), cm.getCursor("anchor")); + }, + + "Ctrl-S": "save", "Ctrl-W": "save", "S": "saveAll", "F": "open", "U": repeated("undo"), "K": "close", + "Delete": function(cm) { kill(cm, cm.getCursor(), bySentence(cm, cm.getCursor(), 1), true); }, + auto: "emacs", nofallthrough: true, disableInput: true + }; + + CodeMirror.keyMap["emacs-Alt-G"] = { + "G": function(cm) { + var prefix = getPrefix(cm, true); + if (prefix != null && prefix > 0) return cm.setCursor(prefix - 1); + + getInput(cm, "Goto line", function(str) { + var num; + if (str && !isNaN(num = Number(str)) && num == num|0 && num > 0) + cm.setCursor(num - 1); + }); + }, + auto: "emacs", nofallthrough: true, disableInput: true + }; + + CodeMirror.keyMap["emacs-Ctrl-Q"] = { + "Tab": repeated("insertTab"), auto: "emacs", nofallthrough: true }; + + var prefixMap = {"Ctrl-G": clearPrefix}; + function regPrefix(d) { + prefixMap[d] = function(cm) { addPrefix(cm, d); }; + keyMap["Ctrl-" + d] = function(cm) { addPrefix(cm, d); }; + prefixPreservingKeys["Ctrl-" + d] = true; + } + for (var i = 0; i < 10; ++i) regPrefix(String(i)); + regPrefix("-"); })(); diff --git a/gulliver/js/codemirror/keymap/extra.js b/gulliver/js/codemirror/keymap/extra.js new file mode 100644 index 000000000..18dd5a979 --- /dev/null +++ b/gulliver/js/codemirror/keymap/extra.js @@ -0,0 +1,43 @@ +// A number of additional default bindings that are too obscure to +// include in the core codemirror.js file. + +(function() { + "use strict"; + + var Pos = CodeMirror.Pos; + + function moveLines(cm, start, end, dist) { + if (!dist || start > end) return 0; + + var from = cm.clipPos(Pos(start, 0)), to = cm.clipPos(Pos(end)); + var text = cm.getRange(from, to); + + if (start <= cm.firstLine()) + cm.replaceRange("", from, Pos(to.line + 1, 0)); + else + cm.replaceRange("", Pos(from.line - 1), to); + var target = from.line + dist; + if (target <= cm.firstLine()) { + cm.replaceRange(text + "\n", Pos(target, 0)); + return cm.firstLine() - from.line; + } else { + var targetPos = cm.clipPos(Pos(target - 1)); + cm.replaceRange("\n" + text, targetPos); + return targetPos.line + 1 - from.line; + } + } + + function moveSelectedLines(cm, dist) { + var head = cm.getCursor("head"), anchor = cm.getCursor("anchor"); + cm.operation(function() { + var moved = moveLines(cm, Math.min(head.line, anchor.line), Math.max(head.line, anchor.line), dist); + cm.setSelection(Pos(anchor.line + moved, anchor.ch), Pos(head.line + moved, head.ch)); + }); + } + + CodeMirror.commands.moveLinesUp = function(cm) { moveSelectedLines(cm, -1); }; + CodeMirror.commands.moveLinesDown = function(cm) { moveSelectedLines(cm, 1); }; + + CodeMirror.keyMap["default"]["Alt-Up"] = "moveLinesUp"; + CodeMirror.keyMap["default"]["Alt-Down"] = "moveLinesDown"; +})(); diff --git a/gulliver/js/codemirror/keymap/vim.js b/gulliver/js/codemirror/keymap/vim.js index 77b696d93..75b4e454d 100644 --- a/gulliver/js/codemirror/keymap/vim.js +++ b/gulliver/js/codemirror/keymap/vim.js @@ -40,6 +40,10 @@ * TODO: Implement the remaining special marks. They have more complex * behavior. * + * Events: + * 'vim-mode-change' - raised on the editor anytime the current mode changes, + * Event object: {mode: "visual", subMode: "linewise"} + * * Code structure: * 1. Default keymap * 2. Variable declarations and short basic helpers @@ -58,27 +62,39 @@ var defaultKeymap = [ // Key to key mapping. This goes first to make it possible to override // existing mappings. - { keys: ['Left'], type: 'keyToKey', toKeys: ['h'] }, - { keys: ['Right'], type: 'keyToKey', toKeys: ['l'] }, - { keys: ['Up'], type: 'keyToKey', toKeys: ['k'] }, - { keys: ['Down'], type: 'keyToKey', toKeys: ['j'] }, - { keys: ['Space'], type: 'keyToKey', toKeys: ['l'] }, - { keys: ['Backspace'], type: 'keyToKey', toKeys: ['h'] }, - { keys: ['Ctrl-Space'], type: 'keyToKey', toKeys: ['W'] }, - { keys: ['Ctrl-Backspace'], type: 'keyToKey', toKeys: ['B'] }, - { keys: ['Shift-Space'], type: 'keyToKey', toKeys: ['w'] }, - { keys: ['Shift-Backspace'], type: 'keyToKey', toKeys: ['b'] }, - { keys: ['Ctrl-n'], type: 'keyToKey', toKeys: ['j'] }, - { keys: ['Ctrl-p'], type: 'keyToKey', toKeys: ['k'] }, - { keys: ['Ctrl-['], type: 'keyToKey', toKeys: ['Esc'] }, - { keys: ['Ctrl-c'], type: 'keyToKey', toKeys: ['Esc'] }, - { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'] }, - { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'] }, - { keys: ['Home'], type: 'keyToKey', toKeys: ['0'] }, - { keys: ['End'], type: 'keyToKey', toKeys: ['$'] }, - { keys: ['PageUp'], type: 'keyToKey', toKeys: ['Ctrl-b'] }, - { keys: ['PageDown'], type: 'keyToKey', toKeys: ['Ctrl-f'] }, + { keys: [''], type: 'keyToKey', toKeys: ['h'] }, + { keys: [''], type: 'keyToKey', toKeys: ['l'] }, + { keys: [''], type: 'keyToKey', toKeys: ['k'] }, + { keys: [''], type: 'keyToKey', toKeys: ['j'] }, + { keys: [''], type: 'keyToKey', toKeys: ['l'] }, + { keys: [''], type: 'keyToKey', toKeys: ['h'] }, + { keys: [''], type: 'keyToKey', toKeys: ['W'] }, + { keys: [''], type: 'keyToKey', toKeys: ['B'] }, + { keys: [''], type: 'keyToKey', toKeys: ['w'] }, + { keys: [''], type: 'keyToKey', toKeys: ['b'] }, + { keys: [''], type: 'keyToKey', toKeys: ['j'] }, + { keys: [''], type: 'keyToKey', toKeys: ['k'] }, + { keys: ['C-['], type: 'keyToKey', toKeys: [''] }, + { keys: [''], type: 'keyToKey', toKeys: [''] }, + { keys: ['s'], type: 'keyToKey', toKeys: ['c', 'l'], context: 'normal' }, + { keys: ['s'], type: 'keyToKey', toKeys: ['x', 'i'], context: 'visual'}, + { keys: ['S'], type: 'keyToKey', toKeys: ['c', 'c'], context: 'normal' }, + { keys: ['S'], type: 'keyToKey', toKeys: ['d', 'c', 'c'], context: 'visual' }, + { keys: [''], type: 'keyToKey', toKeys: ['0'] }, + { keys: [''], type: 'keyToKey', toKeys: ['$'] }, + { keys: [''], type: 'keyToKey', toKeys: [''] }, + { keys: [''], type: 'keyToKey', toKeys: [''] }, + { keys: [''], type: 'keyToKey', toKeys: ['j', '^'], context: 'normal' }, // Motions + { keys: ['H'], type: 'motion', + motion: 'moveToTopLine', + motionArgs: { linewise: true, toJumplist: true }}, + { keys: ['M'], type: 'motion', + motion: 'moveToMiddleLine', + motionArgs: { linewise: true, toJumplist: true }}, + { keys: ['L'], type: 'motion', + motion: 'moveToBottomLine', + motionArgs: { linewise: true, toJumplist: true }}, { keys: ['h'], type: 'motion', motion: 'moveByCharacters', motionArgs: { forward: false }}, @@ -124,19 +140,25 @@ motionArgs: { forward: false, wordEnd: true, bigWord: true, inclusive: true }}, { keys: ['{'], type: 'motion', motion: 'moveByParagraph', - motionArgs: { forward: false }}, + motionArgs: { forward: false, toJumplist: true }}, { keys: ['}'], type: 'motion', motion: 'moveByParagraph', - motionArgs: { forward: true }}, - { keys: ['Ctrl-f'], type: 'motion', + motionArgs: { forward: true, toJumplist: true }}, + { keys: [''], type: 'motion', motion: 'moveByPage', motionArgs: { forward: true }}, - { keys: ['Ctrl-b'], type: 'motion', + { keys: [''], type: 'motion', motion: 'moveByPage', motionArgs: { forward: false }}, + { keys: [''], type: 'motion', + motion: 'moveByScroll', + motionArgs: { forward: true, explicitRepeat: true }}, + { keys: [''], type: 'motion', + motion: 'moveByScroll', + motionArgs: { forward: false, explicitRepeat: true }}, { keys: ['g', 'g'], type: 'motion', motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: false, explicitRepeat: true, linewise: true }}, + motionArgs: { forward: false, explicitRepeat: true, linewise: true, toJumplist: true }}, { keys: ['G'], type: 'motion', motion: 'moveToLineOrEdgeOfDocument', - motionArgs: { forward: true, explicitRepeat: true, linewise: true }}, + motionArgs: { forward: true, explicitRepeat: true, linewise: true, toJumplist: true }}, { keys: ['0'], type: 'motion', motion: 'moveToStartOfLine' }, { keys: ['^'], type: 'motion', motion: 'moveToFirstNonWhiteSpaceCharacter' }, @@ -154,7 +176,7 @@ motionArgs: { inclusive: true }}, { keys: ['%'], type: 'motion', motion: 'moveToMatchedSymbol', - motionArgs: { inclusive: true }}, + motionArgs: { inclusive: true, toJumplist: true }}, { keys: ['f', 'character'], type: 'motion', motion: 'moveToCharacter', motionArgs: { forward: true , inclusive: true }}, @@ -167,29 +189,40 @@ { keys: ['T', 'character'], type: 'motion', motion: 'moveTillCharacter', motionArgs: { forward: false }}, - { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark' }, - { keys: ['`', 'character'], type: 'motion', motion: 'goToMark' }, - { keys: [']', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, - { keys: ['[', '`',], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, + { keys: [';'], type: 'motion', motion: 'repeatLastCharacterSearch', + motionArgs: { forward: true }}, + { keys: [','], type: 'motion', motion: 'repeatLastCharacterSearch', + motionArgs: { forward: false }}, + { keys: ['\'', 'character'], type: 'motion', motion: 'goToMark', + motionArgs: {toJumplist: true}}, + { keys: ['`', 'character'], type: 'motion', motion: 'goToMark', + motionArgs: {toJumplist: true}}, + { keys: [']', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true } }, + { keys: ['[', '`'], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false } }, { keys: [']', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: true, linewise: true } }, { keys: ['[', '\''], type: 'motion', motion: 'jumpToMark', motionArgs: { forward: false, linewise: true } }, + { keys: [']', 'character'], type: 'motion', + motion: 'moveToSymbol', + motionArgs: { forward: true, toJumplist: true}}, + { keys: ['[', 'character'], type: 'motion', + motion: 'moveToSymbol', + motionArgs: { forward: false, toJumplist: true}}, { keys: ['|'], type: 'motion', motion: 'moveToColumn', motionArgs: { }}, // Operators { keys: ['d'], type: 'operator', operator: 'delete' }, { keys: ['y'], type: 'operator', operator: 'yank' }, - { keys: ['c'], type: 'operator', operator: 'change', - operatorArgs: { enterInsertMode: true } }, + { keys: ['c'], type: 'operator', operator: 'change' }, { keys: ['>'], type: 'operator', operator: 'indent', operatorArgs: { indentRight: true }}, { keys: ['<'], type: 'operator', operator: 'indent', operatorArgs: { indentRight: false }}, { keys: ['g', '~'], type: 'operator', operator: 'swapcase' }, { keys: ['n'], type: 'motion', motion: 'findNext', - motionArgs: { forward: true }}, + motionArgs: { forward: true, toJumplist: true }}, { keys: ['N'], type: 'motion', motion: 'findNext', - motionArgs: { forward: false }}, + motionArgs: { forward: false, toJumplist: true }}, // Operator-Motion dual commands { keys: ['x'], type: 'operatorMotion', operator: 'delete', motion: 'moveByCharacters', motionArgs: { forward: true }, @@ -204,36 +237,55 @@ motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }}, { keys: ['C'], type: 'operatorMotion', - operator: 'change', operatorArgs: { enterInsertMode: true }, + operator: 'change', motion: 'moveToEol', motionArgs: { inclusive: true }, operatorMotionArgs: { visualLine: true }}, - { keys: ['~'], type: 'operatorMotion', operator: 'swapcase', + { keys: ['~'], type: 'operatorMotion', + operator: 'swapcase', operatorArgs: { shouldMoveCursor: true }, motion: 'moveByCharacters', motionArgs: { forward: true }}, // Actions - { keys: ['a'], type: 'action', action: 'enterInsertMode', + { keys: [''], type: 'action', action: 'jumpListWalk', + actionArgs: { forward: true }}, + { keys: [''], type: 'action', action: 'jumpListWalk', + actionArgs: { forward: false }}, + { keys: [''], type: 'action', + action: 'scroll', + actionArgs: { forward: true, linewise: true }}, + { keys: [''], type: 'action', + action: 'scroll', + actionArgs: { forward: false, linewise: true }}, + { keys: ['a'], type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'charAfter' }}, - { keys: ['A'], type: 'action', action: 'enterInsertMode', + { keys: ['A'], type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }}, - { keys: ['i'], type: 'action', action: 'enterInsertMode' }, - { keys: ['I'], type: 'action', action: 'enterInsertMode', - motion: 'moveToFirstNonWhiteSpaceCharacter' }, + { keys: ['i'], type: 'action', action: 'enterInsertMode', isEdit: true, + actionArgs: { insertAt: 'inplace' }}, + { keys: ['I'], type: 'action', action: 'enterInsertMode', isEdit: true, + actionArgs: { insertAt: 'firstNonBlank' }}, { keys: ['o'], type: 'action', action: 'newLineAndEnterInsertMode', + isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }}, { keys: ['O'], type: 'action', action: 'newLineAndEnterInsertMode', + isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }}, { keys: ['v'], type: 'action', action: 'toggleVisualMode' }, { keys: ['V'], type: 'action', action: 'toggleVisualMode', actionArgs: { linewise: true }}, - { keys: ['J'], type: 'action', action: 'joinLines' }, - { keys: ['p'], type: 'action', action: 'paste', - actionArgs: { after: true }}, - { keys: ['P'], type: 'action', action: 'paste', - actionArgs: { after: false }}, - { keys: ['r', 'character'], type: 'action', action: 'replace' }, + { keys: ['J'], type: 'action', action: 'joinLines', isEdit: true }, + { keys: ['p'], type: 'action', action: 'paste', isEdit: true, + actionArgs: { after: true, isEdit: true }}, + { keys: ['P'], type: 'action', action: 'paste', isEdit: true, + actionArgs: { after: false, isEdit: true }}, + { keys: ['r', 'character'], type: 'action', action: 'replace', isEdit: true }, + { keys: ['@', 'character'], type: 'action', action: 'replayMacro' }, + { keys: ['q', 'character'], type: 'action', action: 'enterMacroRecordMode' }, + // Handle Replace-mode as a special case of insert mode. + { keys: ['R'], type: 'action', action: 'enterInsertMode', isEdit: true, + actionArgs: { replace: true }}, { keys: ['u'], type: 'action', action: 'undo' }, - { keys: ['Ctrl-r'], type: 'action', action: 'redo' }, + { keys: [''], type: 'action', action: 'redo' }, { keys: ['m', 'character'], type: 'action', action: 'setMark' }, - { keys: ['\"', 'character'], type: 'action', action: 'setRegister' }, + { keys: ['"', 'character'], type: 'action', action: 'setRegister' }, { keys: ['z', 'z'], type: 'action', action: 'scrollToCursor', actionArgs: { position: 'center' }}, { keys: ['z', '.'], type: 'action', action: 'scrollToCursor', @@ -241,7 +293,7 @@ motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: ['z', 't'], type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }}, - { keys: ['z', 'Enter'], type: 'action', action: 'scrollToCursor', + { keys: ['z', ''], type: 'action', action: 'scrollToCursor', actionArgs: { position: 'top' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: ['z', '-'], type: 'action', action: 'scrollToCursor', @@ -250,6 +302,12 @@ actionArgs: { position: 'bottom' }, motion: 'moveToFirstNonWhiteSpaceCharacter' }, { keys: ['.'], type: 'action', action: 'repeatLastEdit' }, + { keys: [''], type: 'action', action: 'incrementNumberToken', + isEdit: true, + actionArgs: {increase: true, backtrack: false}}, + { keys: [''], type: 'action', action: 'incrementNumberToken', + isEdit: true, + actionArgs: {increase: false, backtrack: false}}, // Text object motions { keys: ['a', 'character'], type: 'motion', motion: 'textObjectManipulation' }, @@ -258,21 +316,57 @@ motionArgs: { textObjectInner: true }}, // Search { keys: ['/'], type: 'search', - searchArgs: { forward: true, querySrc: 'prompt' }}, + searchArgs: { forward: true, querySrc: 'prompt', toJumplist: true }}, { keys: ['?'], type: 'search', - searchArgs: { forward: false, querySrc: 'prompt' }}, + searchArgs: { forward: false, querySrc: 'prompt', toJumplist: true }}, { keys: ['*'], type: 'search', - searchArgs: { forward: true, querySrc: 'wordUnderCursor' }}, + searchArgs: { forward: true, querySrc: 'wordUnderCursor', toJumplist: true }}, { keys: ['#'], type: 'search', - searchArgs: { forward: false, querySrc: 'wordUnderCursor' }}, + searchArgs: { forward: false, querySrc: 'wordUnderCursor', toJumplist: true }}, // Ex command { keys: [':'], type: 'ex' } ]; var Vim = function() { - var alphabetRegex = /[A-Za-z]/; + CodeMirror.defineOption('vimMode', false, function(cm, val) { + if (val) { + cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); + cm.on('beforeSelectionChange', beforeSelectionChange); + maybeInitVimState(cm); + CodeMirror.on(cm.getInputField(), 'paste', getOnPasteFn(cm)); + } else if (cm.state.vim) { + cm.setOption('keyMap', 'default'); + cm.setOption('disableInput', false); + cm.off('beforeSelectionChange', beforeSelectionChange); + CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm)); + cm.state.vim = null; + } + }); + function beforeSelectionChange(cm, cur) { + var vim = cm.state.vim; + if (vim.insertMode || vim.exMode) return; + + var head = cur.head; + if (head.ch && head.ch == cm.doc.getLine(head.line).length) { + head.ch--; + } + } + function getOnPasteFn(cm) { + var vim = cm.state.vim; + if (!vim.onPasteFn) { + vim.onPasteFn = function() { + if (!vim.insertMode) { + cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + actions.enterInsertMode(cm, {}, vim); + } + }; + } + return vim.onPasteFn; + } + var numberRegex = /[\d]/; - var whiteSpaceRegex = /\s/; var wordRegexp = [(/\w/), (/[^\w\s]/)], bigWordRegexp = [(/\S/)]; function makeKeyRange(start, size) { var keys = []; @@ -284,18 +378,12 @@ var upperCaseAlphabet = makeKeyRange(65, 26); var lowerCaseAlphabet = makeKeyRange(97, 26); var numbers = makeKeyRange(48, 10); - var SPECIAL_SYMBOLS = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;\"\''; - var specialSymbols = SPECIAL_SYMBOLS.split(''); + var specialSymbols = '~`!@#$%^&*()_-+=[{}]\\|/?.,<>:;"\''.split(''); var specialKeys = ['Left', 'Right', 'Up', 'Down', 'Space', 'Backspace', 'Esc', 'Home', 'End', 'PageUp', 'PageDown', 'Enter']; - var validMarks = upperCaseAlphabet.concat(lowerCaseAlphabet).concat( - numbers).concat(['<', '>']); - var validRegisters = upperCaseAlphabet.concat(lowerCaseAlphabet).concat( - numbers).concat('-\"'.split('')); + var validMarks = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['<', '>']); + var validRegisters = [].concat(upperCaseAlphabet, lowerCaseAlphabet, numbers, ['-', '"']); - function isAlphabet(k) { - return alphabetRegex.test(k); - } function isLine(cm, line) { return line >= cm.firstLine() && line <= cm.lastLine(); } @@ -311,18 +399,9 @@ function isUpperCase(k) { return (/^[A-Z]$/).test(k); } - function isAlphanumeric(k) { - return (/^[\w]$/).test(k); - } - function isWhiteSpace(k) { - return whiteSpaceRegex.test(k); - } function isWhiteSpaceString(k) { return (/^\s*$/).test(k); } - function inRangeInclusive(x, start, end) { - return x >= start && x <= end; - } function inArray(val, arr) { for (var i = 0; i < arr.length; i++) { if (arr[i] == val) { @@ -332,25 +411,108 @@ return false; } - // Global Vim state. Call getVimGlobalState to get and initialize. - var vimGlobalState; - function getVimGlobalState() { - if (!vimGlobalState) { - vimGlobalState = { - // The current search query. - searchQuery: null, - // Whether we are searching backwards. - searchIsReversed: false, - registerController: new RegisterController({}) - }; + var createCircularJumpList = function() { + var size = 100; + var pointer = -1; + var head = 0; + var tail = 0; + var buffer = new Array(size); + function add(cm, oldCur, newCur) { + var current = pointer % size; + var curMark = buffer[current]; + function useNextSlot(cursor) { + var next = ++pointer % size; + var trashMark = buffer[next]; + if (trashMark) { + trashMark.clear(); + } + buffer[next] = cm.setBookmark(cursor); + } + if (curMark) { + var markPos = curMark.find(); + // avoid recording redundant cursor position + if (markPos && !cursorEqual(markPos, oldCur)) { + useNextSlot(oldCur); + } + } else { + useNextSlot(oldCur); + } + useNextSlot(newCur); + head = pointer; + tail = pointer - size + 1; + if (tail < 0) { + tail = 0; + } } - return vimGlobalState; - } - function getVimState(cm) { - if (!cm.vimState) { + function move(cm, offset) { + pointer += offset; + if (pointer > head) { + pointer = head; + } else if (pointer < tail) { + pointer = tail; + } + var mark = buffer[(size + pointer) % size]; + // skip marks that are temporarily removed from text buffer + if (mark && !mark.find()) { + var inc = offset > 0 ? 1 : -1; + var newCur; + var oldCur = cm.getCursor(); + do { + pointer += inc; + mark = buffer[(size + pointer) % size]; + // skip marks that are the same as current position + if (mark && + (newCur = mark.find()) && + !cursorEqual(oldCur, newCur)) { + break; + } + } while (pointer < head && pointer > tail); + } + return mark; + } + return { + cachedCursor: undefined, //used for # and * jumps + add: add, + move: move + }; + }; + + var createMacroState = function() { + return { + macroKeyBuffer: [], + latestRegister: undefined, + inReplay: false, + lastInsertModeChanges: { + changes: [], // Change list + expectCursorActivityForChange: false // Set to true on change, false on cursorActivity. + }, + enteredMacroMode: undefined, + isMacroPlaying: false, + toggle: function(cm, registerName) { + if (this.enteredMacroMode) { //onExit + this.enteredMacroMode(); // close dialog + this.enteredMacroMode = undefined; + } else { //onEnter + this.latestRegister = registerName; + this.enteredMacroMode = cm.openDialog( + '(recording)['+registerName+']', null, {bottom:true}); + } + } + }; + }; + + + function maybeInitVimState(cm) { + if (!cm.state.vim) { // Store instance state in the CodeMirror object. - cm.vimState = { + cm.state.vim = { inputState: new InputState(), + // Vim's input state that triggered the last edit, used to repeat + // motions and operators with '.'. + lastEditInputState: undefined, + // Vim's action command before the last edit, used to repeat actions + // with '.' and insert mode repeat. + lastEditActionCommand: undefined, // When using jk for navigation, if you move from a longer line to a // shorter line, the cursor may clip to the end of the shorter line. // If j is pressed again and cursor goes to the next line, the @@ -363,12 +525,30 @@ // executed in between. lastMotion: null, marks: {}, + insertMode: false, + // Repeat count for changes made in insert mode, triggered by key + // sequences like 3,i. Only exists when insertMode is true. + insertModeRepeat: undefined, visualMode: false, // If we are in visual line mode. No effect if visualMode is false. visualLine: false }; } - return cm.vimState; + return cm.state.vim; + } + var vimGlobalState; + function resetVimGlobalState() { + vimGlobalState = { + // The current search query. + searchQuery: null, + // Whether we are searching backwards. + searchIsReversed: false, + jumpList: createCircularJumpList(), + macroModeState: createMacroState(), + // Recording latest f, t, F or T motion command. + lastChararacterSearch: {increment:0, forward:true, selectedCharacter:''}, + registerController: new RegisterController({}) + }; } var vimApi= { @@ -378,49 +558,59 @@ // Testing hook, though it might be useful to expose the register // controller anyways. getRegisterController: function() { - return getVimGlobalState().registerController; + return vimGlobalState.registerController; }, // Testing hook. - clearVimGlobalState_: function() { - vimGlobalState = null; + resetVimGlobalState_: resetVimGlobalState, + + // Testing hook. + getVimGlobalState_: function() { + return vimGlobalState; }, - map: function(lhs, rhs) { + + // Testing hook. + maybeInitVimState_: maybeInitVimState, + + InsertModeKey: InsertModeKey, + map: function(lhs, rhs, ctx) { // Add user defined key bindings. - exCommandDispatcher.map(lhs, rhs); + exCommandDispatcher.map(lhs, rhs, ctx); }, defineEx: function(name, prefix, func){ - if (name.indexOf(prefix) === 0) { - exCommands[name]=func; - exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; - }else throw new Error("(Vim.defineEx) \""+prefix+"\" is not a prefix of \""+name+"\", command not registered"); - }, - // Initializes vim state variable on the CodeMirror object. Should only be - // called lazily by handleKey or for testing. - maybeInitState: function(cm) { - getVimState(cm); + if (name.indexOf(prefix) !== 0) { + throw new Error('(Vim.defineEx) "'+prefix+'" is not a prefix of "'+name+'", command not registered'); + } + exCommands[name]=func; + exCommandDispatcher.commandMap_[prefix]={name:name, shortName:prefix, type:'api'}; }, // This is the outermost function called by CodeMirror, after keys have // been mapped to their Vim equivalents. handleKey: function(cm, key) { var command; - var vim = getVimState(cm); - if (key == 'Esc') { + var vim = maybeInitVimState(cm); + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.enteredMacroMode) { + if (key == 'q') { + actions.exitMacroRecordMode(); + vim.inputState = new InputState(); + return; + } + } + if (key == '') { // Clear input state and get back to normal mode. vim.inputState = new InputState(); if (vim.visualMode) { - exitVisualMode(cm, vim); + exitVisualMode(cm); } return; } - if (vim.visualMode && - cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) { - // The selection was cleared. Exit visual mode. - exitVisualMode(cm, vim); - } + // Enter visual mode when the mouse selects text. if (!vim.visualMode && !cursorEqual(cm.getCursor('head'), cm.getCursor('anchor'))) { vim.visualMode = true; vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); + cm.on('mousedown', exitVisualMode); } if (key != '0' || (key == '0' && vim.inputState.getRepeat() === 0)) { // Have to special case 0 since it's both a motion and a number. @@ -439,8 +629,14 @@ this.handleKey(cm, command.toKeys[i]); } } else { + if (macroModeState.enteredMacroMode) { + logKey(macroModeState, key); + } commandDispatcher.processCommand(cm, vim, command); } + }, + handleEx: function(cm, input) { + exCommandDispatcher.processCommand(cm, input); } }; @@ -520,10 +716,16 @@ */ function RegisterController(registers) { this.registers = registers; - this.unamedRegister = registers['\"'] = new Register(); + this.unamedRegister = registers['"'] = new Register(); } RegisterController.prototype = { pushText: function(registerName, operator, text, linewise) { + if (linewise && text.charAt(0) == '\n') { + text = text.slice(1) + '\n'; + } + if(linewise && text.charAt(text.length - 1) !== '\n'){ + text += '\n'; + } // Lowercase and uppercase registers refer to the same register. // Uppercase just means append. var register = this.isValidRegister(registerName) ? @@ -566,6 +768,9 @@ this.unamedRegister.set(text, linewise); } }, + setRegisterText: function(name, text, linewise) { + this.getRegister(name).set(text, linewise); + }, // Gets the register named @name. If one of @name doesn't already exist, // create it. If @name is invalid, return the unamedRegister. getRegister: function(name) { @@ -592,45 +797,80 @@ matchCommand: function(key, keyMap, vim) { var inputState = vim.inputState; var keys = inputState.keyBuffer.concat(key); + var matchedCommands = []; + var selectedCharacter; for (var i = 0; i < keyMap.length; i++) { var command = keyMap[i]; if (matchKeysPartial(keys, command.keys)) { - if (keys.length < command.keys.length) { - // Matches part of a multi-key command. Buffer and wait for next - // stroke. - inputState.keyBuffer.push(key); - return null; - } else { - if (inputState.operator && command.type == 'action') { - // Ignore matched action commands after an operator. Operators - // only operate on motions. This check is really for text - // objects since aW, a[ etcs conflicts with a. - continue; - } - // Matches whole comand. Return the command. - if (command.keys[keys.length - 1] == 'character') { - inputState.selectedCharacter = keys[keys.length - 1]; - if(inputState.selectedCharacter.length>1){ - switch(inputState.selectedCharacter){ - case "Enter": - inputState.selectedCharacter='\n'; - break; - case "Space": - inputState.selectedCharacter=' '; - break; - default: - continue; - } + if (inputState.operator && command.type == 'action') { + // Ignore matched action commands after an operator. Operators + // only operate on motions. This check is really for text + // objects since aW, a[ etcs conflicts with a. + continue; + } + // Match commands that take as an argument. + if (command.keys[keys.length - 1] == 'character') { + selectedCharacter = keys[keys.length - 1]; + if(selectedCharacter.length>1){ + switch(selectedCharacter){ + case '': + selectedCharacter='\n'; + break; + case '': + selectedCharacter=' '; + break; + default: + continue; } } - inputState.keyBuffer = []; - return command; } + // Add the command to the list of matched commands. Choose the best + // command later. + matchedCommands.push(command); } } - // Clear the buffer since there are no partial matches. - inputState.keyBuffer = []; - return null; + + // Returns the command if it is a full match, or null if not. + function getFullyMatchedCommandOrNull(command) { + if (keys.length < command.keys.length) { + // Matches part of a multi-key command. Buffer and wait for next + // stroke. + inputState.keyBuffer.push(key); + return null; + } else { + if (command.keys[keys.length - 1] == 'character') { + inputState.selectedCharacter = selectedCharacter; + } + // Clear the buffer since a full match was found. + inputState.keyBuffer = []; + return command; + } + } + + if (!matchedCommands.length) { + // Clear the buffer since there were no matches. + inputState.keyBuffer = []; + return null; + } else if (matchedCommands.length == 1) { + return getFullyMatchedCommandOrNull(matchedCommands[0]); + } else { + // Find the best match in the list of matchedCommands. + var context = vim.visualMode ? 'visual' : 'normal'; + var bestMatch; // Default to first in the list. + for (var i = 0; i < matchedCommands.length; i++) { + var current = matchedCommands[i]; + if (current.context == context) { + bestMatch = current; + break; + } else if (!bestMatch && !current.context) { + // Only set an imperfect match to best match if no best match is + // set and the imperfect match is not restricted to another + // context. + bestMatch = current; + } + } + return getFullyMatchedCommandOrNull(bestMatch); + } }, processCommand: function(cm, vim, command) { vim.inputState.repeatOverride = command.repeatOverride; @@ -721,7 +961,10 @@ actionArgs.repeatIsExplicit = repeatIsExplicit; actionArgs.registerName = inputState.registerName; vim.inputState = new InputState(); - vim.lastMotion = null, + vim.lastMotion = null; + if (command.isEdit) { + this.recordLastEdit(vim, inputState, command); + } actions[command.action](cm, actionArgs, vim); }, processSearch: function(cm, vim, command) { @@ -738,24 +981,24 @@ try { updateSearchQuery(cm, query, ignoreCase, smartCase); } catch (e) { - showConfirm(cm, 'Invalid regex: ' + regexPart); + showConfirm(cm, 'Invalid regex: ' + query); return; } commandDispatcher.processMotion(cm, vim, { type: 'motion', motion: 'findNext', - motionArgs: { forward: true } + motionArgs: { forward: true, toJumplist: command.searchArgs.toJumplist } }); } function onPromptClose(query) { cm.scrollTo(originalScrollPos.left, originalScrollPos.top); handleQuery(query, true /** ignoreCase */, true /** smartCase */); } - function onPromptKeyUp(e, query) { + function onPromptKeyUp(_e, query) { var parsedQuery; try { parsedQuery = updateSearchQuery(cm, query, - true /** ignoreCase */, true /** smartCase */) + true /** ignoreCase */, true /** smartCase */); } catch (e) { // Swallow bad regexes for incremental search. } @@ -766,7 +1009,7 @@ cm.scrollTo(originalScrollPos.left, originalScrollPos.top); } } - function onPromptKeyDown(e, query, close) { + function onPromptKeyDown(e, _query, close) { var keyName = CodeMirror.keyName(e); if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { updateSearchQuery(cm, originalQuery); @@ -803,22 +1046,30 @@ return; } var query = cm.getLine(word.start.line).substring(word.start.ch, - word.end.ch + 1); + word.end.ch); if (isKeyword) { query = '\\b' + query + '\\b'; } else { query = escapeRegex(query); } + + // cachedCursor is used to save the old position of the cursor + // when * or # causes vim to seek for the nearest word and shift + // the cursor before entering the motion. + vimGlobalState.jumpList.cachedCursor = cm.getCursor(); cm.setCursor(word.start); + handleQuery(query, true /** ignoreCase */, false /** smartCase */); break; } }, processEx: function(cm, vim, command) { function onPromptClose(input) { + // Give the prompt some time to close so that if processCommand shows + // an error, the elements don't overlap. exCommandDispatcher.processCommand(cm, input); } - function onPromptKeyDown(e, input, close) { + function onPromptKeyDown(e, _input, close) { var keyName = CodeMirror.keyName(e); if (keyName == 'Esc' || keyName == 'Ctrl-C' || keyName == 'Ctrl-[') { CodeMirror.e_stop(e); @@ -857,7 +1108,7 @@ var curEnd; var repeat; if (operator) { - this.recordLastEdit(cm, vim, inputState); + this.recordLastEdit(vim, inputState); } if (inputState.repeatOverride !== undefined) { // If repeatOverride is specified, that takes precedence over the @@ -886,6 +1137,17 @@ if (!motionResult) { return; } + if (motionArgs.toJumplist) { + var jumpList = vimGlobalState.jumpList; + // if the current motion is # or *, use cachedCursor + var cachedCursor = jumpList.cachedCursor; + if (cachedCursor) { + recordJumpPosition(cm, cachedCursor, motionResult); + delete jumpList.cachedCursor; + } else { + recordJumpPosition(cm, curOriginal, motionResult); + } + } if (motionResult instanceof Array) { curStart = motionResult[0]; curEnd = motionResult[1]; @@ -915,6 +1177,11 @@ if (vim.visualLine) { if (cursorIsBefore(selectionStart, selectionEnd)) { selectionStart.ch = 0; + + var lastLine = cm.lastLine(); + if (selectionEnd.line > lastLine) { + selectionEnd.line = lastLine; + } selectionEnd.ch = lineLength(cm, selectionEnd.line); } else { selectionEnd.ch = 0; @@ -961,7 +1228,7 @@ // Expand selection to entire line. expandSelectionToLine(cm, curStart, curEnd); } else if (motionArgs.forward) { - // Clip to trailing newlines only if we the motion goes forward. + // Clip to trailing newlines only if the motion goes forward. clipToLine(cm, curStart, curEnd); } operatorArgs.registerName = registerName; @@ -970,15 +1237,17 @@ operators[operator](cm, operatorArgs, vim, curStart, curEnd, curOriginal); if (vim.visualMode) { - exitVisualMode(cm, vim); - } - if (operatorArgs.enterInsertMode) { - actions.enterInsertMode(cm); + exitVisualMode(cm); } } }, - recordLastEdit: function(cm, vim, inputState) { - vim.lastEdit = inputState; + recordLastEdit: function(vim, inputState, actionCommand) { + var macroModeState = vimGlobalState.macroModeState; + if (macroModeState.inReplay) { return; } + vim.lastEditInputState = inputState; + vim.lastEditActionCommand = actionCommand; + macroModeState.lastInsertModeChanges.changes = []; + macroModeState.lastInsertModeChanges.expectCursorActivityForChange = false; } }; @@ -988,13 +1257,26 @@ */ // All of the functions below return Cursor objects. var motions = { + moveToTopLine: function(cm, motionArgs) { + var line = getUserVisibleLines(cm).top + motionArgs.repeat -1; + return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) }; + }, + moveToMiddleLine: function(cm) { + var range = getUserVisibleLines(cm); + var line = Math.floor((range.top + range.bottom) * 0.5); + return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) }; + }, + moveToBottomLine: function(cm, motionArgs) { + var line = getUserVisibleLines(cm).bottom - motionArgs.repeat +1; + return { line: line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(line)) }; + }, expandToLine: function(cm, motionArgs) { // Expands forward to end of line, and then to next line if repeat is // >1. Does not handle backward motion! var cur = cm.getCursor(); return { line: cur.line + motionArgs.repeat - 1, ch: Infinity }; }, - findNext: function(cm, motionArgs, vim) { + findNext: function(cm, motionArgs) { var state = getSearchState(cm); var query = state.getQuery(); if (!query) { @@ -1006,7 +1288,7 @@ highlightSearchMatches(cm, query); return findNext(cm, prev/** prev */, query, motionArgs.repeat); }, - goToMark: function(cm, motionArgs, vim) { + goToMark: function(_cm, motionArgs, vim) { var mark = vim.marks[motionArgs.selectedCharacter]; if (mark) { return mark.find(); @@ -1014,7 +1296,7 @@ return null; }, jumpToMark: function(cm, motionArgs, vim) { - var best = cm.getCursor(); + var best = cm.getCursor(); for (var i = 0; i < motionArgs.repeat; i++) { var cursor = best; for (var key in vim.marks) { @@ -1023,7 +1305,7 @@ } var mark = vim.marks[key].find(); var isWrongDirection = (motionArgs.forward) ? - cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark) + cursorIsBefore(mark, cursor) : cursorIsBefore(cursor, mark); if (isWrongDirection) { continue; @@ -1033,7 +1315,7 @@ } var equal = cursorEqual(cursor, best); - var between = (motionArgs.forward) ? + var between = (motionArgs.forward) ? cusrorIsBetween(cursor, mark, best) : cusrorIsBetween(best, mark, cursor); @@ -1068,6 +1350,7 @@ switch (vim.lastMotion) { case this.moveByLines: case this.moveByDisplayLines: + case this.moveByScroll: case this.moveToColumn: case this.moveToEol: endCh = vim.lastHPos; @@ -1077,30 +1360,46 @@ } var repeat = motionArgs.repeat+(motionArgs.repeatOffset||0); var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat; - if (line < cm.firstLine() || line > cm.lastLine() ) { - return null; + var first = cm.firstLine(); + var last = cm.lastLine(); + // Vim cancels linewise motions that start on an edge and move beyond + // that edge. It does not cancel motions that do not start on an edge. + if ((line < first && cur.line == first) || + (line > last && cur.line == last)) { + return; } if(motionArgs.toFirstChar){ endCh=findFirstNonWhiteSpaceCharacter(cm.getLine(line)); vim.lastHPos = endCh; } - vim.lastHSPos = cm.charCoords({line:line, ch:endCh},"div").left; + vim.lastHSPos = cm.charCoords({line:line, ch:endCh},'div').left; return { line: line, ch: endCh }; }, moveByDisplayLines: function(cm, motionArgs, vim) { var cur = cm.getCursor(); switch (vim.lastMotion) { case this.moveByDisplayLines: + case this.moveByScroll: case this.moveByLines: case this.moveToColumn: case this.moveToEol: break; default: - vim.lastHSPos = cm.charCoords(cur,"div").left; + vim.lastHSPos = cm.charCoords(cur,'div').left; } var repeat = motionArgs.repeat; - var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),"line",vim.lastHSPos); - if(res.hitSide)return null; + var res=cm.findPosV(cur,(motionArgs.forward ? repeat : -repeat),'line',vim.lastHSPos); + if (res.hitSide) { + if (motionArgs.forward) { + var lastCharCoords = cm.charCoords(res, 'div'); + var goalCoords = { top: lastCharCoords.top + 8, left: vim.lastHSPos }; + var res = cm.coordsChar(goalCoords, 'div'); + } else { + var resCoords = cm.charCoords({ line: cm.firstLine(), ch: 0}, 'div'); + resCoords.left = vim.lastHSPos; + res = cm.coordsChar(resCoords, 'div'); + } + } vim.lastHPos = res.ch; return res; }, @@ -1131,6 +1430,23 @@ } return { line: line, ch: 0 }; }, + moveByScroll: function(cm, motionArgs, vim) { + var scrollbox = cm.getScrollInfo(); + var curEnd = null; + var repeat = motionArgs.repeat; + if (!repeat) { + repeat = scrollbox.clientHeight / (2 * cm.defaultTextHeight()); + } + var orig = cm.charCoords(cm.getCursor(), 'local'); + motionArgs.repeat = repeat; + var curEnd = motions.moveByDisplayLines(cm, motionArgs, vim); + if (!curEnd) { + return null; + } + var dest = cm.charCoords(curEnd, 'local'); + cm.scrollTo(null, scrollbox.top + dest.top - orig.top); + return curEnd; + }, moveByWords: function(cm, motionArgs) { return moveToWord(cm, motionArgs.repeat, !!motionArgs.forward, !!motionArgs.wordEnd, !!motionArgs.bigWord); @@ -1139,30 +1455,37 @@ var repeat = motionArgs.repeat; var curEnd = moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter); - if(!curEnd)return cm.getCursor(); var increment = motionArgs.forward ? -1 : 1; + recordLastCharacterSearch(increment, motionArgs); + if(!curEnd)return cm.getCursor(); curEnd.ch += increment; return curEnd; }, moveToCharacter: function(cm, motionArgs) { var repeat = motionArgs.repeat; + recordLastCharacterSearch(0, motionArgs); return moveToCharacter(cm, repeat, motionArgs.forward, motionArgs.selectedCharacter) || cm.getCursor(); }, + moveToSymbol: function(cm, motionArgs) { + var repeat = motionArgs.repeat; + return findSymbol(cm, repeat, motionArgs.forward, + motionArgs.selectedCharacter) || cm.getCursor(); + }, moveToColumn: function(cm, motionArgs, vim) { var repeat = motionArgs.repeat; // repeat is equivalent to which column we want to move to! vim.lastHPos = repeat - 1; - vim.lastHSPos = cm.charCoords(cm.getCursor(),"div").left; + vim.lastHSPos = cm.charCoords(cm.getCursor(),'div').left; return moveToColumn(cm, repeat); }, moveToEol: function(cm, motionArgs, vim) { var cur = cm.getCursor(); vim.lastHPos = Infinity; - var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity } + var retval={ line: cur.line + motionArgs.repeat - 1, ch: Infinity }; var end=cm.clipPos(retval); end.ch--; - vim.lastHSPos = cm.charCoords(end,"div").left; + vim.lastHSPos = cm.charCoords(end,'div').left; return retval; }, moveToFirstNonWhiteSpaceCharacter: function(cm) { @@ -1172,11 +1495,26 @@ return { line: cursor.line, ch: findFirstNonWhiteSpaceCharacter(cm.getLine(cursor.line)) }; }, - moveToMatchedSymbol: function(cm, motionArgs) { + moveToMatchedSymbol: function(cm) { var cursor = cm.getCursor(); - var symbol = cm.getLine(cursor.line).charAt(cursor.ch); - if (isMatchableSymbol(symbol)) { - return findMatchedSymbol(cm, cm.getCursor(), motionArgs.symbol); + var line = cursor.line; + var ch = cursor.ch; + var lineText = cm.getLine(line); + var symbol; + var startContext = cm.getTokenAt(cursor).type; + var startCtxLevel = getContextLevel(startContext); + do { + symbol = lineText.charAt(ch++); + if (symbol && isMatchableSymbol(symbol)) { + var endContext = cm.getTokenAt({line:line, ch:ch}).type; + var endCtxLevel = getContextLevel(endContext); + if (startCtxLevel >= endCtxLevel) { + break; + } + } + } while (symbol); + if (symbol) { + return findMatchedSymbol(cm, {line:line, ch:ch-1}, symbol); } else { return cursor; } @@ -1209,35 +1547,60 @@ var start = tmp.start; var end = tmp.end; return [start, end]; + }, + repeatLastCharacterSearch: function(cm, motionArgs) { + var lastSearch = vimGlobalState.lastChararacterSearch; + var repeat = motionArgs.repeat; + var forward = motionArgs.forward === lastSearch.forward; + var increment = (lastSearch.increment ? 1 : 0) * (forward ? -1 : 1); + cm.moveH(-increment, 'char'); + motionArgs.inclusive = forward ? true : false; + var curEnd = moveToCharacter(cm, repeat, forward, lastSearch.selectedCharacter); + if (!curEnd) { + cm.moveH(increment, 'char'); + return cm.getCursor(); + } + curEnd.ch += increment; + return curEnd; } }; var operators = { - change: function(cm, operatorArgs, vim, curStart, curEnd) { - getVimGlobalState().registerController.pushText( + change: function(cm, operatorArgs, _vim, curStart, curEnd) { + vimGlobalState.registerController.pushText( operatorArgs.registerName, 'change', cm.getRange(curStart, curEnd), operatorArgs.linewise); if (operatorArgs.linewise) { - // Delete starting at the first nonwhitespace character of the first - // line, instead of from the start of the first line. This way we get - // an indent when we get into insert mode. This behavior isn't quite - // correct because we should treat this as a completely new line, and - // indent should be whatever codemirror thinks is the right indent. - // But cm.indentLine doesn't seem work on empty lines. - // TODO: Fix the above. - curStart.ch = - findFirstNonWhiteSpaceCharacter(cm.getLine(curStart.line)); - // Insert an additional newline so that insert mode can start there. - // curEnd should be on the first character of the new line. - cm.replaceRange('\n', curStart, curEnd); + // Push the next line back down, if there is a next line. + var replacement = curEnd.line > cm.lastLine() ? '' : '\n'; + cm.replaceRange(replacement, curStart, curEnd); + cm.indentLine(curStart.line, 'smart'); + // null ch so setCursor moves to end of line. + curStart.ch = null; } else { + // Exclude trailing whitespace if the range is not all whitespace. + var text = cm.getRange(curStart, curEnd); + if (!isWhiteSpaceString(text)) { + var match = (/\s+$/).exec(text); + if (match) { + curEnd = offsetCursor(curEnd, 0, - match[0].length); + } + } cm.replaceRange('', curStart, curEnd); } + actions.enterInsertMode(cm, {}, cm.state.vim); cm.setCursor(curStart); }, // delete is a javascript keyword. - 'delete': function(cm, operatorArgs, vim, curStart, curEnd) { - getVimGlobalState().registerController.pushText( + 'delete': function(cm, operatorArgs, _vim, curStart, curEnd) { + // If the ending line is past the last line, inclusive, instead of + // including the trailing \n, include the \n before the starting line + if (operatorArgs.linewise && + curEnd.line > cm.lastLine() && curStart.line > cm.firstLine()) { + curStart.line--; + curStart.ch = lineLength(cm, curStart.line); + } + vimGlobalState.registerController.pushText( operatorArgs.registerName, 'delete', cm.getRange(curStart, curEnd), operatorArgs.linewise); cm.replaceRange('', curStart, curEnd); @@ -1267,7 +1630,7 @@ cm.setCursor(curStart); cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); }, - swapcase: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) { + swapcase: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { var toSwap = cm.getRange(curStart, curEnd); var swapped = ''; for (var i = 0; i < toSwap.length; i++) { @@ -1276,10 +1639,12 @@ character.toUpperCase(); } cm.replaceRange(swapped, curStart, curEnd); - cm.setCursor(curOriginal); + if (!operatorArgs.shouldMoveCursor) { + cm.setCursor(curOriginal); + } }, - yank: function(cm, operatorArgs, vim, curStart, curEnd, curOriginal) { - getVimGlobalState().registerController.pushText( + yank: function(cm, operatorArgs, _vim, curStart, curEnd, curOriginal) { + vimGlobalState.registerController.pushText( operatorArgs.registerName, 'yank', cm.getRange(curStart, curEnd), operatorArgs.linewise); cm.setCursor(curOriginal); @@ -1287,26 +1652,100 @@ }; var actions = { + jumpListWalk: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat; + var forward = actionArgs.forward; + var jumpList = vimGlobalState.jumpList; + + var mark = jumpList.move(cm, forward ? repeat : -repeat); + var markPos = mark ? mark.find() : undefined; + markPos = markPos ? markPos : cm.getCursor(); + cm.setCursor(markPos); + }, + scroll: function(cm, actionArgs, vim) { + if (vim.visualMode) { + return; + } + var repeat = actionArgs.repeat || 1; + var lineHeight = cm.defaultTextHeight(); + var top = cm.getScrollInfo().top; + var delta = lineHeight * repeat; + var newPos = actionArgs.forward ? top + delta : top - delta; + var cursor = cm.getCursor(); + var cursorCoords = cm.charCoords(cursor, 'local'); + if (actionArgs.forward) { + if (newPos > cursorCoords.top) { + cursor.line += (newPos - cursorCoords.top) / lineHeight; + cursor.line = Math.ceil(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo(null, cursorCoords.top); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } else { + var newBottom = newPos + cm.getScrollInfo().clientHeight; + if (newBottom < cursorCoords.bottom) { + cursor.line -= (cursorCoords.bottom - newBottom) / lineHeight; + cursor.line = Math.floor(cursor.line); + cm.setCursor(cursor); + cursorCoords = cm.charCoords(cursor, 'local'); + cm.scrollTo( + null, cursorCoords.bottom - cm.getScrollInfo().clientHeight); + } else { + // Cursor stays within bounds. Just reposition the scroll window. + cm.scrollTo(null, newPos); + } + } + }, scrollToCursor: function(cm, actionArgs) { var lineNum = cm.getCursor().line; - var heightProp = window.getComputedStyle(cm.getScrollerElement()). - getPropertyValue('height'); - var height = parseInt(heightProp); - var y = cm.charCoords({line: lineNum, ch: 0}, "local").top; - var halfHeight = parseInt(height) / 2; + var charCoords = cm.charCoords({line: lineNum, ch: 0}, 'local'); + var height = cm.getScrollInfo().clientHeight; + var y = charCoords.top; + var lineHeight = charCoords.bottom - y; switch (actionArgs.position) { - case 'center': y = y - (height / 2) + 10; - break; - case 'bottom': y = y - height; - break; - case 'top': break; + case 'center': y = y - (height / 2) + lineHeight; + break; + case 'bottom': y = y - height + lineHeight*1.4; + break; + case 'top': y = y + lineHeight*0.4; + break; } cm.scrollTo(null, y); - // The calculations are slightly off, use scrollIntoView to nudge the - // view into the right place. - cm.scrollIntoView(); }, - enterInsertMode: function(cm, actionArgs) { + replayMacro: function(cm, actionArgs) { + var registerName = actionArgs.selectedCharacter; + var repeat = actionArgs.repeat; + var macroModeState = vimGlobalState.macroModeState; + if (registerName == '@') { + registerName = macroModeState.latestRegister; + } + var keyBuffer = parseRegisterToKeyBuffer(macroModeState, registerName); + while(repeat--){ + executeMacroKeyBuffer(cm, macroModeState, keyBuffer); + } + }, + exitMacroRecordMode: function() { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.toggle(); + parseKeyBufferToRegister(macroModeState.latestRegister, + macroModeState.macroKeyBuffer); + }, + enterMacroRecordMode: function(cm, actionArgs) { + var macroModeState = vimGlobalState.macroModeState; + var registerName = actionArgs.selectedCharacter; + macroModeState.toggle(cm, registerName); + emptyMacroKeyBuffer(macroModeState); + }, + enterInsertMode: function(cm, actionArgs, vim) { + if (cm.getOption('readOnly')) { return; } + vim.insertMode = true; + vim.insertModeRepeat = actionArgs && actionArgs.repeat || 1; var insertAt = (actionArgs) ? actionArgs.insertAt : null; if (insertAt == 'eol') { var cursor = cm.getCursor(); @@ -1314,8 +1753,26 @@ cm.setCursor(cursor); } else if (insertAt == 'charAfter') { cm.setCursor(offsetCursor(cm.getCursor(), 0, 1)); + } else if (insertAt == 'firstNonBlank') { + cm.setCursor(motions.moveToFirstNonWhiteSpaceCharacter(cm)); } cm.setOption('keyMap', 'vim-insert'); + cm.setOption('disableInput', false); + if (actionArgs && actionArgs.replace) { + // Handle Replace-mode as a special case of insert mode. + cm.toggleOverwrite(true); + cm.setOption('keyMap', 'vim-replace'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "replace"}); + } else { + cm.setOption('keyMap', 'vim-insert'); + CodeMirror.signal(cm, "vim-mode-change", {mode: "insert"}); + } + if (!vimGlobalState.macroModeState.inReplay) { + // Only record if not replaying. + cm.on('change', onChange); + cm.on('cursorActivity', onCursorActivity); + CodeMirror.on(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } }, toggleVisualMode: function(cm, actionArgs, vim) { var repeat = actionArgs.repeat; @@ -1325,6 +1782,7 @@ // equal to the repeat times the size of the previous visual // operation. if (!vim.visualMode) { + cm.on('mousedown', exitVisualMode); vim.visualMode = true; vim.visualLine = !!actionArgs.linewise; if (vim.visualLine) { @@ -1349,6 +1807,7 @@ } else { cm.setSelection(curStart, curEnd); } + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: vim.visualLine ? "linewise" : ""}); } else { curStart = cm.getCursor('anchor'); curEnd = cm.getCursor('head'); @@ -1361,12 +1820,14 @@ curEnd.ch = cursorIsBefore(curStart, curEnd) ? lineLength(cm, curEnd.line) : 0; cm.setSelection(curStart, curEnd); + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual", subMode: "linewise"}); } else if (vim.visualLine && !actionArgs.linewise) { // v pressed in linewise visual mode. Switch to characterwise visual // mode instead of exiting visual mode. vim.visualLine = false; + CodeMirror.signal(cm, "vim-mode-change", {mode: "visual"}); } else { - exitVisualMode(cm, vim); + exitVisualMode(cm); } } updateMark(cm, vim, '<', cursorIsBefore(curStart, curEnd) ? curStart @@ -1401,7 +1862,8 @@ cm.setCursor(curFinalPos); }); }, - newLineAndEnterInsertMode: function(cm, actionArgs) { + newLineAndEnterInsertMode: function(cm, actionArgs, vim) { + vim.insertMode = true; var insertAt = cm.getCursor(); if (insertAt.line === cm.firstLine() && !actionArgs.after) { // Special case for inserting newline before start of document. @@ -1416,11 +1878,11 @@ CodeMirror.commands.newlineAndIndent; newlineFn(cm); } - this.enterInsertMode(cm); + this.enterInsertMode(cm, { repeat: actionArgs.repeat }, vim); }, - paste: function(cm, actionArgs, vim) { + paste: function(cm, actionArgs) { var cur = cm.getCursor(); - var register = getVimGlobalState().registerController.getRegister( + var register = vimGlobalState.registerController.getRegister( actionArgs.registerName); if (!register.text) { return; @@ -1461,12 +1923,15 @@ cm.setCursor(curPosFinal); }, undo: function(cm, actionArgs) { - repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); + cm.operation(function() { + repeatFn(cm, CodeMirror.commands.undo, actionArgs.repeat)(); + cm.setCursor(cm.getCursor('anchor')); + }); }, redo: function(cm, actionArgs) { repeatFn(cm, CodeMirror.commands.redo, actionArgs.repeat)(); }, - setRegister: function(cm, actionArgs, vim) { + setRegister: function(_cm, actionArgs, vim) { vim.inputState.registerName = actionArgs.selectedCharacter; }, setMark: function(cm, actionArgs, vim) { @@ -1503,24 +1968,50 @@ cm.replaceRange(replaceWithStr, curStart, curEnd); if(vim.visualMode){ cm.setCursor(curStart); - exitVisualMode(cm,vim); + exitVisualMode(cm); }else{ cm.setCursor(offsetCursor(curEnd, 0, -1)); } } }, - repeatLastEdit: function(cm, actionArgs, vim) { - // TODO: Make this repeat insert mode changes. - var lastEdit = vim.lastEdit; - if (lastEdit) { - if (actionArgs.repeat && actionArgs.repeatIsExplicit) { - vim.lastEdit.repeatOverride = actionArgs.repeat; - } - var currentInputState = vim.inputState; - vim.inputState = vim.lastEdit; - commandDispatcher.evalInput(cm, vim); - vim.inputState = currentInputState; + incrementNumberToken: function(cm, actionArgs) { + var cur = cm.getCursor(); + var lineStr = cm.getLine(cur.line); + var re = /-?\d+/g; + var match; + var start; + var end; + var numberStr; + var token; + while ((match = re.exec(lineStr)) !== null) { + token = match[0]; + start = match.index; + end = start + token.length; + if(cur.ch < end)break; } + if(!actionArgs.backtrack && (end <= cur.ch))return; + if (token) { + var increment = actionArgs.increase ? 1 : -1; + var number = parseInt(token) + (increment * actionArgs.repeat); + var from = {ch:start, line:cur.line}; + var to = {ch:end, line:cur.line}; + numberStr = number.toString(); + cm.replaceRange(numberStr, from, to); + } else { + return; + } + cm.setCursor({line: cur.line, ch: start + numberStr.length - 1}); + }, + repeatLastEdit: function(cm, actionArgs, vim) { + var lastEditInputState = vim.lastEditInputState; + if (!lastEditInputState) { return; } + var repeat = actionArgs.repeat; + if (repeat && actionArgs.repeatIsExplicit) { + vim.lastEditInputState.repeatOverride = repeat; + } else { + repeat = vim.lastEditInputState.repeatOverride || repeat; + } + repeatLastEdit(cm, vim, repeat, false /** repeatForInsert */); } }; @@ -1549,7 +2040,7 @@ '\'': function(cm, inclusive) { return findBeginningAndEnd(cm, "'", inclusive); }, - '\"': function(cm, inclusive) { + '"': function(cm, inclusive) { return findBeginningAndEnd(cm, '"', inclusive); } }; @@ -1569,14 +2060,6 @@ var ch = Math.min(Math.max(0, cur.ch), maxCh); return { line: line, ch: ch }; } - // Merge arguments in place, for overriding arguments. - function mergeArgs(to, from) { - for (var prop in from) { - if (from.hasOwnProperty(prop)) { - to[prop] = from[prop]; - } - } - } function copyArgs(args) { var ret = {}; for (var prop in args) { @@ -1589,17 +2072,6 @@ function offsetCursor(cur, offsetLine, offsetCh) { return { line: cur.line + offsetLine, ch: cur.ch + offsetCh }; } - function arrayEq(a1, a2) { - if (a1.length != a2.length) { - return false; - } - for (var i = 0; i < a1.length; i++) { - if (a1[i] != a2[i]) { - return false; - } - } - return true; - } function matchKeysPartial(pressed, mapped) { for (var i = 0; i < pressed.length; i++) { // 'character' means any character. For mark, register commads, etc. @@ -1609,14 +2081,6 @@ } return true; } - function arrayIsSubsetFromBeginning(small, big) { - for (var i = 0; i < small.length; i++) { - if (small[i] != big[i]) { - return false; - } - } - return true; - } function repeatFn(cm, fn, repeat) { return function() { for (var i = 0; i < repeat; i++) { @@ -1633,7 +2097,8 @@ function cursorIsBefore(cur1, cur2) { if (cur1.line < cur2.line) { return true; - } else if (cur1.line == cur2.line && cur1.ch < cur2.ch) { + } + if (cur1.line == cur2.line && cur1.ch < cur2.ch) { return true; } return false; @@ -1648,20 +2113,21 @@ return cm.getLine(lineNum).length; } function reverse(s){ - return s.split("").reverse().join(""); + return s.split('').reverse().join(''); } function trim(s) { if (s.trim) { return s.trim(); - } else { - return s.replace(/^\s+|\s+$/g, ''); } + return s.replace(/^\s+|\s+$/g, ''); } function escapeRegex(s) { - return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, "\\$1"); + return s.replace(/([.?*+$\[\]\/\\(){}|\-])/g, '\\$1'); } - function exitVisualMode(cm, vim) { + function exitVisualMode(cm) { + cm.off('mousedown', exitVisualMode); + var vim = cm.state.vim; vim.visualMode = false; vim.visualLine = false; var selectionStart = cm.getCursor('anchor'); @@ -1672,6 +2138,7 @@ // it's not supposed to be. cm.setCursor(clipCursorToContent(cm, selectionEnd)); } + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); } // Remove any trailing newlines from the selection. For @@ -1680,15 +2147,34 @@ // caret to the first character of the next line. function clipToLine(cm, curStart, curEnd) { var selection = cm.getRange(curStart, curEnd); - var lines = selection.split('\n'); - if (lines.length > 1 && isWhiteSpaceString(lines.pop())) { - curEnd.line--; - curEnd.ch = lineLength(cm, curEnd.line); + // Only clip if the selection ends with trailing newline + whitespace + if (/\n\s*$/.test(selection)) { + var lines = selection.split('\n'); + // We know this is all whitepsace. + lines.pop(); + + // Cases: + // 1. Last word is an empty line - do not clip the trailing '\n' + // 2. Last word is not an empty line - clip the trailing '\n' + var line; + // Find the line containing the last word, and clip all whitespace up + // to it. + for (var line = lines.pop(); lines.length > 0 && line && isWhiteSpaceString(line); line = lines.pop()) { + curEnd.line--; + curEnd.ch = 0; + } + // If the last word is not an empty line, clip an additional newline + if (line) { + curEnd.line--; + curEnd.ch = lineLength(cm, curEnd.line); + } else { + curEnd.ch = 0; + } } } // Expand the selection to line ends. - function expandSelectionToLine(cm, curStart, curEnd) { + function expandSelectionToLine(_cm, curStart, curEnd) { curStart.ch = 0; curEnd.ch = 0; curEnd.line++; @@ -1702,7 +2188,7 @@ return firstNonWS == -1 ? text.length : firstNonWS; } - function expandWordUnderCursor(cm, inclusive, forward, bigWord, noSymbol) { + function expandWordUnderCursor(cm, inclusive, _forward, bigWord, noSymbol) { var cur = cm.getCursor(); var line = cm.getLine(cur.line); var idx = cur.ch; @@ -1737,21 +2223,166 @@ var wordAfterRegex = matchRegex.exec(textAfterIdx); var wordStart = idx; - var wordEnd = idx + wordAfterRegex[0].length - 1; + var wordEnd = idx + wordAfterRegex[0].length; // TODO: Find a better way to do this. It will be slow on very long lines. - var wordBeforeRegex = matchRegex.exec(reverse(textBeforeIdx)); + var revTextBeforeIdx = reverse(textBeforeIdx); + var wordBeforeRegex = matchRegex.exec(revTextBeforeIdx); if (wordBeforeRegex) { wordStart -= wordBeforeRegex[0].length; } if (inclusive) { - wordEnd++; + // If present, trim all whitespace after word. + // Otherwise, trim all whitespace before word. + var textAfterWordEnd = line.substring(wordEnd); + var whitespacesAfterWord = textAfterWordEnd.match(/^\s*/)[0].length; + if (whitespacesAfterWord > 0) { + wordEnd += whitespacesAfterWord; + } else { + var revTrim = revTextBeforeIdx.length - wordStart; + var textBeforeWordStart = revTextBeforeIdx.substring(revTrim); + var whitespacesBeforeWord = textBeforeWordStart.match(/^\s*/)[0].length; + wordStart -= whitespacesBeforeWord; + } } return { start: { line: cur.line, ch: wordStart }, end: { line: cur.line, ch: wordEnd }}; } + function recordJumpPosition(cm, oldCur, newCur) { + if(!cursorEqual(oldCur, newCur)) { + vimGlobalState.jumpList.add(cm, oldCur, newCur); + } + } + + function recordLastCharacterSearch(increment, args) { + vimGlobalState.lastChararacterSearch.increment = increment; + vimGlobalState.lastChararacterSearch.forward = args.forward; + vimGlobalState.lastChararacterSearch.selectedCharacter = args.selectedCharacter; + } + + var symbolToMode = { + '(': 'bracket', ')': 'bracket', '{': 'bracket', '}': 'bracket', + '[': 'section', ']': 'section', + '*': 'comment', '/': 'comment', + 'm': 'method', 'M': 'method', + '#': 'preprocess' + }; + var findSymbolModes = { + bracket: { + isComplete: function(state) { + if (state.nextCh === state.symb) { + state.depth++; + if(state.depth >= 1)return true; + } else if (state.nextCh === state.reverseSymb) { + state.depth--; + } + return false; + } + }, + section: { + init: function(state) { + state.curMoveThrough = true; + state.symb = (state.forward ? ']' : '[') === state.symb ? '{' : '}'; + }, + isComplete: function(state) { + return state.index === 0 && state.nextCh === state.symb; + } + }, + comment: { + isComplete: function(state) { + var found = state.lastCh === '*' && state.nextCh === '/'; + state.lastCh = state.nextCh; + return found; + } + }, + // TODO: The original Vim implementation only operates on level 1 and 2. + // The current implementation doesn't check for code block level and + // therefore it operates on any levels. + method: { + init: function(state) { + state.symb = (state.symb === 'm' ? '{' : '}'); + state.reverseSymb = state.symb === '{' ? '}' : '{'; + }, + isComplete: function(state) { + if(state.nextCh === state.symb)return true; + return false; + } + }, + preprocess: { + init: function(state) { + state.index = 0; + }, + isComplete: function(state) { + if (state.nextCh === '#') { + var token = state.lineText.match(/#(\w+)/)[1]; + if (token === 'endif') { + if (state.forward && state.depth === 0) { + return true; + } + state.depth++; + } else if (token === 'if') { + if (!state.forward && state.depth === 0) { + return true; + } + state.depth--; + } + if(token === 'else' && state.depth === 0)return true; + } + return false; + } + } + }; + function findSymbol(cm, repeat, forward, symb) { + var cur = cm.getCursor(); + var increment = forward ? 1 : -1; + var endLine = forward ? cm.lineCount() : -1; + var curCh = cur.ch; + var line = cur.line; + var lineText = cm.getLine(line); + var state = { + lineText: lineText, + nextCh: lineText.charAt(curCh), + lastCh: null, + index: curCh, + symb: symb, + reverseSymb: (forward ? { ')': '(', '}': '{' } : { '(': ')', '{': '}' })[symb], + forward: forward, + depth: 0, + curMoveThrough: false + }; + var mode = symbolToMode[symb]; + if(!mode)return cur; + var init = findSymbolModes[mode].init; + var isComplete = findSymbolModes[mode].isComplete; + if(init)init(state); + while (line !== endLine && repeat) { + state.index += increment; + state.nextCh = state.lineText.charAt(state.index); + if (!state.nextCh) { + line += increment; + state.lineText = cm.getLine(line) || ''; + if (increment > 0) { + state.index = 0; + } else { + var lineLen = state.lineText.length; + state.index = (lineLen > 0) ? (lineLen-1) : 0; + } + state.nextCh = state.lineText.charAt(state.index); + } + if (isComplete(state)) { + cur.line = line; + cur.ch = state.index; + repeat--; + } + } + if (state.nextCh || state.curMoveThrough) { + return { line: line, ch: state.index }; + } + return cur; + } + /* * Returns the boundaries of the next word. If the cursor in the middle of * the word, then returns the boundaries of the current word, starting at @@ -1764,18 +2395,31 @@ * backward. * @param {boolean} bigWord True if punctuation count as part of the word. * False if only [a-zA-Z0-9] characters count as part of the word. + * @param {boolean} emptyLineIsWord True if empty lines should be treated + * as words. * @return {Object{from:number, to:number, line: number}} The boundaries of * the word, or null if there are no more words. */ - // TODO: Treat empty lines (with no whitespace) as words. - function findWord(cm, cur, forward, bigWord) { + function findWord(cm, cur, forward, bigWord, emptyLineIsWord) { var lineNum = cur.line; var pos = cur.ch; var line = cm.getLine(lineNum); var dir = forward ? 1 : -1; var regexps = bigWord ? bigWordRegexp : wordRegexp; + if (emptyLineIsWord && line == '') { + lineNum += dir; + line = cm.getLine(lineNum); + if (!isLine(cm, lineNum)) { + return null; + } + pos = (forward) ? 0 : line.length; + } + while (true) { + if (emptyLineIsWord && line == '') { + return { from: 0, to: 0, line: lineNum }; + } var stop = (dir > 0) ? line.length : -1; var wordStart = stop, wordEnd = stop; // Find bounds of next word. @@ -1815,7 +2459,7 @@ pos = (dir > 0) ? 0 : line.length; } // Should never get here. - throw 'The impossible happened.'; + throw new Error('The impossible happened.'); } /** @@ -1831,56 +2475,48 @@ */ function moveToWord(cm, repeat, forward, wordEnd, bigWord) { var cur = cm.getCursor(); - for (var i = 0; i < repeat; i++) { - var startCh = cur.ch, startLine = cur.line, word; - var movedToNextWord = false; - while (!movedToNextWord) { - // Search and advance. - word = findWord(cm, cur, forward, bigWord); - movedToNextWord = true; - if (word) { - // Move to the word we just found. If by moving to the word we end - // up in the same spot, then move an extra character and search - // again. - cur.line = word.line; - if (forward && wordEnd) { - // 'e' - cur.ch = word.to - 1; - } else if (forward && !wordEnd) { - // 'w' - if (inRangeInclusive(cur.ch, word.from, word.to) && - word.line == startLine) { - // Still on the same word. Go to the next one. - movedToNextWord = false; - cur.ch = word.to - 1; - } else { - cur.ch = word.from; - } - } else if (!forward && wordEnd) { - // 'ge' - if (inRangeInclusive(cur.ch, word.from, word.to) && - word.line == startLine) { - // still on the same word. Go to the next one. - movedToNextWord = false; - cur.ch = word.from; - } else { - cur.ch = word.to; - } - } else if (!forward && !wordEnd) { - // 'b' - cur.ch = word.from; - } - } else { - // No more words to be found. Move to the end. - if (forward) { - return { line: cur.line, ch: lineLength(cm, cur.line) }; - } else { - return { line: cur.line, ch: 0 }; - } - } - } + var curStart = copyCursor(cur); + var words = []; + if (forward && !wordEnd || !forward && wordEnd) { + repeat++; + } + // For 'e', empty lines are not considered words, go figure. + var emptyLineIsWord = !(forward && wordEnd); + for (var i = 0; i < repeat; i++) { + var word = findWord(cm, cur, forward, bigWord, emptyLineIsWord); + if (!word) { + var eodCh = lineLength(cm, cm.lastLine()); + words.push(forward + ? {line: cm.lastLine(), from: eodCh, to: eodCh} + : {line: 0, from: 0, to: 0}); + break; + } + words.push(word); + cur = {line: word.line, ch: forward ? (word.to - 1) : word.from}; + } + var shortCircuit = words.length != repeat; + var firstWord = words[0]; + var lastWord = words.pop(); + if (forward && !wordEnd) { + // w + if (!shortCircuit && (firstWord.from != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return {line: lastWord.line, ch: lastWord.from}; + } else if (forward && wordEnd) { + return {line: lastWord.line, ch: lastWord.to - 1}; + } else if (!forward && wordEnd) { + // ge + if (!shortCircuit && (firstWord.to != curStart.ch || firstWord.line != curStart.line)) { + // We did not start in the middle of a word. Discard the extra word at the end. + lastWord = words.pop(); + } + return {line: lastWord.line, ch: lastWord.to}; + } else { + // b + return {line: lastWord.line, ch: lastWord.from}; } - return cur; } function moveToCharacter(cm, repeat, forward, character) { @@ -1936,12 +2572,17 @@ return idx; } + function getContextLevel(ctx) { + return (ctx === 'string' || ctx === 'comment') ? 1 : 0; + } + function findMatchedSymbol(cm, cur, symb) { var line = cur.line; - symb = symb ? symb : cm.getLine(line).charAt(cur.ch); + var ch = cur.ch; + symb = symb ? symb : cm.getLine(line).charAt(ch); - // Are we at the opening or closing char - var forwards = inArray(symb, ['(', '[', '{']); + var symbContext = cm.getTokenAt({line:line, ch:ch+1}).type; + var symbCtxLevel = getContextLevel(symbContext); var reverseSymb = ({ '(': ')', ')': '(', @@ -1956,12 +2597,13 @@ // set our increment to move forward (+1) or backwards (-1) // depending on which bracket we're matching var increment = ({'(': 1, '{': 1, '[': 1})[symb] || -1; - var depth = 1, nextCh = symb, index = cur.ch, lineText = cm.getLine(line); + var endLine = increment === 1 ? cm.lineCount() : -1; + var depth = 1, nextCh = symb, index = ch, lineText = cm.getLine(line); // Simple search for closing paren--just count openings and closings till // we find our match // TODO: use info from CodeMirror to ignore closing brackets in comments // and quotes, etc. - while (nextCh && depth > 0) { + while (line !== endLine && depth > 0) { index += increment; nextCh = lineText.charAt(index); if (!nextCh) { @@ -1975,10 +2617,14 @@ } nextCh = lineText.charAt(index); } - if (nextCh === symb) { - depth++; - } else if (nextCh === reverseSymb) { - depth--; + var revSymbContext = cm.getTokenAt({line:line, ch:index+1}).type; + var revSymbCtxLevel = getContextLevel(revSymbContext); + if (symbCtxLevel >= revSymbCtxLevel) { + if (nextCh === symb) { + depth++; + } else if (nextCh === reverseSymb) { + depth--; + } } } @@ -1999,16 +2645,6 @@ return { start: start, end: end }; } - function regexLastIndexOf(string, pattern, startIndex) { - for (var i = !startIndex ? string.length : startIndex; - i >= 0; --i) { - if (pattern.test(string.charAt(i))) { - return i; - } - } - return -1; - } - // Takes in a symbol and a cursor and tries to simulate text objects that // have identical opening and closing symbols // TODO support across multiple lines @@ -2074,10 +2710,10 @@ function SearchState() {} SearchState.prototype = { getQuery: function() { - return getVimGlobalState().query; + return vimGlobalState.query; }, setQuery: function(query) { - getVimGlobalState().query = query; + vimGlobalState.query = query; }, getOverlay: function() { return this.searchOverlay; @@ -2086,14 +2722,14 @@ this.searchOverlay = overlay; }, isReversed: function() { - return getVimGlobalState().isReversed; + return vimGlobalState.isReversed; }, setReversed: function(reversed) { - getVimGlobalState().isReversed = reversed; + vimGlobalState.isReversed = reversed; } }; function getSearchState(cm) { - var vim = getVimState(cm); + var vim = cm.state.vim; return vim.searchState_ || (vim.searchState_ = new SearchState()); } function dialog(cm, template, shortText, onClose, options) { @@ -2102,9 +2738,10 @@ onKeyDown: options.onKeyDown, onKeyUp: options.onKeyUp }); } else { - callback(prompt(shortText, "")); + onClose(prompt(shortText, '')); } } + function findUnescapedSlashes(str) { var escapeNextChar = false; var slashes = []; @@ -2127,7 +2764,7 @@ * then both ignoreCase and smartCase are ignored, and 'i' will be passed * through to the Regex object. */ - function parseQuery(cm, query, ignoreCase, smartCase) { + function parseQuery(query, ignoreCase, smartCase) { // Check if the query is already a regex. if (query instanceof RegExp) { return query; } // First try to extract regex + flags from the input. If no flags found, @@ -2156,10 +2793,9 @@ return regexp; } function showConfirm(cm, text) { - if (cm.openConfirm) { - cm.openConfirm('' + text + - ' ', function() {}, - {bottom: true}); + if (cm.openNotification) { + cm.openNotification('' + text + '', + {bottom: true, duration: 5000}); } else { alert(text); } @@ -2186,16 +2822,16 @@ } function regexEqual(r1, r2) { if (r1 instanceof RegExp && r2 instanceof RegExp) { - var props = ["global", "multiline", "ignoreCase", "source"]; + var props = ['global', 'multiline', 'ignoreCase', 'source']; for (var i = 0; i < props.length; i++) { var prop = props[i]; if (r1[prop] !== r2[prop]) { - return(false); + return false; } } - return(true); + return true; } - return(false); + return false; } // Returns true if the query is valid. function updateSearchQuery(cm, rawQuery, ignoreCase, smartCase) { @@ -2203,7 +2839,7 @@ return; } var state = getSearchState(cm); - var query = parseQuery(cm, rawQuery, !!ignoreCase, !!smartCase); + var query = parseQuery(rawQuery, !!ignoreCase, !!smartCase); if (!query) { return; } @@ -2229,7 +2865,7 @@ if (match[0].length == 0) { // Matched empty string, skip to next. stream.next(); - return; + return 'searching'; } if (!stream.sol()) { // Backtrack 1 to match \b @@ -2240,7 +2876,7 @@ } } stream.match(query); - return "searching"; + return 'searching'; } while (!stream.eol()) { stream.next(); @@ -2265,12 +2901,11 @@ if (repeat === undefined) { repeat = 1; } return cm.operation(function() { var pos = cm.getCursor(); - if (!prev) { - pos.ch += 1; - } var cursor = cm.getSearchCursor(query, pos); for (var i = 0; i < repeat; i++) { - if (!cursor.find(prev)) { + var found = cursor.find(prev); + if (i == 0 && found && cursorEqual(cursor.from(), pos)) { found = cursor.find(prev); } + if (!found) { // SearchCursor may have returned null because it hit EOF, wrap // around and try again. cursor = cm.getSearchCursor(query, @@ -2281,7 +2916,8 @@ } } return cursor.from(); - });} + }); + } function clearSearchHighlight(cm) { cm.removeOverlay(getSearchState(cm).getOverlay()); getSearchState(cm).setOverlay(null); @@ -2312,25 +2948,41 @@ } } } + function getUserVisibleLines(cm) { + var scrollInfo = cm.getScrollInfo(); + var occludeToleranceTop = 6; + var occludeToleranceBottom = 10; + var from = cm.coordsChar({left:0, top: occludeToleranceTop + scrollInfo.top}, 'local'); + var bottomY = scrollInfo.clientHeight - occludeToleranceBottom + scrollInfo.top; + var to = cm.coordsChar({left:0, top: bottomY}, 'local'); + return {top: from.line, bottom: to.line}; + } // Ex command handling // Care must be taken when adding to the default Ex command map. For any // pair of commands that have a shared prefix, at least one of their // shortNames must not match the prefix of the other command. var defaultExCommandMap = [ - { name: 'map', type: 'builtIn' }, - { name: 'write', shortName: 'w', type: 'builtIn' }, - { name: 'undo', shortName: 'u', type: 'builtIn' }, - { name: 'redo', shortName: 'red', type: 'builtIn' }, - { name: 'substitute', shortName: 's', type: 'builtIn'}, - { name: 'nohlsearch', shortName: 'noh', type: 'builtIn'}, - { name: 'delmarks', shortName: 'delm', type: 'builtin'} + { name: 'map' }, + { name: 'nmap', shortName: 'nm' }, + { name: 'vmap', shortName: 'vm' }, + { name: 'write', shortName: 'w' }, + { name: 'undo', shortName: 'u' }, + { name: 'redo', shortName: 'red' }, + { name: 'sort', shortName: 'sor' }, + { name: 'substitute', shortName: 's' }, + { name: 'nohlsearch', shortName: 'noh' }, + { name: 'delmarks', shortName: 'delm' } ]; Vim.ExCommandDispatcher = function() { this.buildCommandMap_(); }; Vim.ExCommandDispatcher.prototype = { processCommand: function(cm, input) { + var vim = cm.state.vim; + if (vim.visualMode) { + exitVisualMode(cm); + } var inputStream = new CodeMirror.StringStream(input); var params = {}; params.input = input; @@ -2354,7 +3006,7 @@ if (command.type == 'exToKey') { // Handle Ex to Key mapping. for (var i = 0; i < command.toKeys.length; i++) { - vim.handleKey(cm, command.toKeys[i]); + CodeMirror.Vim.handleKey(cm, command.toKeys[i]); } return; } else if (command.type == 'exToEx') { @@ -2368,7 +3020,12 @@ showConfirm(cm, 'Not an editor command ":' + input + '"'); return; } - exCommands[commandName](cm, params); + try { + exCommands[commandName](cm, params); + } catch(e) { + showConfirm(cm, e); + throw e; + } }, parseInput_: function(cm, inputStream, result) { inputStream.eatWhile(':'); @@ -2404,16 +3061,14 @@ case '$': return cm.lastLine(); case '\'': - var mark = getVimState(cm).marks[inputStream.next()]; + var mark = cm.state.vim.marks[inputStream.next()]; if (mark && mark.find()) { return mark.find().line; - } else { - throw "Mark not set"; } - break; + throw new Error('Mark not set'); default: inputStream.backUp(1); - return cm.getCursor().line; + return undefined; } }, parseCommandArgs_: function(inputStream, params, command) { @@ -2452,8 +3107,9 @@ this.commandMap_[key] = command; } }, - map: function(lhs, rhs) { + map: function(lhs, rhs, ctx) { if (lhs != ':' && lhs.charAt(0) == ':') { + if (ctx) { throw Error('Mode not supported for ex mappings'); } var commandName = lhs.substring(1); if (rhs != ':' && rhs.charAt(0) == ':') { // Ex to Ex mapping @@ -2473,17 +3129,21 @@ } else { if (rhs != ':' && rhs.charAt(0) == ':') { // Key to Ex mapping. - defaultKeymap.unshift({ + var mapping = { keys: parseKeyString(lhs), type: 'keyToEx', - exArgs: { input: rhs.substring(1) }}); + exArgs: { input: rhs.substring(1) }}; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); } else { // Key to key mapping - defaultKeymap.unshift({ + var mapping = { keys: parseKeyString(lhs), type: 'keyToKey', toKeys: parseKeyString(rhs) - }); + }; + if (ctx) { mapping.context = ctx; } + defaultKeymap.unshift(mapping); } } } @@ -2492,47 +3152,20 @@ // Converts a key string sequence of the form abd into Vim's // keymap representation. function parseKeyString(str) { - var idx = 0; + var key, match; var keys = []; - while (idx < str.length) { - if (str.charAt(idx) != '<') { - keys.push(str.charAt(idx)); - idx++; - continue; - } - // Vim key notation here means desktop Vim key-notation. - // See :help key-notation in desktop Vim. - var vimKeyNotationStart = ++idx; - while (str.charAt(idx++) != '>') {} - var vimKeyNotation = str.substring(vimKeyNotationStart, idx - 1); - var mod=''; - var match = (/^C-(.+)$/).exec(vimKeyNotation); - if (match) { - mod='Ctrl-'; - vimKeyNotation=match[1]; - } - var key; - switch (vimKeyNotation) { - case 'BS': - key = 'Backspace'; - break; - case 'CR': - key = 'Enter'; - break; - case 'Del': - key = 'Delete'; - break; - default: - key = vimKeyNotation; - break; - } - keys.push(mod + key); + while (str) { + match = (/<\w+-.+?>|<\w+>|./).exec(str); + if(match === null)break; + key = match[0]; + str = str.substring(match.index + key.length); + keys.push(key); } return keys; } var exCommands = { - map: function(cm, params) { + map: function(cm, params, ctx) { var mapArgs = params.args; if (!mapArgs || mapArgs.length < 2) { if (cm) { @@ -2540,17 +3173,98 @@ } return; } - exCommandDispatcher.map(mapArgs[0], mapArgs[1], cm); + exCommandDispatcher.map(mapArgs[0], mapArgs[1], ctx); }, + nmap: function(cm, params) { this.map(cm, params, 'normal'); }, + vmap: function(cm, params) { this.map(cm, params, 'visual'); }, move: function(cm, params) { - commandDispatcher.processCommand(cm, getVimState(cm), { + commandDispatcher.processCommand(cm, cm.state.vim, { type: 'motion', motion: 'moveToLineOrEdgeOfDocument', motionArgs: { forward: false, explicitRepeat: true, linewise: true }, repeatOverride: params.line+1}); }, + sort: function(cm, params) { + var reverse, ignoreCase, unique, number; + function parseArgs() { + if (params.argString) { + var args = new CodeMirror.StringStream(params.argString); + if (args.eat('!')) { reverse = true; } + if (args.eol()) { return; } + if (!args.eatSpace()) { return 'Invalid arguments'; } + var opts = args.match(/[a-z]+/); + if (opts) { + opts = opts[0]; + ignoreCase = opts.indexOf('i') != -1; + unique = opts.indexOf('u') != -1; + var decimal = opts.indexOf('d') != -1 && 1; + var hex = opts.indexOf('x') != -1 && 1; + var octal = opts.indexOf('o') != -1 && 1; + if (decimal + hex + octal > 1) { return 'Invalid arguments'; } + number = decimal && 'decimal' || hex && 'hex' || octal && 'octal'; + } + if (args.eatSpace() && args.match(/\/.*\//)) { 'patterns not supported'; } + } + } + var err = parseArgs(); + if (err) { + showConfirm(cm, err + ': ' + params.argString); + return; + } + var lineStart = params.line || cm.firstLine(); + var lineEnd = params.lineEnd || params.line || cm.lastLine(); + if (lineStart == lineEnd) { return; } + var curStart = { line: lineStart, ch: 0 }; + var curEnd = { line: lineEnd, ch: lineLength(cm, lineEnd) }; + var text = cm.getRange(curStart, curEnd).split('\n'); + var numberRegex = (number == 'decimal') ? /(-?)([\d]+)/ : + (number == 'hex') ? /(-?)(?:0x)?([0-9a-f]+)/i : + (number == 'octal') ? /([0-7]+)/ : null; + var radix = (number == 'decimal') ? 10 : (number == 'hex') ? 16 : (number == 'octal') ? 8 : null; + var numPart = [], textPart = []; + if (number) { + for (var i = 0; i < text.length; i++) { + if (numberRegex.exec(text[i])) { + numPart.push(text[i]); + } else { + textPart.push(text[i]); + } + } + } else { + textPart = text; + } + function compareFn(a, b) { + if (reverse) { var tmp; tmp = a; a = b; b = tmp; } + if (ignoreCase) { a = a.toLowerCase(); b = b.toLowerCase(); } + var anum = number && numberRegex.exec(a); + var bnum = number && numberRegex.exec(b); + if (!anum) { return a < b ? -1 : 1; } + anum = parseInt((anum[1] + anum[2]).toLowerCase(), radix); + bnum = parseInt((bnum[1] + bnum[2]).toLowerCase(), radix); + return anum - bnum; + } + numPart.sort(compareFn); + textPart.sort(compareFn); + text = (!reverse) ? textPart.concat(numPart) : numPart.concat(textPart); + if (unique) { // Remove duplicate lines + var textOld = text; + var lastLine; + text = []; + for (var i = 0; i < textOld.length; i++) { + if (textOld[i] != lastLine) { + text.push(textOld[i]); + } + lastLine = textOld[i]; + } + } + cm.replaceRange(text.join('\n'), curStart, curEnd); + }, substitute: function(cm, params) { + if (!cm.getSearchCursor) { + throw new Error('Search feature not available. Requires searchcursor.js or ' + + 'any other getSearchCursor implementation.'); + } var argString = params.argString; var slashes = findUnescapedSlashes(argString); if (slashes[0] !== 0) { @@ -2562,6 +3276,7 @@ var replacePart = ''; var flagsPart; var count; + var confirm = false; // Whether to confirm each replace. if (slashes[1]) { replacePart = argString.substring(slashes[1] + 1, slashes[2]); } @@ -2573,6 +3288,10 @@ count = parseInt(trailing[1]); } if (flagsPart) { + if (flagsPart.indexOf('c') != -1) { + confirm = true; + flagsPart.replace('c', ''); + } regexPart = regexPart + '/' + flagsPart; } if (regexPart) { @@ -2588,27 +3307,15 @@ } var state = getSearchState(cm); var query = state.getQuery(); - var lineStart = params.line || cm.firstLine(); + var lineStart = (params.line !== undefined) ? params.line : cm.getCursor().line; var lineEnd = params.lineEnd || lineStart; if (count) { lineStart = lineEnd; lineEnd = lineStart + count - 1; } var startPos = clipCursorToContent(cm, { line: lineStart, ch: 0 }); - function doReplace() { - for (var cursor = cm.getSearchCursor(query, startPos); - cursor.findNext() && - isInRange(cursor.from(), lineStart, lineEnd);) { - var text = cm.getRange(cursor.from(), cursor.to()); - var newText = text.replace(query, replacePart); - cursor.replace(newText); - } - var vim = getVimState(cm); - if (vim.visualMode) { - exitVisualMode(cm, vim); - } - } - cm.operation(doReplace); + var cursor = cm.getSearchCursor(query, startPos); + doReplace(cm, confirm, lineStart, lineEnd, cursor, query, replacePart); }, redo: CodeMirror.commands.redo, undo: CodeMirror.commands.undo, @@ -2625,13 +3332,13 @@ clearSearchHighlight(cm); }, delmarks: function(cm, params) { - if (!params.argString || !params.argString.trim()) { + if (!params.argString || !trim(params.argString)) { showConfirm(cm, 'Argument required'); return; } - var state = getVimState(cm); - var stream = new CodeMirror.StringStream(params.argString.trim()); + var state = cm.state.vim; + var stream = new CodeMirror.StringStream(trim(params.argString)); while (!stream.eol()) { stream.eatSpace(); @@ -2676,7 +3383,7 @@ delete state.marks[mark]; } } else { - showConfirm(cm, 'Invalid argument: ' + startMark + "-"); + showConfirm(cm, 'Invalid argument: ' + startMark + '-'); return; } } else { @@ -2689,6 +3396,98 @@ var exCommandDispatcher = new Vim.ExCommandDispatcher(); + /** + * @param {CodeMirror} cm CodeMirror instance we are in. + * @param {boolean} confirm Whether to confirm each replace. + * @param {Cursor} lineStart Line to start replacing from. + * @param {Cursor} lineEnd Line to stop replacing at. + * @param {RegExp} query Query for performing matches with. + * @param {string} replaceWith Text to replace matches with. May contain $1, + * $2, etc for replacing captured groups using Javascript replace. + */ + function doReplace(cm, confirm, lineStart, lineEnd, searchCursor, query, + replaceWith) { + // Set up all the functions. + cm.state.vim.exMode = true; + var done = false; + var lastPos = searchCursor.from(); + function replaceAll() { + cm.operation(function() { + while (!done) { + replace(); + next(); + } + stop(); + }); + } + function replace() { + var text = cm.getRange(searchCursor.from(), searchCursor.to()); + var newText = text.replace(query, replaceWith); + searchCursor.replace(newText); + } + function next() { + var found = searchCursor.findNext(); + if (!found) { + done = true; + } else if (isInRange(searchCursor.from(), lineStart, lineEnd)) { + cm.scrollIntoView(searchCursor.from(), 30); + cm.setSelection(searchCursor.from(), searchCursor.to()); + lastPos = searchCursor.from(); + done = false; + } else { + done = true; + } + } + function stop(close) { + if (close) { close(); } + cm.focus(); + if (lastPos) { + cm.setCursor(lastPos); + var vim = cm.state.vim; + vim.exMode = false; + vim.lastHPos = vim.lastHSPos = lastPos.ch; + } + } + function onPromptKeyDown(e, _value, close) { + // Swallow all keys. + CodeMirror.e_stop(e); + var keyName = CodeMirror.keyName(e); + switch (keyName) { + case 'Y': + replace(); next(); break; + case 'N': + next(); break; + case 'A': + cm.operation(replaceAll); break; + case 'L': + replace(); + // fall through and exit. + case 'Q': + case 'Esc': + case 'Ctrl-C': + case 'Ctrl-[': + stop(close); + break; + } + if (done) { stop(close); } + } + + // Actually do replace. + next(); + if (done) { + showConfirm(cm, 'No matches for ' + query.source); + return; + } + if (!confirm) { + replaceAll(); + return; + } + showPrompt(cm, { + prefix: 'replace with ' + replaceWith + ' (y/n/a/q/l)', + onKeyDown: onPromptKeyDown + }); + } + // Register Vim with CodeMirror function buildVimKeyMap() { /** @@ -2697,33 +3496,36 @@ * modifers. */ // TODO: Figure out a way to catch capslock. - function handleKeyEvent_(cm, key, modifier) { - if (isUpperCase(key)) { + function cmKeyToVimKey(key, modifier) { + var vimKey = key; + if (isUpperCase(vimKey)) { // Convert to lower case if shift is not the modifier since the key // we get from CodeMirror is always upper case. if (modifier == 'Shift') { modifier = null; } else { - key = key.toLowerCase(); + vimKey = vimKey.toLowerCase(); } } if (modifier) { // Vim will parse modifier+key combination as a single key. - key = modifier + '-' + key; + vimKey = modifier.charAt(0) + '-' + vimKey; } - vim.handleKey(cm, key); + var specialKey = ({Enter:'CR',Backspace:'BS',Delete:'Del'})[vimKey]; + vimKey = specialKey ? specialKey : vimKey; + vimKey = vimKey.length > 1 ? '<'+ vimKey + '>' : vimKey; + return vimKey; } // Closure to bind CodeMirror, key, modifier. - function keyMapper(key, modifier) { + function keyMapper(vimKey) { return function(cm) { - handleKeyEvent_(cm, key, modifier); + CodeMirror.Vim.handleKey(cm, vimKey); }; } - var modifiers = ['Shift', 'Ctrl']; - var keyMap = { + var cmToVimKeymap = { 'nofallthrough': true, 'style': 'fat-cursor' }; @@ -2735,11 +3537,9 @@ // them. key = "'" + key + "'"; } - if (modifier) { - keyMap[modifier + '-' + key] = keyMapper(keys[i], modifier); - } else { - keyMap[key] = keyMapper(keys[i]); - } + var vimKey = cmKeyToVimKey(keys[i], modifier); + var cmKey = modifier ? modifier + '-' + key : key; + cmToVimKeymap[cmKey] = keyMapper(vimKey); } } bindKeys(upperCaseAlphabet); @@ -2751,13 +3551,31 @@ bindKeys(numbers, 'Ctrl'); bindKeys(specialKeys); bindKeys(specialKeys, 'Ctrl'); - return keyMap; + return cmToVimKeymap; } CodeMirror.keyMap.vim = buildVimKeyMap(); function exitInsertMode(cm) { + var vim = cm.state.vim; + var inReplay = vimGlobalState.macroModeState.inReplay; + if (!inReplay) { + cm.off('change', onChange); + cm.off('cursorActivity', onCursorActivity); + CodeMirror.off(cm.getInputField(), 'keydown', onKeyEventTargetKeyDown); + } + if (!inReplay && vim.insertModeRepeat > 1) { + // Perform insert mode repeat for commands like 3,a and 3,o. + repeatLastEdit(cm, vim, vim.insertModeRepeat - 1, + true /** repeatForInsert */); + vim.lastEditInputState.repeatOverride = vim.insertModeRepeat; + } + delete vim.insertModeRepeat; cm.setCursor(cm.getCursor().line, cm.getCursor().ch-1, true); + vim.insertMode = false; cm.setOption('keyMap', 'vim'); + cm.setOption('disableInput', true); + cm.toggleOverwrite(false); // exit replace mode if we were in it. + CodeMirror.signal(cm, "vim-mode-change", {mode: "normal"}); } CodeMirror.keyMap['vim-insert'] = { @@ -2776,10 +3594,192 @@ fallthrough: ['default'] }; + CodeMirror.keyMap['vim-replace'] = { + 'Backspace': 'goCharLeft', + fallthrough: ['vim-insert'] + }; + + function parseRegisterToKeyBuffer(macroModeState, registerName) { + var match, key; + var register = vimGlobalState.registerController.getRegister(registerName); + var text = register.toString(); + var macroKeyBuffer = macroModeState.macroKeyBuffer; + emptyMacroKeyBuffer(macroModeState); + do { + match = (/<\w+-.+?>|<\w+>|./).exec(text); + if(match === null)break; + key = match[0]; + text = text.substring(match.index + key.length); + macroKeyBuffer.push(key); + } while (text); + return macroKeyBuffer; + } + + function parseKeyBufferToRegister(registerName, keyBuffer) { + var text = keyBuffer.join(''); + vimGlobalState.registerController.setRegisterText(registerName, text); + } + + function emptyMacroKeyBuffer(macroModeState) { + if(macroModeState.isMacroPlaying)return; + var macroKeyBuffer = macroModeState.macroKeyBuffer; + macroKeyBuffer.length = 0; + } + + function executeMacroKeyBuffer(cm, macroModeState, keyBuffer) { + macroModeState.isMacroPlaying = true; + for (var i = 0, len = keyBuffer.length; i < len; i++) { + CodeMirror.Vim.handleKey(cm, keyBuffer[i]); + }; + macroModeState.isMacroPlaying = false; + } + + function logKey(macroModeState, key) { + if(macroModeState.isMacroPlaying)return; + var macroKeyBuffer = macroModeState.macroKeyBuffer; + macroKeyBuffer.push(key); + } + + /** + * Listens for changes made in insert mode. + * Should only be active in insert mode. + */ + function onChange(_cm, changeObj) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + while (changeObj) { + lastChange.expectCursorActivityForChange = true; + if (changeObj.origin == '+input' || changeObj.origin == 'paste' + || changeObj.origin === undefined /* only in testing */) { + var text = changeObj.text.join('\n'); + lastChange.changes.push(text); + } + // Change objects may be chained with next. + changeObj = changeObj.next; + } + } + + /** + * Listens for any kind of cursor activity on CodeMirror. + * - For tracking cursor activity in insert mode. + * - Should only be active in insert mode. + */ + function onCursorActivity() { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + if (lastChange.expectCursorActivityForChange) { + lastChange.expectCursorActivityForChange = false; + } else { + // Cursor moved outside the context of an edit. Reset the change. + lastChange.changes = []; + } + } + + /** Wrapper for special keys pressed in insert mode */ + function InsertModeKey(keyName) { + this.keyName = keyName; + } + + /** + * Handles raw key down events from the text area. + * - Should only be active in insert mode. + * - For recording deletes in insert mode. + */ + function onKeyEventTargetKeyDown(e) { + var macroModeState = vimGlobalState.macroModeState; + var lastChange = macroModeState.lastInsertModeChanges; + var keyName = CodeMirror.keyName(e); + function onKeyFound() { + lastChange.changes.push(new InsertModeKey(keyName)); + return true; + } + if (keyName.indexOf('Delete') != -1 || keyName.indexOf('Backspace') != -1) { + CodeMirror.lookupKey(keyName, ['vim-insert'], onKeyFound); + } + } + + /** + * Repeats the last edit, which includes exactly 1 command and at most 1 + * insert. Operator and motion commands are read from lastEditInputState, + * while action commands are read from lastEditActionCommand. + * + * If repeatForInsert is true, then the function was called by + * exitInsertMode to repeat the insert mode changes the user just made. The + * corresponding enterInsertMode call was made with a count. + */ + function repeatLastEdit(cm, vim, repeat, repeatForInsert) { + var macroModeState = vimGlobalState.macroModeState; + macroModeState.inReplay = true; + var isAction = !!vim.lastEditActionCommand; + var cachedInputState = vim.inputState; + function repeatCommand() { + if (isAction) { + commandDispatcher.processAction(cm, vim, vim.lastEditActionCommand); + } else { + commandDispatcher.evalInput(cm, vim); + } + } + function repeatInsert(repeat) { + if (macroModeState.lastInsertModeChanges.changes.length > 0) { + // For some reason, repeat cw in desktop VIM will does not repeat + // insert mode changes. Will conform to that behavior. + repeat = !vim.lastEditActionCommand ? 1 : repeat; + repeatLastInsertModeChanges(cm, repeat, macroModeState); + } + } + vim.inputState = vim.lastEditInputState; + if (isAction && vim.lastEditActionCommand.interlaceInsertRepeat) { + // o and O repeat have to be interlaced with insert repeats so that the + // insertions appear on separate lines instead of the last line. + for (var i = 0; i < repeat; i++) { + repeatCommand(); + repeatInsert(1); + } + } else { + if (!repeatForInsert) { + // Hack to get the cursor to end up at the right place. If I is + // repeated in insert mode repeat, cursor will be 1 insert + // change set left of where it should be. + repeatCommand(); + } + repeatInsert(repeat); + } + vim.inputState = cachedInputState; + if (vim.insertMode && !repeatForInsert) { + // Don't exit insert mode twice. If repeatForInsert is set, then we + // were called by an exitInsertMode call lower on the stack. + exitInsertMode(cm); + } + macroModeState.inReplay = false; + }; + + function repeatLastInsertModeChanges(cm, repeat, macroModeState) { + var lastChange = macroModeState.lastInsertModeChanges; + function keyHandler(binding) { + if (typeof binding == 'string') { + CodeMirror.commands[binding](cm); + } else { + binding(cm); + } + return true; + } + for (var i = 0; i < repeat; i++) { + for (var j = 0; j < lastChange.changes.length; j++) { + var change = lastChange.changes[j]; + if (change instanceof InsertModeKey) { + CodeMirror.lookupKey(change.keyName, ['vim-insert'], keyHandler); + } else { + var cur = cm.getCursor(); + cm.replaceRange(change, cur, cur); + } + } + } + } + + resetVimGlobalState(); return vimApi; }; // Initialize Vim and make it available as an API. - var vim = Vim(); - CodeMirror.Vim = vim; + CodeMirror.Vim = Vim(); } )(); diff --git a/gulliver/js/codemirror/lib/codemirror.css b/gulliver/js/codemirror/lib/codemirror.css index 753a15f91..23eaf74d4 100644 --- a/gulliver/js/codemirror/lib/codemirror.css +++ b/gulliver/js/codemirror/lib/codemirror.css @@ -3,7 +3,7 @@ .CodeMirror { /* Set height, width, borders, and global font properties here */ font-family: monospace; - height: '95%'; + height: 300px; } .CodeMirror-scroll { /* Set scrolling behaviour here */ @@ -19,7 +19,7 @@ padding: 0 4px; /* Horizontal padding of content */ } -.CodeMirror-scrollbar-filler { +.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { background-color: white; /* The little square between H and V scrollbars */ } @@ -28,6 +28,7 @@ .CodeMirror-gutters { border-right: 1px solid #ddd; background-color: #f7f7f7; + white-space: nowrap; } .CodeMirror-linenumbers {} .CodeMirror-linenumber { @@ -73,10 +74,8 @@ .cm-s-default .cm-string {color: #a11;} .cm-s-default .cm-string-2 {color: #f50;} .cm-s-default .cm-meta {color: #555;} -.cm-s-default .cm-error {color: #f00;} .cm-s-default .cm-qualifier {color: #555;} .cm-s-default .cm-builtin {color: #30a;} -.cm-s-default .cm-PMbuiltin {color: white; background: #38A;} .cm-s-default .cm-bracket {color: #997;} .cm-s-default .cm-tag {color: #170;} .cm-s-default .cm-attribute {color: #00c;} @@ -91,10 +90,12 @@ .cm-em {font-style: italic;} .cm-link {text-decoration: underline;} +.cm-s-default .cm-error {color: #f00;} .cm-invalidchar {color: #f00;} div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} +.CodeMirror-activeline-background {background: #e8f2ff;} /* STOP */ @@ -111,12 +112,14 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-scroll { /* 30px is the magic margin used to hide the element's real scrollbars */ - /* See overflow: hidden in .CodeMirror, and the paddings in .CodeMirror-sizer */ + /* See overflow: hidden in .CodeMirror */ margin-bottom: -30px; margin-right: -30px; padding-bottom: 30px; padding-right: 30px; height: 100%; outline: none; /* Prevent dragging from highlighting the element */ position: relative; + -moz-box-sizing: content-box; + box-sizing: content-box; } .CodeMirror-sizer { position: relative; @@ -125,7 +128,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} /* The fake, visible scrollbars. Used to force redraw during scrolling before actuall scrolling happens, thus preventing shaking and flickering artifacts. */ -.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler { +.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { position: absolute; z-index: 6; display: none; @@ -142,17 +145,21 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } .CodeMirror-scrollbar-filler { right: 0; bottom: 0; - z-index: 6; +} +.CodeMirror-gutter-filler { + left: 0; bottom: 0; } .CodeMirror-gutters { position: absolute; left: 0; top: 0; - height: 100%; padding-bottom: 30px; z-index: 3; } .CodeMirror-gutter { + white-space: normal; height: 100%; + -moz-box-sizing: content-box; + box-sizing: content-box; padding-bottom: 30px; margin-bottom: -32px; display: inline-block; @@ -190,6 +197,16 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} white-space: pre-wrap; word-break: normal; } +.CodeMirror-code pre { + border-right: 30px solid transparent; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; +} +.CodeMirror-wrap .CodeMirror-code pre { + border-right: none; + width: auto; +} .CodeMirror-linebackground { position: absolute; left: 0; right: 0; top: 0; bottom: 0; @@ -202,9 +219,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} overflow: auto; } -.CodeMirror-widget { - display: inline-block; -} +.CodeMirror-widget {} .CodeMirror-wrap .CodeMirror-scroll { overflow-x: hidden; @@ -212,7 +227,8 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} .CodeMirror-measure { position: absolute; - width: 100%; height: 0px; + width: 100%; + height: 0; overflow: hidden; visibility: hidden; } @@ -237,7 +253,7 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} } /* IE7 hack to prevent it from returning funny offsetTops on the spans */ -/*.CodeMirror span { *vertical-align: text-bottom; }*/ +.CodeMirror span { *vertical-align: text-bottom; } @media print { /* Hide the cursor when printing */ diff --git a/gulliver/js/codemirror/lib/codemirror.js b/gulliver/js/codemirror/lib/codemirror.js index 120e09bbf..d82bd143c 100644 --- a/gulliver/js/codemirror/lib/codemirror.js +++ b/gulliver/js/codemirror/lib/codemirror.js @@ -1,4 +1,4 @@ -// CodeMirror version 3.16 +// CodeMirror version 3.21 // // CodeMirror is the only global var we claim window.CodeMirror = (function() { @@ -9,9 +9,14 @@ window.CodeMirror = (function() { // Crude, but necessary to handle a number of hard-to-feature-detect // bugs and behavior differences. var gecko = /gecko\/\d/i.test(navigator.userAgent); - var ie = /MSIE \d/.test(navigator.userAgent); - var ie_lt8 = ie && (document.documentMode == null || document.documentMode < 8); - var ie_lt9 = ie && (document.documentMode == null || document.documentMode < 9); + // IE11 currently doesn't count as 'ie', since it has almost none of + // the same bugs as earlier versions. Use ie_gt10 to handle + // incompatibilities in that version. + var old_ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = old_ie && (document.documentMode == null || document.documentMode < 8); + var ie_lt9 = old_ie && (document.documentMode == null || document.documentMode < 9); + var ie_gt10 = /Trident\/([7-9]|\d{2,})\./.test(navigator.userAgent); + var ie = old_ie || ie_gt10; var webkit = /WebKit\//.test(navigator.userAgent); var qtwebkit = webkit && /Qt\/\d+\.\d+/.test(navigator.userAgent); var chrome = /Chrome\//.test(navigator.userAgent); @@ -33,7 +38,7 @@ window.CodeMirror = (function() { if (opera_version && opera_version >= 15) { opera = false; webkit = true; } // Some browsers use the wrong event properties to signal cmd/ctrl on OS X var flipCtrlCmd = mac && (qtwebkit || opera && (opera_version == null || opera_version < 12.11)); - var captureMiddleClick = gecko || (ie && !ie_lt9); + var captureMiddleClick = gecko || (old_ie && !ie_lt9); // Optimize some code when these features are not used var sawReadOnlySpans = false, sawCollapsedSpans = false; @@ -59,7 +64,8 @@ window.CodeMirror = (function() { overlays: [], modeGen: 0, overwrite: false, focused: false, - suppressEdits: false, pasteIncoming: false, + suppressEdits: false, + pasteIncoming: false, cutIncoming: false, draggingText: false, highlight: new Delayed()}; @@ -73,7 +79,7 @@ window.CodeMirror = (function() { // Override magic textarea content restore that IE sometimes does // on our hidden textarea on reload - if (ie) setTimeout(bind(resetInput, this, true), 20); + if (old_ie) setTimeout(bind(resetInput, this, true), 20); registerEventHandlers(this); // IE throws unspecified error in certain cases, when @@ -193,6 +199,10 @@ window.CodeMirror = (function() { function loadMode(cm) { cm.doc.mode = CodeMirror.getMode(cm.options, cm.doc.modeOption); + resetModeState(cm); + } + + function resetModeState(cm) { cm.doc.iter(function(line) { if (line.stateAfter) line.stateAfter = null; if (line.styles) line.styles = null; @@ -242,7 +252,6 @@ window.CodeMirror = (function() { var map = keyMap[cm.options.keyMap], style = map.style; cm.display.wrapper.className = cm.display.wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + (style ? " cm-keymap-" + style : ""); - cm.state.disableInput = map.disableInput; } function themeChanged(cm) { @@ -306,15 +315,13 @@ window.CodeMirror = (function() { // Make sure the gutters options contains the element // "CodeMirror-linenumbers" when the lineNumbers option is true. function setGuttersForLineNumbers(options) { - var found = false; - for (var i = 0; i < options.gutters.length; ++i) { - if (options.gutters[i] == "CodeMirror-linenumbers") { - if (options.lineNumbers) found = true; - else options.gutters.splice(i--, 1); - } + var found = indexOf(options.gutters, "CodeMirror-linenumbers"); + if (found == -1 && options.lineNumbers) { + options.gutters = options.gutters.concat(["CodeMirror-linenumbers"]); + } else if (found > -1 && !options.lineNumbers) { + options.gutters = options.gutters.slice(0); + options.gutters.splice(found, 1); } - if (!found && options.lineNumbers) - options.gutters.push("CodeMirror-linenumbers"); } // SCROLLBARS @@ -357,8 +364,10 @@ window.CodeMirror = (function() { d.gutterFiller.style.width = d.gutters.offsetWidth + "px"; } else d.gutterFiller.style.display = ""; - if (mac_geLion && scrollbarWidth(d.measure) === 0) + if (mac_geLion && scrollbarWidth(d.measure) === 0) { d.scrollbarV.style.minWidth = d.scrollbarH.style.minHeight = mac_geMountainLion ? "18px" : "12px"; + d.scrollbarV.style.pointerEvents = d.scrollbarH.style.pointerEvents = "none"; + } } function visibleLines(display, doc, viewPort) { @@ -413,12 +422,18 @@ window.CodeMirror = (function() { function updateDisplay(cm, changes, viewPort, forced) { var oldFrom = cm.display.showingFrom, oldTo = cm.display.showingTo, updated; var visible = visibleLines(cm.display, cm.doc, viewPort); - for (;;) { + for (var first = true;; first = false) { + var oldWidth = cm.display.scroller.clientWidth; if (!updateDisplayInner(cm, changes, visible, forced)) break; - forced = false; updated = true; + changes = []; updateSelection(cm); updateScrollbars(cm); + if (first && cm.options.lineWrapping && oldWidth != cm.display.scroller.clientWidth) { + forced = true; + continue; + } + forced = false; // Clip forced viewport to actual scrollable area if (viewPort) @@ -427,7 +442,6 @@ window.CodeMirror = (function() { visible = visibleLines(cm.display, cm.doc, viewPort); if (visible.from >= cm.display.showingFrom && visible.to <= cm.display.showingTo) break; - changes = []; } if (updated) { @@ -443,7 +457,7 @@ window.CodeMirror = (function() { // updates. function updateDisplayInner(cm, changes, visible, forced) { var display = cm.display, doc = cm.doc; - if (!display.wrapper.clientWidth) { + if (!display.wrapper.offsetWidth) { display.showingFrom = display.showingTo = doc.first; display.viewOffset = 0; return; @@ -528,6 +542,7 @@ window.CodeMirror = (function() { } display.showingFrom = from; display.showingTo = to; + display.gutters.style.height = ""; updateHeightsInViewport(cm); updateViewOffset(cm); @@ -665,10 +680,11 @@ window.CodeMirror = (function() { } function buildLineElement(cm, line, lineNo, dims, reuse) { - var lineElement = lineContent(cm, line); + var built = buildLineContent(cm, line), lineElement = built.pre; var markers = line.gutterMarkers, display = cm.display, wrap; - if (!cm.options.lineNumbers && !markers && !line.bgClass && !line.wrapClass && !line.widgets) + var bgClass = built.bgClass ? built.bgClass + " " + (line.bgClass || "") : line.bgClass; + if (!cm.options.lineNumbers && !markers && !bgClass && !line.wrapClass && !line.widgets) return lineElement; // Lines with gutter elements, widgets or a background class need @@ -706,12 +722,12 @@ window.CodeMirror = (function() { wrap.appendChild(lineElement); } // Kludge to make sure the styled element lies behind the selection (by z-index) - if (line.bgClass) - wrap.insertBefore(elt("div", null, line.bgClass + " CodeMirror-linebackground"), wrap.firstChild); + if (bgClass) + wrap.insertBefore(elt("div", null, bgClass + " CodeMirror-linebackground"), wrap.firstChild); if (cm.options.lineNumbers || markers) { - var gutterWrap = wrap.insertBefore(elt("div", null, null, "position: absolute; left: " + + var gutterWrap = wrap.insertBefore(elt("div", null, "CodeMirror-gutter-wrapper", "position: absolute; left: " + (cm.options.fixedGutter ? dims.fixedPos : -dims.gutterTotalWidth) + "px"), - wrap.firstChild); + lineElement); if (cm.options.fixedGutter) (wrap.alignable || (wrap.alignable = [])).push(gutterWrap); if (cm.options.lineNumbers && (!markers || !markers["CodeMirror-linenumbers"])) wrap.lineNumber = gutterWrap.appendChild( @@ -902,7 +918,7 @@ window.CodeMirror = (function() { doc.iter(doc.frontier, Math.min(doc.first + doc.size, cm.display.showingTo + 500), function(line) { if (doc.frontier >= cm.display.showingFrom) { // Visible var oldStyles = line.styles; - line.styles = highlightLine(cm, line, state); + line.styles = highlightLine(cm, line, state, true); var ischange = !oldStyles || oldStyles.length != line.styles.length; for (var i = 0; !ischange && i < oldStyles.length; ++i) ischange = oldStyles[i] != line.styles[i]; if (ischange) { @@ -911,7 +927,7 @@ window.CodeMirror = (function() { } line.stateAfter = copyState(doc.mode, state); } else { - processLine(cm, line, state); + processLine(cm, line.text, state); line.stateAfter = doc.frontier % 5 == 0 ? copyState(doc.mode, state) : null; } ++doc.frontier; @@ -933,8 +949,9 @@ window.CodeMirror = (function() { // smallest indentation, which tends to need the least context to // parse correctly. function findStartLine(cm, n, precise) { - var minindent, minline, doc = cm.doc, maxScan = cm.doc.mode.innerMode ? 1000 : 100; - for (var search = n, lim = n - maxScan; search > lim; --search) { + var minindent, minline, doc = cm.doc; + var lim = precise ? -1 : n - (cm.doc.mode.innerMode ? 1000 : 100); + for (var search = n; search > lim; --search) { if (search <= doc.first) return doc.first; var line = getLine(doc, search - 1); if (line.stateAfter && (!precise || search <= doc.frontier)) return search; @@ -954,11 +971,12 @@ window.CodeMirror = (function() { if (!state) state = startState(doc.mode); else state = copyState(doc.mode, state); doc.iter(pos, n, function(line) { - processLine(cm, line, state); + processLine(cm, line.text, state); var save = pos == n - 1 || pos % 5 == 0 || pos >= display.showingFrom && pos < display.showingTo; line.stateAfter = save ? copyState(doc.mode, state) : null; ++pos; }); + if (precise) doc.frontier = pos; return state; } @@ -974,6 +992,10 @@ window.CodeMirror = (function() { function measureChar(cm, line, ch, data, bias) { var dir = -1; data = data || measureLine(cm, line); + if (data.crude) { + var left = data.left + ch * data.width; + return {left: left, right: left + data.width, top: data.top, bottom: data.bottom}; + } for (var pos = ch;; pos += dir) { var r = data[pos]; @@ -995,7 +1017,7 @@ window.CodeMirror = (function() { var memo = cache[i]; if (memo.text == line.text && memo.markedSpans == line.markedSpans && cm.display.scroller.clientWidth == memo.width && - memo.classes == line.textClass + "|" + line.bgClass + "|" + line.wrapClass) + memo.classes == line.textClass + "|" + line.wrapClass) return memo; } } @@ -1015,15 +1037,18 @@ window.CodeMirror = (function() { var cache = cm.display.measureLineCache; var memo = {text: line.text, width: cm.display.scroller.clientWidth, markedSpans: line.markedSpans, measure: measure, - classes: line.textClass + "|" + line.bgClass + "|" + line.wrapClass}; + classes: line.textClass + "|" + line.wrapClass}; if (cache.length == 16) cache[++cm.display.measureLineCachePos % 16] = memo; else cache.push(memo); return measure; } function measureLineInner(cm, line) { + if (!cm.options.lineWrapping && line.text.length >= cm.options.crudeMeasuringFrom) + return crudelyMeasureLine(cm, line); + var display = cm.display, measure = emptyArray(line.text.length); - var pre = lineContent(cm, line, measure, true); + var pre = buildLineContent(cm, line, measure, true).pre; // IE does not cache element positions of inline elements between // calls to getBoundingClientRect. This makes the loop below, @@ -1036,7 +1061,7 @@ window.CodeMirror = (function() { // doesn't work when wrapping is on, but in that case the // situation is slightly better, since IE does cache line-wrapping // information and only recomputes per-line. - if (ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { + if (old_ie && !ie_lt8 && !cm.options.lineWrapping && pre.childNodes.length > 100) { var fragment = document.createDocumentFragment(); var chunk = 10, n = pre.childNodes.length; for (var i = 0, chunks = Math.ceil(n / chunk); i < chunks; ++i) { @@ -1096,7 +1121,7 @@ window.CodeMirror = (function() { } } if (!rect) rect = data[i] = measureRect(getRect(node)); - if (cur.measureRight) rect.right = getRect(cur.measureRight).left; + if (cur.measureRight) rect.right = getRect(cur.measureRight).left - outer.left; if (cur.leftSide) rect.leftSide = measureRect(getRect(cur.leftSide)); } removeChildren(cm.display.measure); @@ -1108,6 +1133,15 @@ window.CodeMirror = (function() { return data; } + function crudelyMeasureLine(cm, line) { + var copy = new Line(line.text.slice(0, 100), null); + if (line.textClass) copy.textClass = line.textClass; + var measure = measureLineInner(cm, copy); + var left = measureChar(cm, copy, 0, measure, "left"); + var right = measureChar(cm, copy, 99, measure, "right"); + return {crude: true, top: left.top, left: left.left, bottom: left.bottom, width: (right.right - left.left) / 100}; + } + function measureLineWidth(cm, line) { var hasBadSpan = false; if (line.markedSpans) for (var i = 0; i < line.markedSpans; ++i) { @@ -1115,9 +1149,10 @@ window.CodeMirror = (function() { if (sp.collapsed && (sp.to == null || sp.to == line.text.length)) hasBadSpan = true; } var cached = !hasBadSpan && findCachedMeasurement(cm, line); - if (cached) return measureChar(cm, line, line.text.length, cached.measure, "right").right; + if (cached || line.text.length >= cm.options.crudeMeasuringFrom) + return measureChar(cm, line, line.text.length, cached && cached.measure, "right").right; - var pre = lineContent(cm, line, null, true); + var pre = buildLineContent(cm, line, null, true).pre; var end = pre.appendChild(zeroWidthElement(cm.display.measure)); removeChildrenAndAdd(cm.display.measure, pre); return getRect(end).right - getRect(cm.display.lineDiv).left; @@ -1262,7 +1297,7 @@ window.CodeMirror = (function() { if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) { var ch = x < fromX || x - fromX <= toX - x ? from : to; var xDiff = x - (ch == from ? fromX : toX); - while (isExtendingChar.test(lineObj.text.charAt(ch))) ++ch; + while (isExtendingChar(lineObj.text.charAt(ch))) ++ch; var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside, xDiff < 0 ? -1 : xDiff ? 1 : 0); return pos; @@ -1360,11 +1395,14 @@ window.CodeMirror = (function() { } if (!updated && op.selectionChanged) updateSelection(cm); if (op.updateScrollPos) { - display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = newScrollPos.scrollTop; - display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = newScrollPos.scrollLeft; + var top = Math.max(0, Math.min(display.scroller.scrollHeight - display.scroller.clientHeight, newScrollPos.scrollTop)); + var left = Math.max(0, Math.min(display.scroller.scrollWidth - display.scroller.clientWidth, newScrollPos.scrollLeft)); + display.scroller.scrollTop = display.scrollbarV.scrollTop = doc.scrollTop = top; + display.scroller.scrollLeft = display.scrollbarH.scrollLeft = doc.scrollLeft = left; alignHorizontally(cm); if (op.scrollToPos) - scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos), op.scrollToPosMargin); + scrollPosIntoView(cm, clipPos(cm.doc, op.scrollToPos.from), + clipPos(cm.doc, op.scrollToPos.to), op.scrollToPos.margin); } else if (newScrollPos) { scrollCursorIntoView(cm); } @@ -1451,7 +1489,7 @@ window.CodeMirror = (function() { // supported or compatible enough yet to rely on.) function readInput(cm) { var input = cm.display.input, prevInput = cm.display.prevInput, doc = cm.doc, sel = doc.sel; - if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.state.disableInput) return false; + if (!cm.state.focused || hasSelection(input) || isReadOnly(cm) || cm.options.disableInput) return false; if (cm.state.pasteIncoming && cm.state.fakedLastChar) { input.value = input.value.substring(0, input.value.length - 1); cm.state.fakedLastChar = false; @@ -1469,22 +1507,32 @@ window.CodeMirror = (function() { var same = 0, l = Math.min(prevInput.length, text.length); while (same < l && prevInput.charCodeAt(same) == text.charCodeAt(same)) ++same; var from = sel.from, to = sel.to; + var inserted = text.slice(same); if (same < prevInput.length) from = Pos(from.line, from.ch - (prevInput.length - same)); else if (cm.state.overwrite && posEq(from, to) && !cm.state.pasteIncoming) - to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + (text.length - same))); + to = Pos(to.line, Math.min(getLine(doc, to.line).text.length, to.ch + inserted.length)); var updateInput = cm.curOp.updateInput; - var changeEvent = {from: from, to: to, text: splitLines(text.slice(same)), - origin: cm.state.pasteIncoming ? "paste" : "+input"}; + var changeEvent = {from: from, to: to, text: splitLines(inserted), + origin: cm.state.pasteIncoming ? "paste" : cm.state.cutIncoming ? "cut" : "+input"}; makeChange(cm.doc, changeEvent, "end"); cm.curOp.updateInput = updateInput; signalLater(cm, "inputRead", cm, changeEvent); + if (inserted && !cm.state.pasteIncoming && cm.options.electricChars && + cm.options.smartIndent && sel.head.ch < 100) { + var electric = cm.getModeAt(sel.head).electricChars; + if (electric) for (var i = 0; i < electric.length; i++) + if (inserted.indexOf(electric.charAt(i)) > -1) { + indentLine(cm, sel.head.line, "smart"); + break; + } + } if (text.length > 1000 || text.indexOf("\n") > -1) input.value = cm.display.prevInput = ""; else cm.display.prevInput = text; if (withOp) endOperation(cm); - cm.state.pasteIncoming = false; + cm.state.pasteIncoming = cm.state.cutIncoming = false; return true; } @@ -1519,7 +1567,7 @@ window.CodeMirror = (function() { function registerEventHandlers(cm) { var d = cm.display; on(d.scroller, "mousedown", operation(cm, onMouseDown)); - if (ie) + if (old_ie) on(d.scroller, "dblclick", operation(cm, function(e) { if (signalDOMEvent(cm, e)) return; var pos = posFromMouse(cm, e); @@ -1586,7 +1634,10 @@ window.CodeMirror = (function() { if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; if (e.keyCode == 16) cm.doc.sel.shift = false; })); - on(d.input, "input", bind(fastPoll, cm)); + on(d.input, "input", function() { + if (ie && !ie_lt9 && cm.display.inputHasSelection) cm.display.inputHasSelection = null; + fastPoll(cm); + }); on(d.input, "keydown", operation(cm, onKeyDown)); on(d.input, "keypress", operation(cm, onKeyPress)); on(d.input, "focus", bind(onFocus, cm)); @@ -1622,21 +1673,22 @@ window.CodeMirror = (function() { fastPoll(cm); }); - function prepareCopy() { + function prepareCopy(e) { if (d.inaccurateSelection) { d.prevInput = ""; d.inaccurateSelection = false; d.input.value = cm.getSelection(); selectInput(d.input); } + if (e.type == "cut") cm.state.cutIncoming = true; } on(d.input, "cut", prepareCopy); on(d.input, "copy", prepareCopy); // Needed to handle Tab key in KHTML if (khtml) on(d.sizer, "mouseup", function() { - if (document.activeElement == d.input) d.input.blur(); - focusInput(cm); + if (document.activeElement == d.input) d.input.blur(); + focusInput(cm); }); } @@ -1720,6 +1772,9 @@ window.CodeMirror = (function() { e_preventDefault(e2); extendSelection(cm.doc, start); focusInput(cm); + // Work around unexplainable focus problem in IE9 (#2127) + if (old_ie && !ie_lt9) + setTimeout(function() {document.body.focus(); focusInput(cm);}, 20); } }); // Let the drag handler handle this. @@ -1794,7 +1849,7 @@ window.CodeMirror = (function() { } var move = operation(cm, function(e) { - if (!ie && !e_button(e)) done(e); + if (!old_ie && !e_button(e)) done(e); else extend(e); }); var up = operation(cm, done); @@ -1802,17 +1857,16 @@ window.CodeMirror = (function() { on(document, "mouseup", up); } - function clickInGutter(cm, e) { - var display = cm.display; + function gutterEvent(cm, e, type, prevent, signalfn) { try { var mX = e.clientX, mY = e.clientY; } catch(e) { return false; } + if (mX >= Math.floor(getRect(cm.display.gutters).right)) return false; + if (prevent) e_preventDefault(e); - if (mX >= Math.floor(getRect(display.gutters).right)) return false; - e_preventDefault(e); - if (!hasHandler(cm, "gutterClick")) return true; - + var display = cm.display; var lineBox = getRect(display.lineDiv); - if (mY > lineBox.bottom) return true; + + if (mY > lineBox.bottom || !hasHandler(cm, type)) return e_defaultPrevented(e); mY -= lineBox.top - display.viewOffset; for (var i = 0; i < cm.options.gutters.length; ++i) { @@ -1820,11 +1874,19 @@ window.CodeMirror = (function() { if (g && getRect(g).right >= mX) { var line = lineAtHeight(cm.doc, mY); var gutter = cm.options.gutters[i]; - signalLater(cm, "gutterClick", cm, line, gutter, e); - break; + signalfn(cm, type, cm, line, gutter, e); + return e_defaultPrevented(e); } } - return true; + } + + function contextMenuInGutter(cm, e) { + if (!hasHandler(cm, "gutterContextMenu")) return false; + return gutterEvent(cm, e, "gutterContextMenu", false, signal); + } + + function clickInGutter(cm, e) { + return gutterEvent(cm, e, "gutterClick", true, signalLater); } // Kludge to work around strange IE behavior where it'll sometimes @@ -1869,7 +1931,6 @@ window.CodeMirror = (function() { if (cm.state.draggingText) replaceRange(cm.doc, "", curFrom, curTo, "paste"); cm.replaceSelection(text, null, "paste"); focusInput(cm); - onFocus(cm); } } catch(e){} @@ -1887,6 +1948,7 @@ window.CodeMirror = (function() { // Recent Safari (~6.0.2) have a tendency to segfault when this happens, so we don't do it there. if (e.dataTransfer.setDragImage && !safari) { var img = elt("img", null, null, "position: fixed; left: 0; top: 0;"); + img.src = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="; if (opera) { img.width = img.height = 1; cm.display.wrapper.appendChild(img); @@ -1932,7 +1994,7 @@ window.CodeMirror = (function() { // know one. These don't have to be accurate -- the result of them // being wrong would just be a slight flicker on the first wheel // scroll (if it is large enough). - if (ie) wheelPixelsPerUnit = -.53; + if (old_ie) wheelPixelsPerUnit = -.53; else if (gecko) wheelPixelsPerUnit = 15; else if (chrome) wheelPixelsPerUnit = -.7; else if (safari) wheelPixelsPerUnit = -1/3; @@ -2086,7 +2148,7 @@ window.CodeMirror = (function() { var cm = this; if (!cm.state.focused) onFocus(cm); if (signalDOMEvent(cm, e) || cm.options.onKeyEvent && cm.options.onKeyEvent(cm, addStop(e))) return; - if (ie && e.keyCode == 27) e.returnValue = false; + if (old_ie && e.keyCode == 27) e.returnValue = false; var code = e.keyCode; // IE does strange things with escape. cm.doc.sel.shift = code == 16 || e.shiftKey; @@ -2107,10 +2169,6 @@ window.CodeMirror = (function() { if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(cm, e)) return; var ch = String.fromCharCode(charCode == null ? keyCode : charCode); - if (this.options.electricChars && this.doc.mode.electricChars && - this.options.smartIndent && !isReadOnly(this) && - this.doc.mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(cm, function() {indentLine(cm, cm.doc.sel.to.line, "smart");}), 75); if (handleCharBinding(cm, e, ch)) return; if (ie && !ie_lt9) cm.display.inputHasSelection = null; fastPoll(cm); @@ -2145,17 +2203,21 @@ window.CodeMirror = (function() { function onContextMenu(cm, e) { if (signalDOMEvent(cm, e, "contextmenu")) return; var display = cm.display, sel = cm.doc.sel; - if (eventInWidget(display, e)) return; + if (eventInWidget(display, e) || contextMenuInGutter(cm, e)) return; var pos = posFromMouse(cm, e), scrollPos = display.scroller.scrollTop; if (!pos || opera) return; // Opera is difficult. - if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) + + // Reset the current text selection only if the click is done outside of the selection + // and 'resetSelectionOnContextMenu' option is true. + var reset = cm.options.resetSelectionOnContextMenu; + if (reset && (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))) operation(cm, setSelection)(cm.doc, pos, pos); var oldCSS = display.input.style.cssText; display.inputDiv.style.position = "absolute"; display.input.style.cssText = "position: fixed; width: 30px; height: 30px; top: " + (e.clientY - 5) + - "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: white; outline: none;" + + "px; left: " + (e.clientX - 5) + "px; z-index: 1000; background: transparent; outline: none;" + "border-width: 0; outline: none; overflow: hidden; opacity: .05; -ms-opacity: .05; filter: alpha(opacity=5);"; focusInput(cm); resetInput(cm, true); @@ -2164,8 +2226,8 @@ window.CodeMirror = (function() { function prepareSelectAllHack() { if (display.input.selectionStart != null) { - var extval = display.input.value = " " + (posEq(sel.from, sel.to) ? "" : display.input.value); - display.prevInput = " "; + var extval = display.input.value = "\u200b" + (posEq(sel.from, sel.to) ? "" : display.input.value); + display.prevInput = "\u200b"; display.input.selectionStart = 1; display.input.selectionEnd = extval.length; } } @@ -2177,10 +2239,10 @@ window.CodeMirror = (function() { // Try to detect the user choosing select-all if (display.input.selectionStart != null) { - if (!ie || ie_lt9) prepareSelectAllHack(); + if (!old_ie || ie_lt9) prepareSelectAllHack(); clearTimeout(detectingSelectAll); var i = 0, poll = function(){ - if (display.prevInput == " " && display.input.selectionStart == 0) + if (display.prevInput == "\u200b" && display.input.selectionStart == 0) operation(cm, commands.selectAll)(cm); else if (i++ < 10) detectingSelectAll = setTimeout(poll, 500); else resetInput(cm); @@ -2189,7 +2251,7 @@ window.CodeMirror = (function() { } } - if (ie && !ie_lt9) prepareSelectAllHack(); + if (old_ie && !ie_lt9) prepareSelectAllHack(); if (captureMiddleClick) { e_stop(e); var mouseup = function() { @@ -2299,6 +2361,7 @@ window.CodeMirror = (function() { } function makeChangeNoReadonly(doc, change, selUpdate) { + if (change.text.length == 1 && change.text[0] == "" && posEq(change.from, change.to)) return; var selAfter = computeSelAfterChange(doc, change, selUpdate); addToHistory(doc, change, selAfter, doc.cm ? doc.cm.curOp.id : NaN); @@ -2461,6 +2524,7 @@ window.CodeMirror = (function() { function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);} + function cmp(a, b) {return a.line - b.line || a.ch - b.ch;} function copyPos(x) {return Pos(x.line, x.ch);} // SELECTION @@ -2521,6 +2585,7 @@ window.CodeMirror = (function() { var sel = doc.sel; sel.goalColumn = null; + if (bias == null) bias = posLess(head, sel.head) ? -1 : 1; // Skip over atomic spans. if (checkAtomic || !posEq(anchor, sel.anchor)) anchor = skipAtomic(doc, anchor, bias, checkAtomic != "push"); @@ -2598,28 +2663,31 @@ window.CodeMirror = (function() { // SCROLLING function scrollCursorIntoView(cm) { - var coords = scrollPosIntoView(cm, cm.doc.sel.head, cm.options.cursorScrollMargin); + var coords = scrollPosIntoView(cm, cm.doc.sel.head, null, cm.options.cursorScrollMargin); if (!cm.state.focused) return; var display = cm.display, box = getRect(display.sizer), doScroll = null; if (coords.top + box.top < 0) doScroll = true; else if (coords.bottom + box.top > (window.innerHeight || document.documentElement.clientHeight)) doScroll = false; if (doScroll != null && !phantom) { - var hidden = display.cursor.style.display == "none"; - if (hidden) { - display.cursor.style.display = ""; - display.cursor.style.left = coords.left + "px"; - display.cursor.style.top = (coords.top - display.viewOffset) + "px"; - } - display.cursor.scrollIntoView(doScroll); - if (hidden) display.cursor.style.display = "none"; + var scrollNode = elt("div", "\u200b", null, "position: absolute; top: " + + (coords.top - display.viewOffset) + "px; height: " + + (coords.bottom - coords.top + scrollerCutOff) + "px; left: " + + coords.left + "px; width: 2px;"); + cm.display.lineSpace.appendChild(scrollNode); + scrollNode.scrollIntoView(doScroll); + cm.display.lineSpace.removeChild(scrollNode); } } - function scrollPosIntoView(cm, pos, margin) { + function scrollPosIntoView(cm, pos, end, margin) { if (margin == null) margin = 0; for (;;) { var changed = false, coords = cursorCoords(cm, pos); - var scrollPos = calculateScrollPos(cm, coords.left, coords.top - margin, coords.left, coords.bottom + margin); + var endCoords = !end || end == pos ? coords : cursorCoords(cm, end); + var scrollPos = calculateScrollPos(cm, Math.min(coords.left, endCoords.left), + Math.min(coords.top, endCoords.top) - margin, + Math.max(coords.left, endCoords.left), + Math.max(coords.bottom, endCoords.bottom) + margin); var startTop = cm.doc.scrollTop, startLeft = cm.doc.scrollLeft; if (scrollPos.scrollTop != null) { setScrollTop(cm, scrollPos.scrollTop); @@ -2690,7 +2758,10 @@ window.CodeMirror = (function() { var tabSize = cm.options.tabSize; var line = getLine(doc, n), curSpace = countColumn(line.text, null, tabSize); var curSpaceString = line.text.match(/^\s*/)[0], indentation; - if (how == "smart") { + if (!aggressive && !/\S/.test(line.text)) { + indentation = 0; + how = "not"; + } else if (how == "smart") { indentation = cm.doc.mode.indent(state, line.text.slice(curSpaceString.length), line.text); if (indentation == Pass) { if (!aggressive) return; @@ -2716,6 +2787,8 @@ window.CodeMirror = (function() { if (indentString != curSpaceString) replaceRange(cm.doc, indentString, Pos(n, 0), Pos(n, curSpaceString.length), "+input"); + else if (doc.sel.head.line == n && doc.sel.head.ch < curSpaceString.length) + setSelection(doc, Pos(n, curSpaceString.length), Pos(n, curSpaceString.length), 1); line.stateAfter = null; } @@ -2816,7 +2889,7 @@ window.CodeMirror = (function() { CodeMirror.prototype = { constructor: CodeMirror, - focus: function(){window.focus(); focusInput(this); onFocus(this); fastPoll(this);}, + focus: function(){window.focus(); focusInput(this); fastPoll(this);}, setOption: function(option, value) { var options = this.options, old = options[option]; @@ -2870,7 +2943,7 @@ window.CodeMirror = (function() { }), indentSelection: operation(null, function(how) { var sel = this.doc.sel; - if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how); + if (posEq(sel.from, sel.to)) return indentLine(this, sel.from.line, how, true); var e = sel.to.line - (sel.to.ch ? 0 : 1); for (var i = sel.from.line; i <= e; ++i) indentLine(this, i, how); }), @@ -2915,11 +2988,31 @@ window.CodeMirror = (function() { }, getHelper: function(pos, type) { - if (!helpers.hasOwnProperty(type)) return; + return this.getHelpers(pos, type)[0]; + }, + + getHelpers: function(pos, type) { + var found = []; + if (!helpers.hasOwnProperty(type)) return helpers; var help = helpers[type], mode = this.getModeAt(pos); - return mode[type] && help[mode[type]] || - mode.helperType && help[mode.helperType] || - help[mode.name]; + if (typeof mode[type] == "string") { + if (help[mode[type]]) found.push(help[mode[type]]); + } else if (mode[type]) { + for (var i = 0; i < mode[type].length; i++) { + var val = help[mode[type][i]]; + if (val) found.push(val); + } + } else if (mode.helperType && help[mode.helperType]) { + found.push(help[mode.helperType]); + } else if (help[mode.name]) { + found.push(help[mode.name]); + } + for (var i = 0; i < help._global.length; i++) { + var cur = help._global[i]; + if (cur.pred(mode, this) && indexOf(found, cur.val) == -1) + found.push(cur.val); + } + return found; }, getStateAfter: function(line, precise) { @@ -3066,7 +3159,10 @@ window.CodeMirror = (function() { triggerOnKeyDown: operation(null, onKeyDown), - execCommand: function(cmd) {return commands[cmd](this);}, + execCommand: function(cmd) { + if (commands.hasOwnProperty(cmd)) + return commands[cmd](this); + }, findPosH: function(from, amount, unit, visually) { var dir = 1; @@ -3108,14 +3204,18 @@ window.CodeMirror = (function() { }, moveV: operation(null, function(dir, unit) { - var sel = this.doc.sel; - var pos = cursorCoords(this, sel.head, "div"); - if (sel.goalColumn != null) pos.left = sel.goalColumn; - var target = findPosV(this, pos, dir, unit); - - if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); + var sel = this.doc.sel, target, goal; + if (sel.shift || sel.extend || posEq(sel.from, sel.to)) { + var pos = cursorCoords(this, sel.head, "div"); + if (sel.goalColumn != null) pos.left = sel.goalColumn; + target = findPosV(this, pos, dir, unit); + if (unit == "page") addToScrollPos(this, 0, charCoords(this, target, "div").top - pos.top); + goal = pos.left; + } else { + target = dir < 0 ? sel.from : sel.to; + } extendSelection(this.doc, target, target, dir); - sel.goalColumn = pos.left; + if (goal != null) sel.goalColumn = goal; }), toggleOverwrite: function(value) { @@ -3137,17 +3237,23 @@ window.CodeMirror = (function() { clientHeight: scroller.clientHeight - co, clientWidth: scroller.clientWidth - co}; }, - scrollIntoView: operation(null, function(pos, margin) { - if (typeof pos == "number") pos = Pos(pos, 0); + scrollIntoView: operation(null, function(range, margin) { + if (range == null) range = {from: this.doc.sel.head, to: null}; + else if (typeof range == "number") range = {from: Pos(range, 0), to: null}; + else if (range.from == null) range = {from: range, to: null}; + if (!range.to) range.to = range.from; if (!margin) margin = 0; - var coords = pos; - if (!pos || pos.line != null) { - this.curOp.scrollToPos = pos ? clipPos(this.doc, pos) : this.doc.sel.head; - this.curOp.scrollToPosMargin = margin; - coords = cursorCoords(this, this.curOp.scrollToPos); + var coords = range; + if (range.from.line != null) { + this.curOp.scrollToPos = {from: range.from, to: range.to, margin: margin}; + coords = {from: cursorCoords(this, range.from), + to: cursorCoords(this, range.to)}; } - var sPos = calculateScrollPos(this, coords.left, coords.top - margin, coords.right, coords.bottom + margin); + var sPos = calculateScrollPos(this, Math.min(coords.from.left, coords.to.left), + Math.min(coords.from.top, coords.to.top) - margin, + Math.max(coords.from.right, coords.to.right), + Math.max(coords.from.bottom, coords.to.bottom) + margin); updateScrollPos(this, sPos.scrollLeft, sPos.scrollTop); }), @@ -3165,9 +3271,11 @@ window.CodeMirror = (function() { operation: function(f){return runInOp(this, f);}, refresh: operation(null, function() { + var badHeight = this.display.cachedTextHeight == null; clearCaches(this); updateScrollPos(this, this.doc.scrollLeft, this.doc.scrollTop); regChange(this); + if (badHeight) estimateLineHeights(this); }), swapDoc: operation(null, function(doc) { @@ -3177,6 +3285,7 @@ window.CodeMirror = (function() { clearCaches(this); resetInput(this, true); updateScrollPos(this, doc.scrollLeft, doc.scrollTop); + signalLater(this, "swapDoc", this, old); return old; }), @@ -3216,12 +3325,18 @@ window.CodeMirror = (function() { option("indentWithTabs", false); option("smartIndent", true); option("tabSize", 4, function(cm) { - loadMode(cm); + resetModeState(cm); clearCaches(cm); regChange(cm); }, true); + option("specialChars", /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\ufeff]/g, function(cm, val) { + cm.options.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g"); + cm.refresh(); + }, true); + option("specialCharPlaceholder", defaultSpecialCharPlaceholder, function(cm) {cm.refresh();}, true); option("electricChars", true); option("rtlMoveVisually", !windows); + option("wholeLineUpdateBefore", true); option("theme", "default", function(cm) { themeChanged(cm); @@ -3251,10 +3366,19 @@ window.CodeMirror = (function() { option("lineNumberFormatter", function(integer) {return integer;}, guttersChanged, true); option("showCursorWhenSelecting", false, updateSelection, true); + option("resetSelectionOnContextMenu", true); + option("readOnly", false, function(cm, val) { - if (val == "nocursor") {onBlur(cm); cm.display.input.blur();} - else if (!val) resetInput(cm, true); + if (val == "nocursor") { + onBlur(cm); + cm.display.input.blur(); + cm.display.disabled = true; + } else { + cm.display.disabled = false; + if (!val) resetInput(cm, true); + } }); + option("disableInput", false, function(cm, val) {if (!val) resetInput(cm, true);}, true); option("dragDrop", true); option("cursorBlinkRate", 530); @@ -3262,12 +3386,14 @@ window.CodeMirror = (function() { option("cursorHeight", 1); option("workTime", 100); option("workDelay", 100); - option("flattenSpans", true); + option("flattenSpans", true, resetModeState, true); + option("addModeClass", false, resetModeState, true); option("pollInterval", 100); option("undoDepth", 40, function(cm, val){cm.doc.history.undoDepth = val;}); option("historyEventDelay", 500); option("viewportMargin", 10, function(cm){cm.refresh();}, true); - option("maxHighlightLength", 10000, function(cm){loadMode(cm); cm.refresh();}, true); + option("maxHighlightLength", 10000, resetModeState, true); + option("crudeMeasuringFrom", 10000); option("moveInputWithCursor", true, function(cm, val) { if (!val) cm.display.inputDiv.style.top = cm.display.inputDiv.style.left = 0; }); @@ -3323,6 +3449,9 @@ window.CodeMirror = (function() { } } modeObj.name = spec.name; + if (spec.helperType) modeObj.helperType = spec.helperType; + if (spec.modeProps) for (var prop in spec.modeProps) + modeObj[prop] = spec.modeProps[prop]; return modeObj; }; @@ -3353,9 +3482,13 @@ window.CodeMirror = (function() { var helpers = CodeMirror.helpers = {}; CodeMirror.registerHelper = function(type, name, value) { - if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {}; + if (!helpers.hasOwnProperty(type)) helpers[type] = CodeMirror[type] = {_global: []}; helpers[type][name] = value; }; + CodeMirror.registerGlobalHelper = function(type, name, predicate, value) { + CodeMirror.registerHelper(type, name, value); + helpers[type]._global.push({pred: predicate, val: value}); + }; // UTILITIES @@ -3460,7 +3593,9 @@ window.CodeMirror = (function() { indentAuto: function(cm) {cm.indentSelection("smart");}, indentMore: function(cm) {cm.indentSelection("add");}, indentLess: function(cm) {cm.indentSelection("subtract");}, - insertTab: function(cm) {cm.replaceSelection("\t", "end", "+input");}, + insertTab: function(cm) { + cm.replaceSelection("\t", "end", "+input"); + }, defaultTab: function(cm) { if (cm.somethingSelected()) cm.indentSelection("add"); else cm.replaceSelection("\t", "end", "+input"); @@ -3486,7 +3621,8 @@ window.CodeMirror = (function() { keyMap.basic = { "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", - "Delete": "delCharAfter", "Backspace": "delCharBefore", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Delete": "delCharAfter", "Backspace": "delCharBefore", "Shift-Backspace": "delCharBefore", + "Tab": "defaultTab", "Shift-Tab": "indentAuto", "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" }; // Note that the save and find-related commands aren't defined by @@ -3632,11 +3768,12 @@ window.CodeMirror = (function() { this.string = string; this.tabSize = tabSize || 8; this.lastColumnPos = this.lastColumnValue = 0; + this.lineStart = 0; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, - sol: function() {return this.pos == 0;}, + sol: function() {return this.pos == this.lineStart;}, peek: function() {return this.string.charAt(this.pos) || undefined;}, next: function() { if (this.pos < this.string.length) @@ -3669,9 +3806,12 @@ window.CodeMirror = (function() { this.lastColumnValue = countColumn(this.string, this.start, this.tabSize, this.lastColumnPos, this.lastColumnValue); this.lastColumnPos = this.start; } - return this.lastColumnValue; + return this.lastColumnValue - (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); + }, + indentation: function() { + return countColumn(this.string, null, this.tabSize) - + (this.lineStart ? countColumn(this.string, this.lineStart, this.tabSize) : 0); }, - indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { var cased = function(str) {return caseInsensitive ? str.toLowerCase() : str;}; @@ -3687,7 +3827,12 @@ window.CodeMirror = (function() { return match; } }, - current: function(){return this.string.slice(this.start, this.pos);} + current: function(){return this.string.slice(this.start, this.pos);}, + hideFirstChars: function(n, inner) { + this.lineStart += n; + try { return inner(); } + finally { this.lineStart -= n; } + } }; CodeMirror.StringStream = StringStream; @@ -3739,7 +3884,7 @@ window.CodeMirror = (function() { if (withOp) endOperation(cm); }; - TextMarker.prototype.find = function() { + TextMarker.prototype.find = function(bothSides) { var from, to; for (var i = 0; i < this.lines.length; ++i) { var line = this.lines[i]; @@ -3750,7 +3895,7 @@ window.CodeMirror = (function() { if (span.to != null) to = Pos(found, span.to); } } - if (this.type == "bookmark") return from; + if (this.type == "bookmark" && !bothSides) return from; return from && {from: from, to: to}; }; @@ -3787,37 +3932,40 @@ window.CodeMirror = (function() { } }; + var nextMarkerId = 0; + function markText(doc, from, to, options, type) { if (options && options.shared) return markTextShared(doc, from, to, options, type); if (doc.cm && !doc.cm.curOp) return operation(doc.cm, markText)(doc, from, to, options, type); var marker = new TextMarker(doc, type); - if (type == "range" && !posLess(from, to)) return marker; if (options) copyObj(options, marker); + if (posLess(to, from) || posEq(from, to) && marker.clearWhenEmpty !== false) + return marker; if (marker.replacedWith) { marker.collapsed = true; marker.replacedWith = elt("span", [marker.replacedWith], "CodeMirror-widget"); if (!options.handleMouseEvents) marker.replacedWith.ignoreEvents = true; } - if (marker.collapsed) sawCollapsedSpans = true; + if (marker.collapsed) { + if (conflictingCollapsedRange(doc, from.line, from, to, marker) || + from.line != to.line && conflictingCollapsedRange(doc, to.line, from, to, marker)) + throw new Error("Inserting collapsed marker partially overlapping an existing one"); + sawCollapsedSpans = true; + } if (marker.addToHistory) addToHistory(doc, {from: from, to: to, origin: "markText"}, {head: doc.sel.head, anchor: doc.sel.anchor}, NaN); - var curLine = from.line, size = 0, collapsedAtStart, collapsedAtEnd, cm = doc.cm, updateMaxLine; + var curLine = from.line, cm = doc.cm, updateMaxLine; doc.iter(curLine, to.line + 1, function(line) { if (cm && marker.collapsed && !cm.options.lineWrapping && visualLine(doc, line) == cm.display.maxLine) updateMaxLine = true; var span = {from: null, to: null, marker: marker}; - size += line.text.length; - if (curLine == from.line) {span.from = from.ch; size -= from.ch;} - if (curLine == to.line) {span.to = to.ch; size -= line.text.length - to.ch;} - if (marker.collapsed) { - if (curLine == to.line) collapsedAtEnd = collapsedSpanAt(line, to.ch); - if (curLine == from.line) collapsedAtStart = collapsedSpanAt(line, from.ch); - else updateLineHeight(line, 0); - } + if (curLine == from.line) span.from = from.ch; + if (curLine == to.line) span.to = to.ch; + if (marker.collapsed && curLine != from.line) updateLineHeight(line, 0); addMarkedSpan(line, span); ++curLine; }); @@ -3833,9 +3981,7 @@ window.CodeMirror = (function() { doc.clearHistory(); } if (marker.collapsed) { - if (collapsedAtStart != collapsedAtEnd) - throw new Error("Inserting collapsed marker overlapping an existing one"); - marker.size = size; + marker.id = ++nextMarkerId; marker.atomic = true; } if (cm) { @@ -3908,7 +4054,7 @@ window.CodeMirror = (function() { if (old) for (var i = 0, nw; i < old.length; ++i) { var span = old[i], marker = span.marker; var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= startCh : span.from < startCh); - if (startsBefore || marker.type == "bookmark" && span.from == startCh && (!isInsert || !span.marker.insertLeft)) { + if (startsBefore || span.from == startCh && marker.type == "bookmark" && (!isInsert || !span.marker.insertLeft)) { var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= startCh : span.to > startCh); (nw || (nw = [])).push({from: span.from, to: endsAfter ? null : span.to, @@ -3922,7 +4068,7 @@ window.CodeMirror = (function() { if (old) for (var i = 0, nw; i < old.length; ++i) { var span = old[i], marker = span.marker; var endsAfter = span.to == null || (marker.inclusiveRight ? span.to >= endCh : span.to > endCh); - if (endsAfter || marker.type == "bookmark" && span.from == endCh && (!isInsert || span.marker.insertLeft)) { + if (endsAfter || span.from == endCh && marker.type == "bookmark" && (!isInsert || span.marker.insertLeft)) { var startsBefore = span.from == null || (marker.inclusiveLeft ? span.from <= endCh : span.from < endCh); (nw || (nw = [])).push({from: startsBefore ? null : span.from - endCh, to: span.to == null ? null : span.to - endCh, @@ -3972,13 +4118,9 @@ window.CodeMirror = (function() { } } } - if (sameLine && first) { - // Make sure we didn't create any zero-length spans - for (var i = 0; i < first.length; ++i) - if (first[i].from != null && first[i].from == first[i].to && first[i].marker.type != "bookmark") - first.splice(i--, 1); - if (!first.length) first = null; - } + // Make sure we didn't create any zero-length spans + if (first) first = clearEmptySpans(first); + if (last && last != first) last = clearEmptySpans(last); var newMarkers = [first]; if (!sameLine) { @@ -3995,6 +4137,16 @@ window.CodeMirror = (function() { return newMarkers; } + function clearEmptySpans(spans) { + for (var i = 0; i < spans.length; ++i) { + var span = spans[i]; + if (span.from != null && span.from == span.to && span.marker.clearWhenEmpty !== false) + spans.splice(i--, 1); + } + if (!spans.length) return null; + return spans; + } + function mergeOldSpans(doc, change) { var old = getOldSpans(doc, change); var stretched = stretchSpansOverChange(doc, change); @@ -4045,20 +4197,48 @@ window.CodeMirror = (function() { return parts; } - function collapsedSpanAt(line, ch) { + function extraLeft(marker) { return marker.inclusiveLeft ? -1 : 0; } + function extraRight(marker) { return marker.inclusiveRight ? 1 : 0; } + + function compareCollapsedMarkers(a, b) { + var lenDiff = a.lines.length - b.lines.length; + if (lenDiff != 0) return lenDiff; + var aPos = a.find(), bPos = b.find(); + var fromCmp = cmp(aPos.from, bPos.from) || extraLeft(a) - extraLeft(b); + if (fromCmp) return -fromCmp; + var toCmp = cmp(aPos.to, bPos.to) || extraRight(a) - extraRight(b); + if (toCmp) return toCmp; + return b.id - a.id; + } + + function collapsedSpanAtSide(line, start) { var sps = sawCollapsedSpans && line.markedSpans, found; if (sps) for (var sp, i = 0; i < sps.length; ++i) { sp = sps[i]; - if (!sp.marker.collapsed) continue; - if ((sp.from == null || sp.from < ch) && - (sp.to == null || sp.to > ch) && - (!found || found.width < sp.marker.width)) + if (sp.marker.collapsed && (start ? sp.from : sp.to) == null && + (!found || compareCollapsedMarkers(found, sp.marker) < 0)) found = sp.marker; } return found; } - function collapsedSpanAtStart(line) { return collapsedSpanAt(line, -1); } - function collapsedSpanAtEnd(line) { return collapsedSpanAt(line, line.text.length + 1); } + function collapsedSpanAtStart(line) { return collapsedSpanAtSide(line, true); } + function collapsedSpanAtEnd(line) { return collapsedSpanAtSide(line, false); } + + function conflictingCollapsedRange(doc, lineNo, from, to, marker) { + var line = getLine(doc, lineNo); + var sps = sawCollapsedSpans && line.markedSpans; + if (sps) for (var i = 0; i < sps.length; ++i) { + var sp = sps[i]; + if (!sp.marker.collapsed) continue; + var found = sp.marker.find(true); + var fromCmp = cmp(found.from, from) || extraLeft(sp.marker) - extraLeft(marker); + var toCmp = cmp(found.to, to) || extraRight(sp.marker) - extraRight(marker); + if (fromCmp >= 0 && toCmp <= 0 || fromCmp <= 0 && toCmp >= 0) continue; + if (fromCmp <= 0 && (cmp(found.to, from) || extraRight(sp.marker) - extraLeft(marker)) > 0 || + fromCmp >= 0 && (cmp(found.from, to) || extraLeft(sp.marker) - extraRight(marker)) < 0) + return true; + } + } function visualLine(doc, line) { var merged; @@ -4088,6 +4268,7 @@ window.CodeMirror = (function() { for (var sp, i = 0; i < line.markedSpans.length; ++i) { sp = line.markedSpans[i]; if (sp.marker.collapsed && !sp.marker.replacedWith && sp.from == span.to && + (sp.to == null || sp.to != span.from) && (sp.marker.inclusiveLeft || span.marker.inclusiveRight) && lineIsHiddenInner(doc, line, sp)) return true; } @@ -4181,6 +4362,7 @@ window.CodeMirror = (function() { this.height = estimateHeight ? estimateHeight(this) : 1; }; eventMixin(Line); + Line.prototype.lineNo = function() { return lineNo(this); }; function updateLine(line, text, markedSpans, estimateHeight) { line.text = text; @@ -4201,7 +4383,7 @@ window.CodeMirror = (function() { // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS // classes. - function runMode(cm, text, mode, state, f) { + function runMode(cm, text, mode, state, f, forceToEnd) { var flattenSpans = mode.flattenSpans; if (flattenSpans == null) flattenSpans = cm.options.flattenSpans; var curStart = 0, curStyle = null; @@ -4210,27 +4392,38 @@ window.CodeMirror = (function() { while (!stream.eol()) { if (stream.pos > cm.options.maxHighlightLength) { flattenSpans = false; - // Webkit seems to refuse to render text nodes longer than 57444 characters - stream.pos = Math.min(text.length, stream.start + 50000); + if (forceToEnd) processLine(cm, text, state, stream.pos); + stream.pos = text.length; style = null; } else { style = mode.token(stream, state); } + if (cm.options.addModeClass) { + var mName = CodeMirror.innerMode(mode, state).mode.name; + if (mName) style = "m-" + (style ? mName + " " + style : mName); + } if (!flattenSpans || curStyle != style) { if (curStart < stream.start) f(stream.start, curStyle); curStart = stream.start; curStyle = style; } stream.start = stream.pos; } - if (curStart < stream.pos) f(stream.pos, curStyle); + while (curStart < stream.pos) { + // Webkit seems to refuse to render text nodes longer than 57444 characters + var pos = Math.min(stream.pos, curStart + 50000); + f(pos, curStyle); + curStart = pos; + } } - function highlightLine(cm, line, state) { + function highlightLine(cm, line, state, forceToEnd) { // A styles array always starts with a number identifying the // mode/overlays that it is based on (for easy invalidation). var st = [cm.state.modeGen]; // Compute the base array of styles - runMode(cm, line.text, cm.doc.mode, state, function(end, style) {st.push(end, style);}); + runMode(cm, line.text, cm.doc.mode, state, function(end, style) { + st.push(end, style); + }, forceToEnd); // Run overlays, adjust style array. for (var o = 0; o < cm.state.overlays.length; ++o) { @@ -4269,24 +4462,36 @@ window.CodeMirror = (function() { // Lightweight form of highlight -- proceed over this line and // update state, but don't save a style array. - function processLine(cm, line, state) { + function processLine(cm, text, state, startAt) { var mode = cm.doc.mode; - var stream = new StringStream(line.text, cm.options.tabSize); - if (line.text == "" && mode.blankLine) mode.blankLine(state); + var stream = new StringStream(text, cm.options.tabSize); + stream.start = stream.pos = startAt || 0; + if (text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol() && stream.pos <= cm.options.maxHighlightLength) { mode.token(stream, state); stream.start = stream.pos; } } - var styleToClassCache = {}; - function styleToClass(style) { + var styleToClassCache = {}, styleToClassCacheWithMode = {}; + function interpretTokenStyle(style, builder) { if (!style) return null; - return styleToClassCache[style] || - (styleToClassCache[style] = "cm-" + style.replace(/ +/g, " cm-")); + for (;;) { + var lineClass = style.match(/(?:^|\s)line-(background-)?(\S+)/); + if (!lineClass) break; + style = style.slice(0, lineClass.index) + style.slice(lineClass.index + lineClass[0].length); + var prop = lineClass[1] ? "bgClass" : "textClass"; + if (builder[prop] == null) + builder[prop] = lineClass[2]; + else if (!(new RegExp("(?:^|\s)" + lineClass[2] + "(?:$|\s)")).test(builder[prop])) + builder[prop] += " " + lineClass[2]; + } + var cache = builder.cm.options.addModeClass ? styleToClassCacheWithMode : styleToClassCache; + return cache[style] || + (cache[style] = "cm-" + style.replace(/ +/g, " cm-")); } - function lineContent(cm, realLine, measure, copyWidgets) { + function buildLineContent(cm, realLine, measure, copyWidgets) { var merged, line = realLine, empty = true; while (merged = collapsedSpanAtStart(line)) line = getLine(cm.doc, merged.find().from.line); @@ -4294,14 +4499,13 @@ window.CodeMirror = (function() { var builder = {pre: elt("pre"), col: 0, pos: 0, measure: null, measuredSomething: false, cm: cm, copyWidgets: copyWidgets}; - if (line.textClass) builder.pre.className = line.textClass; do { if (line.text) empty = false; builder.measure = line == realLine && measure; builder.pos = 0; builder.addToken = builder.measure ? buildTokenMeasure : buildToken; - if ((ie || webkit) && cm.getOption("lineWrapping")) + if ((old_ie || webkit) && cm.getOption("lineWrapping")) builder.addToken = buildTokenSplitSpaces(builder.addToken); var next = insertLineContent(line, builder, getLineStyles(cm, line)); if (measure && line == realLine && !builder.measuredSomething) { @@ -4331,21 +4535,30 @@ window.CodeMirror = (function() { } } + var textClass = builder.textClass ? builder.textClass + " " + (realLine.textClass || "") : realLine.textClass; + if (textClass) builder.pre.className = textClass; + signal(cm, "renderLine", cm, realLine, builder.pre); - return builder.pre; + return builder; + } + + function defaultSpecialCharPlaceholder(ch) { + var token = elt("span", "\u2022", "cm-invalidchar"); + token.title = "\\u" + ch.charCodeAt(0).toString(16); + return token; } - var tokenSpecialChars = /[\t\u0000-\u0019\u00ad\u200b\u2028\u2029\uFEFF]/g; function buildToken(builder, text, style, startStyle, endStyle, title) { if (!text) return; - if (!tokenSpecialChars.test(text)) { + var special = builder.cm.options.specialChars; + if (!special.test(text)) { builder.col += text.length; var content = document.createTextNode(text); } else { var content = document.createDocumentFragment(), pos = 0; while (true) { - tokenSpecialChars.lastIndex = pos; - var m = tokenSpecialChars.exec(text); + special.lastIndex = pos; + var m = special.exec(text); var skipped = m ? m.index - pos : text.length - pos; if (skipped) { content.appendChild(document.createTextNode(text.slice(pos, pos + skipped))); @@ -4358,8 +4571,7 @@ window.CodeMirror = (function() { content.appendChild(elt("span", spaceStr(tabWidth), "cm-tab")); builder.col += tabWidth; } else { - var token = elt("span", "\u2022", "cm-invalidchar"); - token.title = "\\u" + m[0].charCodeAt(0).toString(16); + var token = builder.cm.options.specialCharPlaceholder(m[0]); content.appendChild(token); builder.col += 1; } @@ -4379,13 +4591,12 @@ window.CodeMirror = (function() { function buildTokenMeasure(builder, text, style, startStyle, endStyle) { var wrapping = builder.cm.options.lineWrapping; for (var i = 0; i < text.length; ++i) { - var ch = text.charAt(i), start = i == 0; - if (ch >= "\ud800" && ch < "\udbff" && i < text.length - 1) { - ch = text.slice(i, i + 2); - ++i; - } else if (i && wrapping && spanAffectsWrapping(text, i)) { + var start = i == 0, to = i + 1; + while (to < text.length && isExtendingChar(text.charAt(to))) ++to; + var ch = text.slice(i, to); + i = to - 1; + if (i && wrapping && spanAffectsWrapping(text, i)) builder.pre.appendChild(elt("wbr")); - } var old = builder.measure[builder.pos]; var span = builder.measure[builder.pos] = buildToken(builder, ch, style, @@ -4394,7 +4605,7 @@ window.CodeMirror = (function() { // In IE single-space nodes wrap differently than spaces // embedded in larger text nodes, except when set to // white-space: normal (issue #1268). - if (ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && + if (old_ie && wrapping && ch == " " && i && !/\s/.test(text.charAt(i - 1)) && i < text.length - 1 && !/\s/.test(text.charAt(i + 1))) span.style.whiteSpace = "normal"; builder.pos += ch.length; @@ -4410,7 +4621,7 @@ window.CodeMirror = (function() { return out; } return function(builder, text, style, startStyle, endStyle, title) { - return inner(builder, text.replace(/ {3,}/, split), style, startStyle, endStyle, title); + return inner(builder, text.replace(/ {3,}/g, split), style, startStyle, endStyle, title); }; } @@ -4443,7 +4654,7 @@ window.CodeMirror = (function() { var spans = line.markedSpans, allText = line.text, at = 0; if (!spans) { for (var i = 1; i < styles.length; i+=2) - builder.addToken(builder, allText.slice(at, at = styles[i]), styleToClass(styles[i+1])); + builder.addToken(builder, allText.slice(at, at = styles[i]), interpretTokenStyle(styles[i+1], builder)); return; } @@ -4462,7 +4673,7 @@ window.CodeMirror = (function() { if (m.startStyle && sp.from == pos) spanStartStyle += " " + m.startStyle; if (m.endStyle && sp.to == nextChange) spanEndStyle += " " + m.endStyle; if (m.title && !title) title = m.title; - if (m.collapsed && (!collapsed || collapsed.marker.size < m.size)) + if (m.collapsed && (!collapsed || compareCollapsedMarkers(collapsed.marker, m) < 0)) collapsed = sp; } else if (sp.from > pos && nextChange > sp.from) { nextChange = sp.from; @@ -4493,7 +4704,7 @@ window.CodeMirror = (function() { spanStartStyle = ""; } text = allText.slice(at, at = styles[i++]); - style = styleToClass(styles[i++]); + style = interpretTokenStyle(styles[i++], builder); } } } @@ -4512,7 +4723,8 @@ window.CodeMirror = (function() { var lastText = lst(text), lastSpans = spansFor(text.length - 1), nlines = to.line - from.line; // First adjust the line structure - if (from.ch == 0 && to.ch == 0 && lastText == "") { + if (from.ch == 0 && to.ch == 0 && lastText == "" && + (!doc.cm || doc.cm.options.wholeLineUpdateBefore)) { // This is a whole-line replace. Treated specially to make // sure line objects move the way they are supposed to. for (var i = 0, e = text.length - 1, added = []; i < e; ++i) @@ -4794,10 +5006,11 @@ window.CodeMirror = (function() { clearHistory: function() {this.history = makeHistory(this.history.maxGeneration);}, markClean: function() { - this.cleanGeneration = this.changeGeneration(); + this.cleanGeneration = this.changeGeneration(true); }, - changeGeneration: function() { - this.history.lastOp = this.history.lastOrigin = null; + changeGeneration: function(forceSplit) { + if (forceSplit) + this.history.lastOp = this.history.lastOrigin = null; return this.history.generation; }, isClean: function (gen) { @@ -4819,7 +5032,8 @@ window.CodeMirror = (function() { }, setBookmark: function(pos, options) { var realOpts = {replacedWith: options && (options.nodeType == null ? options.widget : options), - insertLeft: options && options.insertLeft}; + insertLeft: options && options.insertLeft, + clearWhenEmpty: false}; pos = clipPos(this, pos); return markText(this, pos, pos, realOpts, "bookmark"); }, @@ -5100,10 +5314,10 @@ window.CodeMirror = (function() { anchorBefore: doc.sel.anchor, headBefore: doc.sel.head, anchorAfter: selAfter.anchor, headAfter: selAfter.head}; hist.done.push(cur); - hist.generation = ++hist.maxGeneration; while (hist.done.length > hist.undoDepth) hist.done.shift(); } + hist.generation = ++hist.maxGeneration; hist.lastTime = time; hist.lastOp = opId; hist.lastOrigin = change.origin; @@ -5399,7 +5613,8 @@ window.CodeMirror = (function() { return true; } - var isExtendingChar = /[\u0300-\u036F\u0483-\u0487\u0488-\u0489\u0591-\u05BD\u05BF\u05C1-\u05C2\u05C4-\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7-\u06E8\u06EA-\u06ED\uA66F\uA670-\uA672\uA674-\uA67D\uA69F\udc00-\udfff]/; + var extendingChars = /[\u0300-\u036f\u0483-\u0489\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u065e\u0670\u06d6-\u06dc\u06de-\u06e4\u06e7\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0900-\u0902\u093c\u0941-\u0948\u094d\u0951-\u0955\u0962\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2\u09e3\u0a01\u0a02\u0a3c\u0a41\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a70\u0a71\u0a75\u0a81\u0a82\u0abc\u0ac1-\u0ac5\u0ac7\u0ac8\u0acd\u0ae2\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57\u0b62\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0d3e\u0d41-\u0d44\u0d4d\u0d57\u0d62\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9\u0ebb\u0ebc\u0ec8-\u0ecd\u0f18\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86\u0f87\u0f90-\u0f97\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039\u103a\u103d\u103e\u1058\u1059\u105e-\u1060\u1071-\u1074\u1082\u1085\u1086\u108d\u109d\u135f\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927\u1928\u1932\u1939-\u193b\u1a17\u1a18\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1b00-\u1b03\u1b34\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80\u1b81\u1ba2-\u1ba5\u1ba8\u1ba9\u1c2c-\u1c33\u1c36\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1dc0-\u1de6\u1dfd-\u1dff\u200c\u200d\u20d0-\u20f0\u2cef-\u2cf1\u2de0-\u2dff\u302a-\u302f\u3099\u309a\ua66f-\ua672\ua67c\ua67d\ua6f0\ua6f1\ua802\ua806\ua80b\ua825\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc\uaa29-\uaa2e\uaa31\uaa32\uaa35\uaa36\uaa43\uaa4c\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uabe5\uabe8\uabed\udc00-\udfff\ufb1e\ufe00-\ufe0f\ufe20-\ufe26\uff9e\uff9f]/; + function isExtendingChar(ch) { return ch.charCodeAt(0) >= 768 && extendingChars.test(ch); } // DOM UTILITIES @@ -5461,13 +5676,18 @@ window.CodeMirror = (function() { spanAffectsWrapping = function(str, i) { return /\-[^ \-?]|\?[^ !\'\"\),.\-\/:;\?\]\}]/.test(str.slice(i - 1, i + 1)); }; - else if (webkit && !/Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + else if (webkit && /Chrome\/(?:29|[3-9]\d|\d\d\d)\./.test(navigator.userAgent)) + spanAffectsWrapping = function(str, i) { + var code = str.charCodeAt(i - 1); + return code >= 8208 && code <= 8212; + }; + else if (webkit) spanAffectsWrapping = function(str, i) { if (i > 1 && str.charCodeAt(i - 1) == 45) { if (/\w/.test(str.charAt(i - 2)) && /[^\-?\.]/.test(str.charAt(i))) return true; if (i > 2 && /[\d\.,]/.test(str.charAt(i - 2)) && /[\d\.,]/.test(str.charAt(i))) return false; } - return /[~!#%&*)=+}\]|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|…[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); + return /[~!#%&*)=+}\]\\|\"\.>,:;][({[<]|-[^\-?\.\u2010-\u201f\u2026]|\?[\w~`@#$%\^&*(_=+{[|><]|\u2026[\w~`@#$%\^&*(_=+{[><]/.test(str.slice(i - 1, i + 1)); }; var knownScrollbarWidth; @@ -5535,14 +5755,14 @@ window.CodeMirror = (function() { var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", - 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", - 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", - 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", - 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; + 46: "Delete", 59: ";", 61: "=", 91: "Mod", 92: "Mod", 93: "Mod", 107: "=", 109: "-", 127: "Delete", + 173: "-", 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63232: "Up", 63233: "Down", 63234: "Left", 63235: "Right", 63272: "Delete", + 63273: "Home", 63275: "End", 63276: "PageUp", 63277: "PageDown", 63302: "Insert"}; CodeMirror.keyNames = keyNames; (function() { // Number keys - for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + for (var i = 0; i < 10; i++) keyNames[i + 48] = keyNames[i + 96] = String(i); // Alphabetic keys for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); // Function keys @@ -5599,29 +5819,29 @@ window.CodeMirror = (function() { } var bidiOther; function getBidiPartAt(order, pos) { + bidiOther = null; for (var i = 0, found; i < order.length; ++i) { var cur = order[i]; - if (cur.from < pos && cur.to > pos) { bidiOther = null; return i; } - if (cur.from == pos || cur.to == pos) { + if (cur.from < pos && cur.to > pos) return i; + if ((cur.from == pos || cur.to == pos)) { if (found == null) { found = i; } else if (compareBidiLevel(order, cur.level, order[found].level)) { - bidiOther = found; + if (cur.from != cur.to) bidiOther = found; return i; } else { - bidiOther = i; + if (cur.from != cur.to) bidiOther = i; return found; } } } - bidiOther = null; return found; } function moveInLine(line, pos, dir, byUnit) { if (!byUnit) return pos + dir; do pos += dir; - while (pos > 0 && isExtendingChar.test(line.text.charAt(pos))); + while (pos > 0 && isExtendingChar(line.text.charAt(pos))); return pos; } @@ -5656,7 +5876,7 @@ window.CodeMirror = (function() { function moveLogically(line, start, dir, byUnit) { var target = start + dir; - if (byUnit) while (target > 0 && isExtendingChar.test(line.text.charAt(target))) target += dir; + if (byUnit) while (target > 0 && isExtendingChar(line.text.charAt(target))) target += dir; return target < 0 || target > line.text.length ? null : target; } @@ -5748,7 +5968,7 @@ window.CodeMirror = (function() { if (type == ",") types[i] = "N"; else if (type == "%") { for (var end = i + 1; end < len && types[end] == "%"; ++end) {} - var replace = (i && types[i-1] == "!") || (end < len - 1 && types[end] == "1") ? "1" : "N"; + var replace = (i && types[i-1] == "!") || (end < len && types[end] == "1") ? "1" : "N"; for (var j = i; j < end; ++j) types[j] = replace; i = end - 1; } @@ -5773,7 +5993,7 @@ window.CodeMirror = (function() { if (isNeutral.test(types[i])) { for (var end = i + 1; end < len && isNeutral.test(types[end]); ++end) {} var before = (i ? types[i-1] : outerType) == "L"; - var after = (end < len - 1 ? types[end] : outerType) == "L"; + var after = (end < len ? types[end] : outerType) == "L"; var replace = before || after ? "L" : "R"; for (var j = i; j < end; ++j) types[j] = replace; i = end - 1; @@ -5823,7 +6043,7 @@ window.CodeMirror = (function() { // THE END - CodeMirror.version = "3.16.0"; + CodeMirror.version = "3.21.0"; return CodeMirror; })(); diff --git a/gulliver/js/codemirror/mode/apl/index.html b/gulliver/js/codemirror/mode/apl/index.html index 119ff17f1..f8282ac42 100644 --- a/gulliver/js/codemirror/mode/apl/index.html +++ b/gulliver/js/codemirror/mode/apl/index.html @@ -1,20 +1,32 @@ - - - - CodeMirror: APL mode - - - - - - - - -

CodeMirror: APL mode

+ + +
+

APL mode

+
+ +

C++ example

+ +
+ +

Java example

+ +

Simple mode that tries to handle C-like languages as well as it @@ -99,5 +192,4 @@ void* zmq_thread_init(void* zmq_context, int signal_fd) { (C code), text/x-c++src (C++ code), text/x-java (Java code), text/x-csharp (C#).

- - + diff --git a/gulliver/js/codemirror/mode/clike/scala.html b/gulliver/js/codemirror/mode/clike/scala.html index f3c7eea49..e9acc049b 100644 --- a/gulliver/js/codemirror/mode/clike/scala.html +++ b/gulliver/js/codemirror/mode/clike/scala.html @@ -1,29 +1,30 @@ - - - - CodeMirror: C-like mode - - - - - - - - - + +CodeMirror: Scala mode + + + + + + + + + + +
+

Scala mode

+ + +

The LESS mode is a sub-mode of the CSS mode (defined in css.js.

+ +

Parsing/Highlighting Tests: normal, verbose.

+
diff --git a/gulliver/js/codemirror/mode/css/less_test.js b/gulliver/js/codemirror/mode/css/less_test.js new file mode 100644 index 000000000..ea64f91d1 --- /dev/null +++ b/gulliver/js/codemirror/mode/css/less_test.js @@ -0,0 +1,48 @@ +(function() { + "use strict"; + + var mode = CodeMirror.getMode({indentUnit: 2}, "text/x-less"); + function MT(name) { test.mode(name, mode, Array.prototype.slice.call(arguments, 1), "less"); } + + MT("variable", + "[variable-2 @base]: [atom #f04615];", + "[qualifier .class] {", + " [property width]: [variable percentage]([number 0.5]); [comment // returns `50%`]", + " [property color]: [variable saturate]([variable-2 @base], [number 5%]);", + "}"); + + MT("amp", + "[qualifier .child], [qualifier .sibling] {", + " [qualifier .parent] [atom &] {", + " [property color]: [keyword black];", + " }", + " [atom &] + [atom &] {", + " [property color]: [keyword red];", + " }", + "}"); + + MT("mixin", + "[qualifier .mixin] ([variable dark]; [variable-2 @color]) {", + " [property color]: [variable darken]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable light]; [variable-2 @color]) {", + " [property color]: [variable lighten]([variable-2 @color], [number 10%]);", + "}", + "[qualifier .mixin] ([variable-2 @_]; [variable-2 @color]) {", + " [property display]: [atom block];", + "}", + "[variable-2 @switch]: [variable light];", + "[qualifier .class] {", + " [qualifier .mixin]([variable-2 @switch]; [atom #888]);", + "}"); + + MT("nest", + "[qualifier .one] {", + " [def @media] ([property width]: [number 400px]) {", + " [property font-size]: [number 1.2em];", + " [def @media] [attribute print] [keyword and] [property color] {", + " [property color]: [keyword blue];", + " }", + " }", + "}"); +})(); diff --git a/gulliver/js/codemirror/mode/css/scss.html b/gulliver/js/codemirror/mode/css/scss.html index b90cbe876..0677d08fe 100644 --- a/gulliver/js/codemirror/mode/css/scss.html +++ b/gulliver/js/codemirror/mode/css/scss.html @@ -1,17 +1,30 @@ - - - - CodeMirror: SCSS mode - - - - - - - -

CodeMirror: SCSS mode

-
- - - -

MIME types defined: text/x-ocaml.

diff --git a/gulliver/js/codemirror/mode/ocaml/ocaml.js b/gulliver/js/codemirror/mode/ocaml/ocaml.js deleted file mode 100644 index 2ce3fb8f0..000000000 --- a/gulliver/js/codemirror/mode/ocaml/ocaml.js +++ /dev/null @@ -1,113 +0,0 @@ -CodeMirror.defineMode('ocaml', function() { - - var words = { - 'true': 'atom', - 'false': 'atom', - 'let': 'keyword', - 'rec': 'keyword', - 'in': 'keyword', - 'of': 'keyword', - 'and': 'keyword', - 'succ': 'keyword', - 'if': 'keyword', - 'then': 'keyword', - 'else': 'keyword', - 'for': 'keyword', - 'to': 'keyword', - 'while': 'keyword', - 'do': 'keyword', - 'done': 'keyword', - 'fun': 'keyword', - 'function': 'keyword', - 'val': 'keyword', - 'type': 'keyword', - 'mutable': 'keyword', - 'match': 'keyword', - 'with': 'keyword', - 'try': 'keyword', - 'raise': 'keyword', - 'begin': 'keyword', - 'end': 'keyword', - 'open': 'builtin', - 'trace': 'builtin', - 'ignore': 'builtin', - 'exit': 'builtin', - 'print_string': 'builtin', - 'print_endline': 'builtin' - }; - - function tokenBase(stream, state) { - var ch = stream.next(); - - if (ch === '"') { - state.tokenize = tokenString; - return state.tokenize(stream, state); - } - if (ch === '(') { - if (stream.eat('*')) { - state.commentLevel++; - state.tokenize = tokenComment; - return state.tokenize(stream, state); - } - } - if (ch === '~') { - stream.eatWhile(/\w/); - return 'variable-2'; - } - if (ch === '`') { - stream.eatWhile(/\w/); - return 'quote'; - } - if (/\d/.test(ch)) { - stream.eatWhile(/[\d]/); - if (stream.eat('.')) { - stream.eatWhile(/[\d]/); - } - return 'number'; - } - if ( /[+\-*&%=<>!?|]/.test(ch)) { - return 'operator'; - } - stream.eatWhile(/\w/); - var cur = stream.current(); - return words[cur] || 'variable'; - } - - function tokenString(stream, state) { - var next, end = false, escaped = false; - while ((next = stream.next()) != null) { - if (next === '"' && !escaped) { - end = true; - break; - } - escaped = !escaped && next === '\\'; - } - if (end && !escaped) { - state.tokenize = tokenBase; - } - return 'string'; - }; - - function tokenComment(stream, state) { - var prev, next; - while(state.commentLevel > 0 && (next = stream.next()) != null) { - if (prev === '(' && next === '*') state.commentLevel++; - if (prev === '*' && next === ')') state.commentLevel--; - prev = next; - } - if (state.commentLevel <= 0) { - state.tokenize = tokenBase; - } - return 'comment'; - } - - return { - startState: function() {return {tokenize: tokenBase, commentLevel: 0};}, - token: function(stream, state) { - if (stream.eatSpace()) return null; - return state.tokenize(stream, state); - } - }; -}); - -CodeMirror.defineMIME('text/x-ocaml', 'ocaml'); diff --git a/gulliver/js/codemirror/mode/pascal/LICENSE b/gulliver/js/codemirror/mode/pascal/LICENSE deleted file mode 100644 index 8e3747e74..000000000 --- a/gulliver/js/codemirror/mode/pascal/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2011 souceLair - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/gulliver/js/codemirror/mode/pascal/index.html b/gulliver/js/codemirror/mode/pascal/index.html index b3016afb1..bf8ff0d63 100644 --- a/gulliver/js/codemirror/mode/pascal/index.html +++ b/gulliver/js/codemirror/mode/pascal/index.html @@ -1,16 +1,30 @@ - - - - CodeMirror: Pascal mode - - - - - - - -

CodeMirror: Pascal mode

+ +CodeMirror: Pascal mode + + + + + + + + + +
+

Pascal mode

+
+ + +

Cython mode

+ +
+ -

Configuration Options:

+

Configuration Options for Python mode:

  • version - 2/3 - The version of Python to recognize. Default is 2.
  • singleLineStringErrors - true/false - If you have a single-line string that is not terminated at the end of the line, this will show subsequent lines as errors if true, otherwise it will consider the newline as the end of the string. Default is false.
  • +
  • hangingIndent - int - If you want to write long arguments to a function starting on a new line, how much that line should be indented. Defaults to one normal indentation unit.

Advanced Configuration Options:

Usefull for superset of python syntax like Enthought enaml, IPython magics and questionmark help

@@ -127,9 +179,10 @@ class ExampleClass(ParentClass):
  • doubleDelimiters - RegEx - Regular Expressoin for double delimiters matching, default :
    ^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))
  • tripleDelimiters - RegEx - Regular Expression for triple delimiters matching, default :
    ^((//=)|(>>=)|(<<=)|(\\*\\*=))
  • identifiers - RegEx - Regular Expression for identifier, default :
    ^[_A-Za-z][_A-Za-z0-9]*
  • +
  • extra_keywords - list of string - List of extra words ton consider as keywords
  • +
  • extra_builtins - list of string - List of extra words ton consider as builtins
  • -

    MIME types defined: text/x-python.

    - - +

    MIME types defined: text/x-python and text/x-cython.

    +
    diff --git a/gulliver/js/codemirror/mode/python/python.js b/gulliver/js/codemirror/mode/python/python.js index 951aaf819..8bea5d19d 100644 --- a/gulliver/js/codemirror/mode/python/python.js +++ b/gulliver/js/codemirror/mode/python/python.js @@ -4,13 +4,14 @@ CodeMirror.defineMode("python", function(conf, parserConf) { function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b"); } - + var singleOperators = parserConf.singleOperators || new RegExp("^[\\+\\-\\*/%&|\\^~<>!]"); var singleDelimiters = parserConf.singleDelimiters || new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); var doubleOperators = parserConf.doubleOperators || new RegExp("^((==)|(!=)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); var doubleDelimiters = parserConf.doubleDelimiters || new RegExp("^((\\+=)|(\\-=)|(\\*=)|(%=)|(/=)|(&=)|(\\|=)|(\\^=))"); var tripleDelimiters = parserConf.tripleDelimiters || new RegExp("^((//=)|(>>=)|(<<=)|(\\*\\*=))"); var identifiers = parserConf.identifiers|| new RegExp("^[_A-Za-z][_A-Za-z0-9]*"); + var hangingIndent = parserConf.hangingIndent || parserConf.indentUnit; var wordOperators = wordRegexp(['and', 'or', 'not', 'is', 'in']); var commonkeywords = ['as', 'assert', 'break', 'class', 'continue', @@ -36,6 +37,12 @@ CodeMirror.defineMode("python", function(conf, parserConf) { var py3 = {'builtins': ['ascii', 'bytes', 'exec', 'print'], 'keywords': ['nonlocal', 'False', 'True', 'None']}; + if(parserConf.extra_keywords != undefined){ + commonkeywords = commonkeywords.concat(parserConf.extra_keywords); + } + if(parserConf.extra_builtins != undefined){ + commonBuiltins = commonBuiltins.concat(parserConf.extra_builtins); + } if (!!parserConf.version && parseInt(parserConf.version, 10) === 3) { commonkeywords = commonkeywords.concat(py3.keywords); commonBuiltins = commonBuiltins.concat(py3.builtins); @@ -72,15 +79,15 @@ CodeMirror.defineMode("python", function(conf, parserConf) { if (stream.eatSpace()) { return null; } - + var ch = stream.peek(); - + // Handle Comments if (ch === '#') { stream.skipToEnd(); return 'comment'; } - + // Handle Number Literals if (stream.match(/^[0-9\.]/, false)) { var floatLiteral = false; @@ -116,13 +123,13 @@ CodeMirror.defineMode("python", function(conf, parserConf) { return 'number'; } } - + // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()); return state.tokenize(stream, state); } - + // Handle operators and Delimiters if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { return null; @@ -135,31 +142,34 @@ CodeMirror.defineMode("python", function(conf, parserConf) { if (stream.match(singleDelimiters)) { return null; } - + if (stream.match(keywords)) { return 'keyword'; } - + if (stream.match(builtins)) { return 'builtin'; } - + if (stream.match(identifiers)) { + if (state.lastToken == 'def' || state.lastToken == 'class') { + return 'def'; + } return 'variable'; } - + // Handle non-detected items stream.next(); return ERRORCLASS; } - + function tokenStringFactory(delimiter) { while ('rub'.indexOf(delimiter.charAt(0).toLowerCase()) >= 0) { delimiter = delimiter.substr(1); } var singleline = delimiter.length == 1; var OUTCLASS = 'string'; - + function tokenString(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"\\]/); @@ -187,7 +197,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) { tokenString.isString = true; return tokenString; } - + function indent(stream, state, type) { type = type || 'py'; var indentUnit = 0; @@ -202,6 +212,11 @@ CodeMirror.defineMode("python", function(conf, parserConf) { break; } } + } else if (stream.match(/\s*($|#)/, false)) { + // An open paren/bracket/brace with only space or comments after it + // on the line will indent the next line a fixed amount, to make it + // easier to put arguments, list items, etc. on their own lines. + indentUnit = stream.indentation() + hangingIndent; } else { indentUnit = stream.column() + stream.current().length; } @@ -210,7 +225,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) { type: type }); } - + function dedent(stream, state, type) { type = type || 'py'; if (state.scopes.length == 1) return; @@ -252,24 +267,24 @@ CodeMirror.defineMode("python", function(conf, parserConf) { // Handle '.' connected identifiers if (current === '.') { style = stream.match(identifiers, false) ? null : ERRORCLASS; - if (style === null && state.lastToken === 'meta') { + if (style === null && state.lastStyle === 'meta') { // Apply 'meta' style to '.' connected identifiers when // appropriate. style = 'meta'; } return style; } - + // Handle decorators if (current === '@') { return stream.match(identifiers, false) ? 'meta' : ERRORCLASS; } if ((style === 'variable' || style === 'builtin') - && state.lastToken === 'meta') { + && state.lastStyle === 'meta') { style = 'meta'; } - + // Handle scope changes. if (current === 'pass' || current === 'return') { state.dedent += 1; @@ -298,7 +313,7 @@ CodeMirror.defineMode("python", function(conf, parserConf) { if (state.scopes.length > 1) state.scopes.shift(); state.dedent -= 1; } - + return style; } @@ -307,34 +322,53 @@ CodeMirror.defineMode("python", function(conf, parserConf) { return { tokenize: tokenBase, scopes: [{offset:basecolumn || 0, type:'py'}], + lastStyle: null, lastToken: null, lambda: false, dedent: 0 }; }, - + token: function(stream, state) { var style = tokenLexer(stream, state); - - state.lastToken = style; - - if (stream.eol() && stream.lambda) { + + state.lastStyle = style; + + var current = stream.current(); + if (current && style) { + state.lastToken = current; + } + + if (stream.eol() && state.lambda) { state.lambda = false; } - return style; }, - + indent: function(state) { if (state.tokenize != tokenBase) { return state.tokenize.isString ? CodeMirror.Pass : 0; } - + return state.scopes[0].offset; - } - + }, + + lineComment: "#", + fold: "indent" }; return external; }); CodeMirror.defineMIME("text/x-python", "python"); + +(function() { + "use strict"; + var words = function(str){return str.split(' ');}; + + CodeMirror.defineMIME("text/x-cython", { + name: "python", + extra_keywords: words("by cdef cimport cpdef ctypedef enum except"+ + "extern gil include nogil property public"+ + "readonly struct union DEF IF ELIF ELSE") + }); +})(); diff --git a/gulliver/js/codemirror/mode/q/index.html b/gulliver/js/codemirror/mode/q/index.html index 303ec1d3a..78ed3d88f 100644 --- a/gulliver/js/codemirror/mode/q/index.html +++ b/gulliver/js/codemirror/mode/q/index.html @@ -1,17 +1,31 @@ - - - - CodeMirror: Q mode - - - - - - - - -

    CodeMirror: Q mode

    + +CodeMirror: Q mode + + + + + + + + + + +
    +

    Q mode

    +

    MIME types defined: text/x-sh.

    +
    diff --git a/gulliver/js/codemirror/mode/shell/shell.js b/gulliver/js/codemirror/mode/shell/shell.js index 9ce139b41..abfd21445 100644 --- a/gulliver/js/codemirror/mode/shell/shell.js +++ b/gulliver/js/codemirror/mode/shell/shell.js @@ -57,7 +57,7 @@ CodeMirror.defineMode('shell', function() { return 'number'; } } - stream.eatWhile(/\w/); + stream.eatWhile(/[\w-]/); var cur = stream.current(); if (stream.peek() === '=' && /\w+/.test(cur)) return 'def'; return words.hasOwnProperty(cur) ? words[cur] : null; @@ -114,5 +114,5 @@ CodeMirror.defineMode('shell', function() { } }; }); - + CodeMirror.defineMIME('text/x-sh', 'shell'); diff --git a/gulliver/js/codemirror/mode/sieve/LICENSE b/gulliver/js/codemirror/mode/sieve/LICENSE deleted file mode 100644 index 8a74612cb..000000000 --- a/gulliver/js/codemirror/mode/sieve/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (C) 2012 Thomas Schmid - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/gulliver/js/codemirror/mode/sieve/index.html b/gulliver/js/codemirror/mode/sieve/index.html index 8b549815c..9d814a9d5 100644 --- a/gulliver/js/codemirror/mode/sieve/index.html +++ b/gulliver/js/codemirror/mode/sieve/index.html @@ -1,17 +1,30 @@ - - - - CodeMirror: Sieve (RFC5228) mode - - - - - - - -

    CodeMirror: Sieve (RFC5228) mode

    - + + + + +

    A plain text/Smarty version 2 or 3 mode, which allows for custom delimiter tags.

    MIME types defined: text/x-smarty

    - - + diff --git a/gulliver/js/codemirror/mode/smarty/smarty.js b/gulliver/js/codemirror/mode/smarty/smarty.js index 9ee1e4851..826c2b966 100644 --- a/gulliver/js/codemirror/mode/smarty/smarty.js +++ b/gulliver/js/codemirror/mode/smarty/smarty.js @@ -1,140 +1,197 @@ +/** + * Smarty 2 and 3 mode. + */ CodeMirror.defineMode("smarty", function(config) { - var keyFuncs = ["debug", "extends", "function", "include", "literal"]; + "use strict"; + + // our default settings; check to see if they're overridden + var settings = { + rightDelimiter: '}', + leftDelimiter: '{', + smartyVersion: 2 // for backward compatibility + }; + if (config.hasOwnProperty("leftDelimiter")) { + settings.leftDelimiter = config.leftDelimiter; + } + if (config.hasOwnProperty("rightDelimiter")) { + settings.rightDelimiter = config.rightDelimiter; + } + if (config.hasOwnProperty("smartyVersion") && config.smartyVersion === 3) { + settings.smartyVersion = 3; + } + + var keyFunctions = ["debug", "extends", "function", "include", "literal"]; var last; var regs = { operatorChars: /[+\-*&%=<>!?]/, - validIdentifier: /[a-zA-Z0-9\_]/, - stringChar: /[\'\"]/ + validIdentifier: /[a-zA-Z0-9_]/, + stringChar: /['"]/ }; - var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{"; - var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}"; - function ret(style, lst) { last = lst; return style; } - - function tokenizer(stream, state) { - function chain(parser) { + var helpers = { + cont: function(style, lastType) { + last = lastType; + return style; + }, + chain: function(stream, state, parser) { state.tokenize = parser; return parser(stream, state); } + }; - if (stream.match(leftDelim, true)) { - if (stream.eat("*")) { - return chain(inBlock("comment", "*" + rightDelim)); - } - else { - state.tokenize = inSmarty; - return "tag"; - } - } - else { - // I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char - stream.next(); - return null; - } - } - function inSmarty(stream, state) { - if (stream.match(rightDelim, true)) { - state.tokenize = tokenizer; - return ret("tag", null); - } + // our various parsers + var parsers = { - var ch = stream.next(); - if (ch == "$") { - stream.eatWhile(regs.validIdentifier); - return ret("variable-2", "variable"); - } - else if (ch == ".") { - return ret("operator", "property"); - } - else if (regs.stringChar.test(ch)) { - state.tokenize = inAttribute(ch); - return ret("string", "string"); - } - else if (regs.operatorChars.test(ch)) { - stream.eatWhile(regs.operatorChars); - return ret("operator", "operator"); - } - else if (ch == "[" || ch == "]") { - return ret("bracket", "bracket"); - } - else if (/\d/.test(ch)) { - stream.eatWhile(/\d/); - return ret("number", "number"); - } - else { - if (state.last == "variable") { - if (ch == "@") { - stream.eatWhile(regs.validIdentifier); - return ret("property", "property"); + // the main tokenizer + tokenizer: function(stream, state) { + if (stream.match(settings.leftDelimiter, true)) { + if (stream.eat("*")) { + return helpers.chain(stream, state, parsers.inBlock("comment", "*" + settings.rightDelimiter)); + } else { + // Smarty 3 allows { and } surrounded by whitespace to NOT slip into Smarty mode + state.depth++; + var isEol = stream.eol(); + var isFollowedByWhitespace = /\s/.test(stream.peek()); + if (settings.smartyVersion === 3 && settings.leftDelimiter === "{" && (isEol || isFollowedByWhitespace)) { + state.depth--; + return null; + } else { + state.tokenize = parsers.smarty; + last = "startTag"; + return "tag"; + } } - else if (ch == "|") { - stream.eatWhile(regs.validIdentifier); - return ret("qualifier", "modifier"); - } - } - else if (state.last == "whitespace") { - stream.eatWhile(regs.validIdentifier); - return ret("attribute", "modifier"); - } - else if (state.last == "property") { - stream.eatWhile(regs.validIdentifier); - return ret("property", null); - } - else if (/\s/.test(ch)) { - last = "whitespace"; + } else { + stream.next(); return null; } + }, - var str = ""; - if (ch != "/") { - str += ch; - } - var c = ""; - while ((c = stream.eat(regs.validIdentifier))) { - str += c; - } - var i, j; - for (i=0, j=keyFuncs.length; i - - - - CodeMirror: SPARQL mode - - - - - - - - -

    CodeMirror: SPARQL mode

    -
    
       

    MIME type defined: text/x-vb.

    - + diff --git a/gulliver/js/codemirror/mode/vb/vb.js b/gulliver/js/codemirror/mode/vb/vb.js index 764c07101..27b227195 100644 --- a/gulliver/js/codemirror/mode/vb/vb.js +++ b/gulliver/js/codemirror/mode/vb/vb.js @@ -1,10 +1,10 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { var ERRORCLASS = 'error'; - + function wordRegexp(words) { return new RegExp("^((" + words.join(")|(") + "))\\b", "i"); } - + var singleOperators = new RegExp("^[\\+\\-\\*/%&\\\\|\\^~<>!]"); var singleDelimiters = new RegExp('^[\\(\\)\\[\\]\\{\\}@,:`=;\\.]'); var doubleOperators = new RegExp("^((==)|(<>)|(<=)|(>=)|(<>)|(<<)|(>>)|(//)|(\\*\\*))"); @@ -15,9 +15,9 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { var openingKeywords = ['class','module', 'sub','enum','select','while','if','function', 'get','set','property', 'try']; var middleKeywords = ['else','elseif','case', 'catch']; var endKeywords = ['next','loop']; - + var wordOperators = wordRegexp(['and', 'or', 'not', 'xor', 'in']); - var commonkeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until', + var commonkeywords = ['as', 'dim', 'break', 'continue','optional', 'then', 'until', 'goto', 'byval','byref','new','handles','property', 'return', 'const','private', 'protected', 'friend', 'public', 'shared', 'static', 'true','false']; var commontypes = ['integer','string','double','decimal','boolean','short','char', 'float','single']; @@ -34,13 +34,13 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { var indentInfo = null; - + function indent(_stream, state) { state.currentIndent++; } - + function dedent(_stream, state) { state.currentIndent--; } @@ -49,16 +49,16 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { if (stream.eatSpace()) { return null; } - + var ch = stream.peek(); - + // Handle Comments if (ch === "'") { stream.skipToEnd(); return 'comment'; } - - + + // Handle Number Literals if (stream.match(/^((&H)|(&O))?[0-9\.a-f]/i, false)) { var floatLiteral = false; @@ -66,7 +66,7 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { if (stream.match(/^\d*\.\d+F?/i)) { floatLiteral = true; } else if (stream.match(/^\d+\.\d*F?/)) { floatLiteral = true; } else if (stream.match(/^\.\d+F?/)) { floatLiteral = true; } - + if (floatLiteral) { // Float literals may be "imaginary" stream.eat(/J/i); @@ -93,13 +93,13 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { return 'number'; } } - + // Handle Strings if (stream.match(stringPrefixes)) { state.tokenize = tokenStringFactory(stream.current()); return state.tokenize(stream, state); } - + // Handle operators and Delimiters if (stream.match(tripleDelimiters) || stream.match(doubleDelimiters)) { return null; @@ -137,28 +137,28 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { dedent(stream,state); return 'keyword'; } - + if (stream.match(types)) { return 'keyword'; } - + if (stream.match(keywords)) { return 'keyword'; } - + if (stream.match(identifiers)) { return 'variable'; } - + // Handle non-detected items stream.next(); return ERRORCLASS; } - + function tokenStringFactory(delimiter) { var singleline = delimiter.length == 1; var OUTCLASS = 'string'; - + return function(stream, state) { while (!stream.eol()) { stream.eatWhile(/[^'"]/); @@ -179,7 +179,7 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { return OUTCLASS; }; } - + function tokenLexer(stream, state) { var style = state.tokenize(stream, state); @@ -195,8 +195,8 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { return ERRORCLASS; } } - - + + var delimiter_index = '[({'.indexOf(current); if (delimiter_index !== -1) { indent(stream, state ); @@ -212,7 +212,7 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { return ERRORCLASS; } } - + return style; } @@ -229,7 +229,7 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { }; }, - + token: function(stream, state) { if (stream.sol()) { state.currentIndent += state.nextLineIndent; @@ -237,24 +237,23 @@ CodeMirror.defineMode("vb", function(conf, parserConf) { state.doInCurrentLine = 0; } var style = tokenLexer(stream, state); - + state.lastToken = {style:style, content: stream.current()}; - - - + + + return style; }, - + indent: function(state, textAfter) { var trueText = textAfter.replace(/^\s+|\s+$/g, '') ; if (trueText.match(closing) || trueText.match(doubleClosing) || trueText.match(middle)) return conf.indentUnit*(state.currentIndent-1); if(state.currentIndent < 0) return 0; return state.currentIndent * conf.indentUnit; } - + }; return external; }); CodeMirror.defineMIME("text/x-vb", "vb"); - diff --git a/gulliver/js/codemirror/mode/vbscript/index.html b/gulliver/js/codemirror/mode/vbscript/index.html index 8c86f9ef9..9b506b798 100644 --- a/gulliver/js/codemirror/mode/vbscript/index.html +++ b/gulliver/js/codemirror/mode/vbscript/index.html @@ -1,16 +1,30 @@ - - - - CodeMirror: VBScript mode - - - - - - - -

    CodeMirror: VBScript mode

    + +CodeMirror: VBScript mode + + + + + + + + + +
    +

    VBScript mode

    +