text: (*
   * Copyright (c) 2015, Facebook, Inc.
   * All rights reserved.
   *
   * This source code is licensed under the MIT license found in the
   * LICENSE file in the "hack" directory of this source tree.
   *
   *)
  
  open Hh_prelude
  open ServerEnv
  open ServerCommandTypes
  open Utils
  
  let remove_dead_fixme_warning =
    "hh_server was started without '--no-load', which is required when removing dead fixmes.\n"
    ^ "Please run 'hh_client restart --no-load' to restart it."
  
  let take_max_errors error_list max_errors =
    match max_errors with
    | Some max_errors ->
      let (error_list, dropped_errors) = List.split_n error_list max_errors in
      (error_list, List.length dropped_errors)
    | None -> (error_list, 0)
  
  let single_ctx env path file_input =
    let contents =
      match file_input with
      | ServerCommandTypes.FileName path -> Sys_utils.cat path
      | ServerCommandTypes.FileContent contents -> contents
    in
    let ctx = Provider_utils.ctx_from_server_env env in
    Provider_context.add_or_overwrite_entry_contents ~ctx ~path ~contents
  
  let single_ctx_path env path =
    single_ctx
      env
      (Relative_path.create_detect_prefix path)
      (ServerCommandTypes.FileName path)
  
  let handle : type a. genv -> env -> is_stale:bool -> a t -> env * a =
   fun genv env ~is_stale -> function
    | STATUS { max_errors; _ } ->
      HackEventLogger.check_response
        (Errors.get_error_list env.errorl |> List.map ~f:Errors.get_code);
      let error_list = Errors.get_sorted_error_list env.errorl in
      let error_list = List.map ~f:Errors.to_absolute error_list in
      let (error_list, dropped_count) = take_max_errors error_list max_errors in
      let liveness =
        if is_stale then
          Stale_status
        else
          Live_status
      in
      let has_unsaved_changes = ServerFileSync.has_unsaved_changes env in
      let last_recheck_stats =
        Option.map
          env.ServerEnv.last_recheck_loop_stats_for_actual_work
          ~f:ServerEnv.recheck_loop_stats_to_user_telemetry
      in
      ( env,
        {
          Server_status.liveness;
          has_unsaved_changes;
          error_list;
          dropped_count;
          last_recheck_stats;
        } )
    | STATUS_SINGLE (fn, max_errors) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, take_max_errors (ServerStatusSingle.go fn ctx) max_errors)
    | COVERAGE_LEVELS (path, file_input) ->
      let path = Relative_path.create_detect_prefix path in
      let (ctx, entry) = single_ctx env path file_input in
      (env, ServerColorFile.go_quarantined ~ctx ~entry)
    | INFER_TYPE (file_input, line, column, dynamic_view) ->
      let path =
        match file_input with
        | FileName fn -> Relative_path.create_detect_prefix fn
        | FileContent _ -> Relative_path.create_detect_prefix ""
      in
      let (ctx, entry) = single_ctx env path file_input in
      let ctx =
        Provider_context.map_tcopt ctx ~f:(fun tcopt ->
            { tcopt with GlobalOptions.tco_dynamic_view = dynamic_view })
      in
      let result =
        Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
            ServerInferType.go_ctx ~ctx ~entry ~line ~column)
      in
      (env, result)
    | INFER_TYPE_BATCH (experimental, positions, dynamic_view) ->
      let tcopt = env.ServerEnv.tcopt in
      let tcopt = { tcopt with GlobalOptions.tco_dynamic_view = dynamic_view } in
      let env = { env with tcopt } in
      (env, ServerInferTypeBatch.go genv.workers experimental positions env)
    | IDE_HOVER (path, line, column) ->
      let (ctx, entry) = single_ctx_path env path in
      let result = ServerHover.go_quarantined ~ctx ~entry ~line ~column in
      (env, result)
    | DOCBLOCK_AT (path, line, column, _, kind) ->
      let (ctx, entry) = single_ctx_path env path in
      let r = ServerDocblockAt.go_docblock_ctx ~ctx ~entry ~line ~column ~kind in
      (env, r)
    | DOCBLOCK_FOR_SYMBOL (symbol, kind) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      let r = ServerDocblockAt.go_docblock_for_symbol ~ctx ~symbol ~kind in
      (env, r)
    | IDE_SIGNATURE_HELP (path, line, column) ->
      let (ctx, entry) = single_ctx_path env path in
      (env, ServerSignatureHelp.go_quarantined ~ctx ~entry ~line ~column)
    | COMMANDLINE_AUTOCOMPLETE contents ->
      (* For command line autocomplete, we assume the AUTO332 text has
      already been inserted, and we fake the rest of this information. *)
      let autocomplete_context =
        {
          AutocompleteTypes.is_manually_invoked = false;
          is_xhp_classname = false;
          is_instance_member = false;
          is_after_single_colon = false;
          is_after_double_right_angle_bracket = false;
          is_after_open_square_bracket = false;
          is_after_quote = false;
          is_before_apostrophe = false;
          char_at_pos = ' ';
        }
      in
      (* Since this is being executed from the command line,
       * let's turn on the flags that increase accuracy but slow it down *)
      let sienv =
        {
          env.ServerEnv.local_symbol_table with
          SearchUtils.sie_resolve_signatures = true;
          SearchUtils.sie_resolve_positions = true;
          SearchUtils.sie_resolve_local_decl = true;
        }
      in
      (* feature not implemented here; it only works for LSP *)
      let (ctx, entry) =
        Provider_context.add_or_overwrite_entry_contents
          ~ctx:(Provider_utils.ctx_from_server_env env)
          ~path:(Relative_path.create_detect_prefix "")
          ~contents
      in
      (* Update the symbol index from this file *)
      Facts_parser.mangle_xhp_mode := false;
      let facts_opt =
        Facts_parser.from_text
          ~php5_compat_mode:false
          ~hhvm_compat_mode:true
          ~disable_nontoplevel_declarations:false
          ~disable_legacy_soft_typehints:false
          ~allow_new_attribute_syntax:false
          ~disable_legacy_attribute_syntax:false
          ~enable_xhp_class_modifier:false
          ~disable_xhp_element_mangling:false
          ~filename:Relative_path.default
          ~text:contents
      in
      let sienv =
        match facts_opt with
        | None -> sienv
        | Some facts ->
          SymbolIndex.update_from_facts ~sienv ~path:Relative_path.default ~facts
      in
      let result =
        ServerAutoComplete.go_at_auto332_ctx
          ~ctx
          ~entry
          ~sienv
          ~autocomplete_context
      in
      (env, result.With_complete_flag.value)
    | IDENTIFY_SYMBOL arg ->
      let ctx = Provider_utils.ctx_from_server_env env in
      let get_def_opt type_ name =
        ServerSymbolDefinition.go
          ctx
          None
          SymbolOccurrence.{ type_; name; is_declaration = false; pos = Pos.none }
        |> Option.to_list
        |> List.map ~f:SymbolDefinition.to_absolute
      in
      let arg = Str.split (Str.regexp "::") arg in
      (* The following are all the different named entities I could think of in Hack. *)
      let results =
        match arg with
        | [c_name; member] ->
          let c_name = Utils.add_ns c_name in
          List.concat
            [
              get_def_opt (SymbolOccurrence.Method (c_name, member)) "";
              get_def_opt (SymbolOccurrence.Property (c_name, member)) "";
              get_def_opt (SymbolOccurrence.ClassConst (c_name, member)) "";
              get_def_opt (SymbolOccurrence.Typeconst (c_name, member)) "";
            ]
        | [name] ->
          let name = Utils.add_ns name in
          List.concat
            [
              get_def_opt SymbolOccurrence.Class name;
              (* SymbolOccurrence.Record and Class find the same things *)
              get_def_opt SymbolOccurrence.Function name;
              get_def_opt SymbolOccurrence.GConst name;
            ]
        | _ -> []
      in
      (env, results)
    | IDENTIFY_FUNCTION (filename, file_input, line, column) ->
      let (ctx, entry) =
        single_ctx env (Relative_path.create_detect_prefix filename) file_input
      in
      let result =
        Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
            ServerIdentifyFunction.go_quarantined_absolute
              ~ctx
              ~entry
              ~line
              ~column)
      in
      (env, result)
    | METHOD_JUMP (class_, filter, find_children) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      ( env,
        MethodJumps.get_inheritance
          ctx
          class_
          ~filter
          ~find_children
          env.naming_table
          genv.workers )
    | METHOD_JUMP_BATCH (classes, filter) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerMethodJumpsBatch.go ctx genv.workers classes filter)
    | FIND_REFS find_refs_action ->
      let ctx = Provider_utils.ctx_from_server_env env in
      Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
          let open Done_or_retry in
          let include_defs = false in
          ServerFindRefs.(
            go ctx find_refs_action include_defs genv env
            |> map_env ~f:to_absolute))
    | GO_TO_IMPL go_to_impl_action ->
      Done_or_retry.(
        ServerGoToImpl.go go_to_impl_action genv env
        |> map_env ~f:ServerFindRefs.to_absolute)
    | IDE_FIND_REFS (labelled_file, line, column, include_defs) ->
      Done_or_retry.(
        let (path, file_input) =
          ServerCommandTypesUtils.extract_labelled_file labelled_file
        in
        let (ctx, entry) = single_ctx env path file_input in
        Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
            match ServerFindRefs.go_from_file_ctx ~ctx ~entry ~line ~column with
            | None -> (env, Done None)
            | Some (name, action) ->
              map_env
                ~f:(ServerFindRefs.to_ide name)
                (ServerFindRefs.go ctx action include_defs genv env)))
    | IDE_GO_TO_IMPL (labelled_file, line, column) ->
      Done_or_retry.(
        let (path, file_input) =
          ServerCommandTypesUtils.extract_labelled_file labelled_file
        in
        let (ctx, entry) = single_ctx env path file_input in
        (match ServerFindRefs.go_from_file_ctx ~ctx ~entry ~line ~column with
        | None -> (env, Done None)
        | Some (name, action) ->
          map_env
            ~f:(ServerFindRefs.to_ide name)
            (ServerGoToImpl.go ~action ~genv ~env)))
    | IDE_HIGHLIGHT_REFS (path, file_input, line, column) ->
      let (ctx, entry) =
        single_ctx env (Relative_path.create_detect_prefix path) file_input
      in
      let r =
        Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
            (env, ServerHighlightRefs.go_quarantined ~ctx ~entry ~line ~column))
      in
      r
    | REFACTOR refactor_action ->
      let ctx = Provider_utils.ctx_from_server_env env in
      Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
          ServerRefactor.go ctx refactor_action genv env)
    | IDE_REFACTOR
        { ServerCommandTypes.Ide_refactor_type.filename; line; char; new_name } ->
      let ctx = Provider_utils.ctx_from_server_env env in
      Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
          let open Done_or_retry in
          match
            ServerRefactor.go_ide ctx (filename, line, char) new_name genv env
          with
          | Error e -> (env, Done (Error e))
          | Ok r -> map_env r ~f:(fun x -> Ok x))
    | REMOVE_DEAD_FIXMES codes ->
      if genv.ServerEnv.options |> ServerArgs.no_load then (
        HackEventLogger.check_response
          (Errors.get_error_list env.errorl |> List.map ~f:Errors.get_code);
        (env, `Ok (ServerRefactor.get_fixme_patches codes env))
      ) else
        (env, `Error remove_dead_fixme_warning)
    | REWRITE_LAMBDA_PARAMETERS files ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerRefactor.get_lambda_parameter_rewrite_patches ctx files)
    | REWRITE_TYPE_PARAMS_TYPE files ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerRefactor.get_type_params_type_rewrite_patches ctx files)
    | DUMP_SYMBOL_INFO file_list ->
      (env, SymbolInfoService.go genv.workers file_list env)
    | IN_MEMORY_DEP_TABLE_SIZE ->
      (env, SaveStateService.get_in_memory_dep_table_entry_count ())
    | SAVE_NAMING filename ->
      (env, SaveStateService.go_naming env.naming_table filename)
    | SAVE_STATE
        (filename, gen_saved_ignore_type_errors, replace_state_after_saving) ->
      if Errors.is_empty env.errorl || gen_saved_ignore_type_errors then
        let save_decls =
          genv.local_config.ServerLocalConfig.store_decls_in_saved_state
        in
        ( env,
          SaveStateService.go ~save_decls env filename ~replace_state_after_saving
        )
      else
        (env, Error "There are typecheck errors; cannot generate saved state.")
    | SEARCH (query, type_) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      let lst = env.ServerEnv.local_symbol_table in
      (env, ServerSearch.go ctx query type_ lst)
    | COVERAGE_COUNTS path -> (env, ServerCoverageMetric.go path genv env)
    | LINT fnl ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerLint.go genv ctx fnl)
    | LINT_STDIN { filename; contents } ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerLint.go_stdin ctx ~filename ~contents)
    | LINT_ALL code ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerLint.lint_all genv ctx code)
    | LINT_XCONTROLLER fnl -> (env, ServerLint.go_xcontroller genv env fnl)
    | CREATE_CHECKPOINT x -> (env, ServerCheckpoint.create_checkpoint x)
    | RETRIEVE_CHECKPOINT x -> (env, ServerCheckpoint.retrieve_checkpoint x)
    | DELETE_CHECKPOINT x -> (env, ServerCheckpoint.delete_checkpoint x)
    | STATS -> (env, Stats.get_stats ())
    | FORMAT (content, from, to_) ->
      let legacy_format_options =
        { Lsp.DocumentFormatting.tabSize = 2; insertSpaces = true }
      in
      (env, ServerFormat.go content from to_ legacy_format_options)
    | AI_QUERY _ -> (env, "Ai_query is deprecated")
    | DUMP_FULL_FIDELITY_PARSE file -> (env, FullFidelityParseService.go file)
    | OPEN_FILE (path, contents) ->
      let predeclare = genv.local_config.ServerLocalConfig.predeclare_ide in
      (ServerFileSync.open_file ~predeclare env path contents, ())
    | CLOSE_FILE path -> (ServerFileSync.close_file env path, ())
    | EDIT_FILE (path, edits) ->
      let predeclare = genv.local_config.ServerLocalConfig.predeclare_ide in
      let edits = List.map edits ~f:Ide_api_types.ide_text_edit_to_fc in
      (ServerFileSync.edit_file ~predeclare env path edits, ())
    | IDE_AUTOCOMPLETE (filename, pos, is_manually_invoked) ->
      let pos = pos |> Ide_api_types.ide_pos_to_fc in
      let contents =
        ServerFileSync.get_file_content (ServerCommandTypes.FileName filename)
      in
      let (ctx, entry) =
        Provider_context.add_or_overwrite_entry_contents
          ~ctx:(Provider_utils.ctx_from_server_env env)
          ~path:(Relative_path.create_detect_prefix filename)
          ~contents
      in
      let results =
        Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
            ServerAutoComplete.go_ctx
              ~ctx
              ~entry
              ~sienv:env.ServerEnv.local_symbol_table
              ~is_manually_invoked
              ~line:pos.File_content.line
              ~column:pos.File_content.column)
      in
      (env, results)
    | IDE_FFP_AUTOCOMPLETE (path, pos) ->
      let pos = pos |> Ide_api_types.ide_pos_to_fc in
      let contents =
        ServerFileSync.get_file_content (ServerCommandTypes.FileName path)
      in
      let offset = File_content.get_offset contents pos in
      (* will raise if out of bounds *)
      let char_at_pos = File_content.get_char contents offset in
      let ctx = Provider_utils.ctx_from_server_env env in
      let (ctx, entry) =
        Provider_context.add_or_overwrite_entry_contents
          ~ctx
          ~path:(Relative_path.create_detect_prefix path)
          ~contents
      in
      let result =
        FfpAutocompleteService.auto_complete
          ctx
          entry
          pos
          ~filter_by_token:false
          ~sienv:env.ServerEnv.local_symbol_table
      in
      ( env,
        {
          AutocompleteTypes.completions = result;
          char_at_pos;
          is_complete = true;
        } )
    | DISCONNECT -> (ServerFileSync.clear_sync_data env, ())
    | SUBSCRIBE_DIAGNOSTIC id ->
      let init =
        if is_full_check_done env.full_check then
          env.errorl
        else
          Errors.empty
      in
      let new_env =
        { env with diag_subscribe = Some (Diagnostic_subscription.of_id id init) }
      in
      let () = Hh_logger.log "Diag_subscribe: SUBSCRIBE %d" id in
      (new_env, ())
    | UNSUBSCRIBE_DIAGNOSTIC id ->
      let diag_subscribe =
        match env.diag_subscribe with
        | Some x when Diagnostic_subscription.get_id x = id ->
          let () = Hh_logger.log "Diag_subscribe: UNSUBSCRIBE %d" id in
          None
        | x ->
          let () = Hh_logger.log "Diag_subscribe: UNSUBSCRIBE %d no effect" id in
          x
      in
      let new_env = { env with diag_subscribe } in
      (new_env, ())
    | OUTLINE path ->
      ( env,
        ServerCommandTypes.FileName path
        |> ServerFileSync.get_file_content
        |> FileOutline.outline env.popt )
    | IDE_IDLE -> ({ env with ide_idle = true }, ())
    | RAGE -> (env, ServerRage.go genv env)
    | DYNAMIC_VIEW toggle -> (ServerFileSync.toggle_dynamic_view env toggle, ())
    | CST_SEARCH { sort_results; input; files_to_search } ->
      begin
        try
          (env, CstSearchService.go genv env ~sort_results ~files_to_search input)
        with
        | MultiThreadedCall.Coalesced_failures failures ->
          let failures =
            failures
            |> List.map ~f:WorkerController.failure_to_string
            |> String.concat ~sep:"\n"
          in
          ( env,
            Error
              (Printf.sprintf
                 "Worker failures - check the logs for more details:\n%s\n"
                 failures) )
        | e ->
          let msg = Exn.to_string e in
          let stack = Printexc.get_backtrace () in
          (env, Error (Printf.sprintf "%s\n%s" msg stack))
      end
    | NO_PRECHECKED_FILES -> (ServerPrecheckedFiles.expand_all env, ())
    | GEN_HOT_CLASSES threshold ->
      (env, ServerHotClasses.go genv.workers env threshold)
    | FUN_DEPS_BATCH (positions, dynamic_view) ->
      let tcopt = env.ServerEnv.tcopt in
      let tcopt = { tcopt with GlobalOptions.tco_dynamic_view = dynamic_view } in
      let env = { env with tcopt } in
      (env, ServerFunDepsBatch.go genv.workers positions env)
    | FUN_IS_LOCALLABLE_BATCH positions ->
      let env = { env with tcopt = env.ServerEnv.tcopt } in
      (env, ServerFunIsLocallableBatch.go genv.workers positions env)
    | LIST_FILES_WITH_ERRORS -> (env, ServerEnv.list_files env)
    | FILE_DEPENDENTS filenames ->
      let files = ServerFileDependents.go genv env filenames in
      (env, files)
    | IDENTIFY_TYPES (labelled_file, line, column) ->
      let (path, file_input) =
        ServerCommandTypesUtils.extract_labelled_file labelled_file
      in
      let (ctx, entry) = single_ctx env path file_input in
      let result =
        ServerTypeDefinition.go_quarantined ~ctx ~entry ~line ~column
      in
      (env, result)
    | EXTRACT_STANDALONE target ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (env, ServerExtractStandalone.go ctx target)
    | CONCATENATE_ALL paths -> (env, ServerConcatenateAll.go genv env paths)
    | GO_TO_DEFINITION (labelled_file, line, column) ->
      let (path, file_input) =
        ServerCommandTypesUtils.extract_labelled_file labelled_file
      in
      let (ctx, entry) = single_ctx env path file_input in
      Provider_utils.respect_but_quarantine_unsaved_changes ~ctx ~f:(fun () ->
          (env, ServerGoToDefinition.go_quarantined ~ctx ~entry ~line ~column))
    | BIGCODE path ->
      let (ctx, entry) = single_ctx_path env path in
      let result = ServerBigCode.go_ctx ~ctx ~entry in
      (env, result)
    | VERBOSE verbose ->
      if verbose then
        Hh_logger.Level.set_min_level Hh_logger.Level.Debug
      else
        Hh_logger.Level.set_min_level
          genv.local_config.ServerLocalConfig.min_log_level;
      (env, ())
    | PAUSE pause ->
      let env =
        if pause then
          {
            env with
            full_recheck_on_file_changes =
              Paused { paused_recheck_id = env.init_env.recheck_id };
          }
        else
          { env with full_recheck_on_file_changes = Resumed }
      in
      (env, ())
    | GLOBAL_INFERENCE (submode, files) ->
      let ctx = Provider_utils.ctx_from_server_env env in
      (* We are getting files in the reverse order*)
      let files = List.rev files in
      (env, ServerGlobalInference.execute ctx submode files)
  